Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[getLedgerEntries] XDR to update when contract key is updated #1236

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 112 additions & 60 deletions src/components/FormElements/XdrLedgerKeyPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactElement, useEffect, useState } from "react";
import { parse, stringify } from "lossless-json";

import { Button, Card, Icon, Select } from "@stellar/design-system";

Expand Down Expand Up @@ -116,21 +117,20 @@ export const XdrLedgerKeyPicker = ({

const isXdrInit = useIsXdrInit();

const xdrDecodeJson = (xdr: string) => {
const xdrDecodeLedgerKeyJson = (xdr: string) => {
if (!isXdrInit) {
return null;
}

try {
const xdrJson = StellarXdr.decode("LedgerKey", xdr);
const xdrJson = parse(StellarXdr.decode("LedgerKey", xdr));

return {
jsonString: xdrJson,
xdrJson,
error: "",
};
} catch (e) {
return {
jsonString: "",
error: `Unable to decode input as LedgerKey: ${e}`,
};
}
Expand All @@ -142,7 +142,8 @@ export const XdrLedgerKeyPicker = ({
}

try {
JSON.parse(str);
// check to see if it's a valid JSON
parse(str);
} catch (e) {
return {
xdrString: "",
Expand Down Expand Up @@ -172,79 +173,112 @@ export const XdrLedgerKeyPicker = ({
onChange("");
};

const getKeyType = (val: string) =>
const getLedgerKeyFields = (val: string) =>
ledgerKeyFields.find((field) => field.id === val);

// validate the xdr string on input change
const validateLedgerKeyXdr = (xdrString: string) => {
// if the xdr string is empty (by deleting the input or other)
// reset the form
if (!xdrString) {
setLedgerKeyXdrError("");
reset();
return;
}

// check error
const error = validate.getXdrError(xdrString, "LedgerKey");
const xdrJsonDecoded = xdrDecodeJson(xdrString);
const xdrJsonDecoded = xdrDecodeLedgerKeyJson(xdrString);

// check if the xdr string is valid
if (error?.result === "error") {
setLedgerKeyXdrError(error.message);
return;
}

// check if there was an error during the xdr string decoding
if (xdrJsonDecoded?.error) {
setLedgerKeyXdrError(xdrJsonDecoded?.error);
return;
}

// success case:
// xdr decoded successfully to JSON
if (xdrJsonDecoded?.jsonString) {
setLedgerKeyXdrError("");
// success case: xdr decoded successfully to JSON
if (xdrJsonDecoded?.xdrJson) {
let xdrJsonToString;

const xdrDecodedJSON = JSON.parse(xdrJsonDecoded.jsonString);
const key = Object.keys(xdrDecodedJSON)[0];
const selectedKeyType = getKeyType(key);
// reset the existing error
setLedgerKeyXdrError("");

// Transform LedgerKey Trustline's 'asset' object to match what <AssetPicker/> accepts
if (key === "trustline") {
if (xdrDecodedJSON[key].asset === "native") {
xdrDecodedJSON[key].asset = {
const xdrDecodedJSON = xdrJsonDecoded.xdrJson as Record<
LedgerKeyType,
any
>;

// returns the following ledger key type:
//// account
//// trustline
//// offer
//// data
//// claimable_balance
//// liquidity_pool
//// contract_data
//// contract_code
//// config_setting
//// ttl
const ledgerKey = Object.keys(xdrDecodedJSON)[0] as LedgerKeyType;
const selectedLedgerKeyField = getLedgerKeyFields(ledgerKey);

if (ledgerKey === "trustline") {
// handle native asset
if (xdrDecodedJSON[ledgerKey].asset === "native") {
// transform LedgerKey Trustline's 'asset' object to
// match what <AssetPicker/> accepts
xdrDecodedJSON[ledgerKey].asset = {
code: "",
issuer: "",
type: xdrDecodedJSON[key].asset,
type: xdrDecodedJSON[ledgerKey].asset,
};
}

if (xdrJsonDecoded.jsonString.includes("credit_alphanum")) {
const assetType = Object.keys(xdrDecodedJSON[key].asset)[0];

xdrDecodedJSON[key].asset = {
code: xdrDecodedJSON[key].asset[assetType].asset_code,
issuer: xdrDecodedJSON[key].asset[assetType].issuer,
// handle credit alphanum asset (credit_alphanum4 or credit_alphanum12)
const assetType = Object.keys(xdrDecodedJSON[ledgerKey].asset)[0];

if (
assetType === "credit_alphanum4" ||
assetType === "credit_alphanum12"
) {
xdrDecodedJSON[ledgerKey].asset = {
code: xdrDecodedJSON[ledgerKey].asset[assetType].asset_code,
issuer: xdrDecodedJSON[ledgerKey].asset[assetType].issuer,
type: assetType,
};
}

if (xdrJsonDecoded.jsonString.includes("pool_share")) {
xdrDecodedJSON[key].asset = {
if (assetType === "pool_share") {
xdrDecodedJSON[ledgerKey].asset = {
type: "pool_share",
pool_share: xdrDecodedJSON[key].asset.pool_share,
pool_share: xdrDecodedJSON[ledgerKey].asset.pool_share,
};
}

const xdrDecodedJsonToString = JSON.stringify(xdrDecodedJSON);
setLedgerKeyJsonString(xdrDecodedJsonToString);
// convert the xdr decoded JSON to a string
xdrJsonToString = stringify(xdrDecodedJSON) || "";
} else {
setLedgerKeyJsonString(xdrJsonDecoded.jsonString);
// @TODO: handle other ledger key types (or I don't know what this is for)
xdrJsonToString = stringify(xdrJsonDecoded.xdrJson) || "";
}

if (selectedKeyType) {
setSelectedLedgerKey(selectedKeyType);
setLedgerKeyJsonString(xdrJsonToString);

if (selectedLedgerKeyField) {
setSelectedLedgerKey(selectedLedgerKeyField);
}
return;
}
};

useEffect(() => {
// this creates a form based on the ledger key that got selected from the dropdown
// this is used to create the form for the ledger key template
// ledgerKeyFields
if (!ledgerKeyXdrJsonString && selectedLedgerKey) {
const templatesArr = selectedLedgerKey.templates.split(",");
const formLedgerKeyJson = templatesArr.reduce((accr, item) => {
Expand All @@ -257,17 +291,19 @@ export const XdrLedgerKeyPicker = ({
}, {} as AnyObject);

const formLedgerKeyJsonString = JSON.stringify(formLedgerKeyJson);

setLedgerKeyJsonString(formLedgerKeyJsonString);
}
}, [ledgerKeyXdrJsonString, selectedLedgerKey]);

const renderLedgerKeyTemplate = () => {
// if there is no selected ledger key or the xdr string is empty, return null
if (!selectedLedgerKey || !ledgerKeyXdrJsonString) {
return null;
}

return selectedLedgerKey.templates.split(",").map((template) => {
const ledgerKeyStringToJson = JSON.parse(ledgerKeyXdrJsonString);
const ledgerKeyStringToJson = parse(ledgerKeyXdrJsonString) as AnyObject;
const obj = ledgerKeyStringToJson[selectedLedgerKey!.id];

let jsonXdrEncoded;
Expand All @@ -278,7 +314,9 @@ export const XdrLedgerKeyPicker = ({
);

if (component) {
const handleChange = (val: any, stringifiedVal: any) => {
// handle the change of the input value of the forms created from the
// Ledger Key dropdown (not the Ledger Key XDR input)
const handleChange = (val: any) => {
const error = component.validate?.(val);

if (error && error.result !== "success") {
Expand All @@ -287,11 +325,15 @@ export const XdrLedgerKeyPicker = ({
setFormError({ ...formError, [template]: "" });
}

obj[template] = stringifiedVal;
obj[template] = val;

// stringify the updated json with the input value
const ledgerKeyJsonToString = JSON.stringify(ledgerKeyStringToJson);
setLedgerKeyJsonString(ledgerKeyJsonToString);
// For Contract Data Ledger Key
// we need to parse the key value because it's a nested JSON string
if (template === "key") {
ledgerKeyStringToJson[selectedLedgerKey.id].key = parse(
obj[template],
);
}

// update each template's field with an input value
if (template === "asset") {
Expand Down Expand Up @@ -324,54 +366,62 @@ export const XdrLedgerKeyPicker = ({
};
}

const newObjToString = JSON.stringify(newObj);
const newObjToString = stringify(newObj) || "";
jsonXdrEncoded = jsonEncodeXdr(newObjToString);
} else {
jsonXdrEncoded = jsonEncodeXdr(ledgerKeyJsonToString);
}

// stringify the updated json with the input value
const ledgerKeyJsonToString = stringify(ledgerKeyStringToJson);

setLedgerKeyJsonString(ledgerKeyJsonToString || "");

// for every template that is not an asset, encode the json string
jsonXdrEncoded = jsonEncodeXdr(ledgerKeyJsonToString || "");

if (jsonXdrEncoded?.error) {
setLedgerKeyXdrError(jsonXdrEncoded.error);
return;
}

if (jsonXdrEncoded?.xdrString) {
onChange(jsonXdrEncoded.xdrString);
}
};

const baseProps = {
value: ledgerKeyStringToJson[selectedLedgerKey.id][template] || "",
error: formError[template],
isRequired: true,
disabled: isXdrInputActive,
};

if (template === "asset") {
return component.render({
value: ledgerKeyStringToJson[selectedLedgerKey.id][template],
error: formError[template],
...baseProps,
onChange: (assetObjVal: AssetObjectValue) => {
handleChange(
assetObjVal,
isEmptyObject(sanitizeObject(assetObjVal || {}))
? undefined
: JSON.stringify(assetObjVal),
);
},
isRequired: true,
disabled: isXdrInputActive,
});
}

if (template === "config_setting_id") {
return component.render({
value: ledgerKeyStringToJson[selectedLedgerKey.id][template],
error: formError[template],
...baseProps,
onChange: (selectedConfigSetting: ConfigSettingIdType) => {
handleChange(selectedConfigSetting, selectedConfigSetting);
handleChange(selectedConfigSetting);
},
isRequired: true,
disabled: isXdrInputActive,
});
}

return component.render({
value: ledgerKeyStringToJson[selectedLedgerKey.id][template],
error: formError[template],
...baseProps,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
handleChange(e.target.value, e.target.value);
handleChange(e.target.value);
},
isRequired: true,
disabled: isXdrInputActive,
});
}
return null;
Expand Down Expand Up @@ -414,7 +464,9 @@ export const XdrLedgerKeyPicker = ({
reset();

const selectedVal = e.target.value;
setSelectedLedgerKey(selectedVal ? getKeyType(selectedVal)! : null);
setSelectedLedgerKey(
selectedVal ? getLedgerKeyFields(selectedVal)! : null,
);
}}
disabled={isXdrInputActive}
>
Expand Down
25 changes: 18 additions & 7 deletions src/components/formComponentTemplateEndpoints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -474,14 +474,25 @@ export const formComponentTemplateEndpoints = (
fieldSize="md"
key={id}
id={id}
label="Key"
placeholder="Ex: 67260c4c1807b262ff851b0a3fe141194936bb0215b2f77447f1df11998eabb9"
// @TODO we should display an input for each value
// hotfix: sanitizing value from backlashes and extra quotes
label="Key (ScVal)"
rows={10}
placeholder={`{
"vec": [
{
"symbol": "Balance"
},
{
"address": "CDGAH7TU7UH3BXGYXRIXLJX63LYRIF6APZPIG64ZAW3NNDCPJ7AAWVTZ"
}
]
}
}
}`}
// Convert object to string if needed
value={
JSON.stringify(templ.value)
.replace(/\\/g, "")
.replace(/^"+|"+$/g, "") || ""
typeof templ.value === "object"
? JSON.stringify(templ.value, null, 2)
: templ.value || ""
}
error={templ.error}
onChange={templ.onChange}
Expand Down
Loading