Compare commits

...

22 Commits

Author SHA1 Message Date
Valtteri Karesto
4472957f5c Updated based on feedback 2022-05-27 10:43:16 +03:00
Valtteri Karesto
9a6ef2c393 Minor fixes based on feedback 2022-05-25 13:39:52 +03:00
Valtteri Karesto
56203ce9c6 Remove package-lock 2022-05-25 13:10:05 +03:00
Valtteri Karesto
933bdb5968 Add fee estimate button to fee and update the deploying 2022-05-25 13:05:04 +03:00
Valtteri Karesto
864711697b Update accounts 2022-05-25 13:03:49 +03:00
Valtteri Karesto
e5eaf09721 Just return the fee values, no mutating 2022-05-25 13:03:38 +03:00
Valtteri Karesto
d0dde56c67 Merge pull request #192 from XRPLF/fix/fix-sequence-number
Fix/fix sequence number
2022-05-24 18:20:31 +03:00
Valtteri Karesto
45c6927e72 Take next sequence number from response 2022-05-24 18:16:56 +03:00
Valtteri Karesto
6014b6e79f Update sequence after successful request 2022-05-24 18:14:01 +03:00
Valtteri Karesto
04a99227df Merge pull request #191 from XRPLF/fix/v2-updates
Fix/v2 updates
2022-05-24 15:44:00 +03:00
Valtteri Karesto
0965a1e898 Merge pull request #190 from XRPLF/feat/update-descriptions
Update meta tags
2022-05-24 15:43:47 +03:00
Valtteri Karesto
32445dbebf Mutate tx with fee estimation 2022-05-24 15:27:20 +03:00
Valtteri Karesto
1a1d4901aa Add estimate fee function 2022-05-24 15:26:38 +03:00
Valtteri Karesto
8b646c56dc Fix recursion 2022-05-24 15:26:03 +03:00
Valtteri Karesto
ac38bbc72c Turn the cleaner on by default 2022-05-24 15:25:33 +03:00
Valtteri Karesto
bf1182351a Added link to project 2022-05-23 13:28:41 +03:00
Valtteri Karesto
55e48a943b Update readme 2022-05-23 13:26:46 +03:00
Valtteri Karesto
faf417be69 Update meta tags 2022-05-23 08:55:03 +03:00
Valtteri Karesto
c2eb57211f hotfix/Remove debug code 2022-05-12 14:31:00 +03:00
Valtteri Karesto
0e97df3c8e Merge pull request #188 from eqlabs/fix/old-account-fix
Fixes to users who has old accounts
2022-05-12 08:35:02 +03:00
Valtteri Karesto
5dd0dfdc18 Fixes to users who has old accounts 2022-05-11 16:07:49 +03:00
Valtteri Karesto
ef48bac8f6 Merge pull request #187 from eqlabs/feat/fix-modal
Updated main modal
2022-05-11 14:31:53 +03:00
9 changed files with 403 additions and 12292 deletions

View File

@@ -1,6 +1,8 @@
# XRPL Hooks IDE
# XRPL Hooks Builder
This is the repository for XRPL Hooks IDE. This project is built with Next.JS
https://hooks-builder.xrpl.org/
This is the repository for XRPL Hooks Builder. This project is built with Next.JS
## General

View File

@@ -304,6 +304,18 @@ const Accounts: FC<AccountProps> = (props) => {
if (accountToUpdate) {
accountToUpdate.xrp = balance;
accountToUpdate.sequence = sequence;
accountToUpdate.error = null;
} else {
const oldAccount = state.accounts.find(
(acc) => acc.address === res?.account
);
if (oldAccount) {
oldAccount.xrp = "0";
oldAccount.error = {
code: res?.error,
message: res?.error_message,
};
}
}
});
const objectRequests = snap.accounts.map((acc) => {
@@ -343,7 +355,7 @@ const Accounts: FC<AccountProps> = (props) => {
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [snap.accounts, snap.clientStatus]);
}, [snap.accounts.length, snap.clientStatus]);
return (
<Box
as="div"
@@ -431,18 +443,23 @@ const Accounts: FC<AccountProps> = (props) => {
wordBreak: "break-word",
}}
>
{account.address} (
{Dinero({
amount: Number(account?.xrp || "0"),
precision: 6,
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
})}
)
{account.address}{" "}
{!account?.error ? (
`(${Dinero({
amount: Number(account?.xrp || "0"),
precision: 6,
})
.toUnit()
.toLocaleString(undefined, {
style: "currency",
currency: "XRP",
currencyDisplay: "name",
})})`
) : (
<Box css={{ color: "$red11" }}>
(Account not found, request funds to activate account)
</Box>
)}
</Text>
</Box>
{!props.hideDeployBtn && (
@@ -452,7 +469,7 @@ const Accounts: FC<AccountProps> = (props) => {
e.stopPropagation();
}}
>
<SetHookDialog account={account} />
<SetHookDialog accountAddress={account.address} />
</div>
)}
</Flex>

View File

@@ -21,11 +21,11 @@ import {
import { TTS, tts } from "../utils/hookOnCalculator";
import { deployHook } from "../state/actions";
import type { IAccount } from "../state";
import { useSnapshot } from "valtio";
import state from "../state";
import toast from "react-hot-toast";
import { sha256 } from "../state/actions/deployHook";
import { prepareDeployHookTx, sha256 } from "../state/actions/deployHook";
import estimateFee from "../utils/estimateFee";
const transactionOptions = Object.keys(tts).map((key) => ({
label: key,
@@ -37,6 +37,7 @@ export type SetHookData = {
value: keyof TTS;
label: string;
}[];
Fee: string;
HookNamespace: string;
HookParameters: {
HookParameter: {
@@ -52,176 +53,266 @@ export type SetHookData = {
// }[];
};
export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
const snap = useSnapshot(state);
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const {
register,
handleSubmit,
control,
watch,
setValue,
formState: { errors },
} = useForm<SetHookData>({
defaultValues: {
HookNamespace: snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "HookParameters", // unique name for your Field Array
});
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
({ accountAddress }) => {
const snap = useSnapshot(state);
const account = snap.accounts.find((acc) => acc.address === accountAddress);
// Update value if activeWat changes
useEffect(() => {
setValue(
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false);
const {
register,
handleSubmit,
control,
watch,
setValue,
getValues,
formState: { errors },
} = useForm<SetHookData>({
defaultValues: {
HookNamespace:
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || "",
Invoke: transactionOptions.filter((to) => to.label === "ttPAYMENT"),
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "HookParameters", // unique name for your Field Array
});
const [formInitialized, setFormInitialized] = useState(false);
const [estimateLoading, setEstimateLoading] = useState(false);
// Update value if activeWat changes
useEffect(() => {
setValue(
"HookNamespace",
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
);
setFormInitialized(true);
}, [snap.activeWat, snap.files, setValue]);
// const {
// fields: grantFields,
// append: grantAppend,
// remove: grantRemove,
// } = useFieldArray({
// control,
// name: "HookGrants", // unique name for your Field Array
// });
const [hashedNamespace, setHashedNamespace] = useState("");
const namespace = watch(
"HookNamespace",
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
);
}, [snap.activeWat, snap.files, setValue]);
// const {
// fields: grantFields,
// append: grantAppend,
// remove: grantRemove,
// } = useFieldArray({
// control,
// name: "HookGrants", // unique name for your Field Array
// });
const [hashedNamespace, setHashedNamespace] = useState("");
const namespace = watch(
"HookNamespace",
snap.files?.[snap.active]?.name?.split(".")?.[0] || ""
);
const calculateHashedValue = useCallback(async () => {
const hashedVal = await sha256(namespace);
setHashedNamespace(hashedVal.toUpperCase());
}, [namespace]);
useEffect(() => {
calculateHashedValue();
}, [namespace, calculateHashedValue]);
const calculateHashedValue = useCallback(async () => {
const hashedVal = await sha256(namespace);
setHashedNamespace(hashedVal.toUpperCase());
}, [namespace]);
useEffect(() => {
calculateHashedValue();
}, [namespace, calculateHashedValue]);
if (!account) {
return null;
}
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
const currAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currAccount) currAccount.isLoading = true;
const res = await deployHook(account, data);
if (currAccount) currAccount.isLoading = false;
if (res && res.engine_result === "tesSUCCESS") {
toast.success("Transaction succeeded!");
return setIsSetHookDialogOpen(false);
}
toast.error(`Transaction failed! (${res?.engine_result_message})`);
};
return (
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}>
<DialogTrigger asChild>
<Button
ghost
size="xs"
uppercase
variant={"secondary"}
disabled={
account.isLoading ||
!snap.files.filter((file) => file.compiledWatContent).length
// Calcucate initial fee estimate when modal opens
useEffect(() => {
if (formInitialized && account) {
(async () => {
const formValues = getValues();
const tx = await prepareDeployHookTx(account, formValues);
if (!tx) {
return;
}
>
Set Hook
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogTitle>Deploy configuration</DialogTitle>
<DialogDescription as="div">
<Stack css={{ width: "100%", flex: 1 }}>
<Box css={{ width: "100%" }}>
<Label>Invoke on transactions</Label>
<Controller
name="Invoke"
control={control}
defaultValue={transactionOptions.filter(
(to) => to.label === "ttPAYMENT"
)}
render={({ field }) => (
<Select
{...field}
closeMenuOnSelect={false}
isMulti
menuPosition="fixed"
options={transactionOptions}
/>
)}
/>
</Box>
<Box css={{ width: "100%" }}>
<Label>Hook Namespace Seed</Label>
<Input
{...register("HookNamespace", { required: true })}
autoComplete={"off"}
defaultValue={
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
}
/>
{errors.HookNamespace?.type === "required" && (
<Box css={{ display: "inline", color: "$red11" }}>
Namespace is required
</Box>
)}
<Box css={{ mt: "$3" }}>
<Label>Hook Namespace (sha256)</Label>
<Input readOnly value={hashedNamespace} />
const res = await estimateFee(tx, account);
if (res && res.base_fee) {
setValue("Fee", res.base_fee);
}
})();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formInitialized]);
if (!account) {
return null;
}
const onSubmit: SubmitHandler<SetHookData> = async (data) => {
const currAccount = state.accounts.find(
(acc) => acc.address === account.address
);
if (currAccount) currAccount.isLoading = true;
const res = await deployHook(account, data);
if (currAccount) currAccount.isLoading = false;
if (res && res.engine_result === "tesSUCCESS") {
toast.success("Transaction succeeded!");
return setIsSetHookDialogOpen(false);
}
toast.error(`Transaction failed! (${res?.engine_result_message})`);
};
return (
<Dialog open={isSetHookDialogOpen} onOpenChange={setIsSetHookDialogOpen}>
<DialogTrigger asChild>
<Button
ghost
size="xs"
uppercase
variant={"secondary"}
disabled={
account.isLoading ||
!snap.files.filter((file) => file.compiledWatContent).length
}
>
Set Hook
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogTitle>Deploy configuration</DialogTitle>
<DialogDescription as="div">
<Stack css={{ width: "100%", flex: 1 }}>
<Box css={{ width: "100%" }}>
<Label>Invoke on transactions</Label>
<Controller
name="Invoke"
control={control}
defaultValue={transactionOptions.filter(
(to) => to.label === "ttPAYMENT"
)}
render={({ field }) => (
<Select
{...field}
closeMenuOnSelect={false}
isMulti
menuPosition="fixed"
options={transactionOptions}
/>
)}
/>
</Box>
</Box>
<Box css={{ width: "100%" }}>
<Label style={{ marginBottom: "10px", display: "block" }}>
Hook parameters
</Label>
<Stack>
{fields.map((field, index) => (
<Stack key={field.id}>
<Input
// important to include key with field's id
placeholder="Parameter name"
{...register(
`HookParameters.${index}.HookParameter.HookParameterName`
)}
/>
<Input
placeholder="Value (hex-quoted)"
{...register(
`HookParameters.${index}.HookParameter.HookParameterValue`
)}
/>
<Button onClick={() => remove(index)} variant="destroy">
<Trash weight="regular" size="16px" />
</Button>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
append({
HookParameter: {
HookParameterName: "",
HookParameterValue: "",
},
})
<Box css={{ width: "100%" }}>
<Label>Hook Namespace Seed</Label>
<Input
{...register("HookNamespace", { required: true })}
autoComplete={"off"}
defaultValue={
snap.files?.[snap.activeWat]?.name?.split(".")?.[0] || ""
}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Stack>
</Box>
{/* <Box css={{ width: "100%" }}>
/>
{errors.HookNamespace?.type === "required" && (
<Box css={{ display: "inline", color: "$red11" }}>
Namespace is required
</Box>
)}
<Box css={{ mt: "$3" }}>
<Label>Hook Namespace (sha256)</Label>
<Input readOnly value={hashedNamespace} />
</Box>
</Box>
<Box css={{ width: "100%" }}>
<Label style={{ marginBottom: "10px", display: "block" }}>
Hook parameters
</Label>
<Stack>
{fields.map((field, index) => (
<Stack key={field.id}>
<Input
// important to include key with field's id
placeholder="Parameter name"
{...register(
`HookParameters.${index}.HookParameter.HookParameterName`
)}
/>
<Input
placeholder="Value (hex-quoted)"
{...register(
`HookParameters.${index}.HookParameter.HookParameterValue`
)}
/>
<Button onClick={() => remove(index)} variant="destroy">
<Trash weight="regular" size="16px" />
</Button>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
append({
HookParameter: {
HookParameterName: "",
HookParameterValue: "",
},
})
}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Stack>
</Box>
<Box css={{ width: "100%", position: "relative" }}>
<Label>Fee</Label>
<Box css={{ display: "flex", alignItems: "center" }}>
<Input
type="number"
{...register("Fee", { required: true })}
autoComplete={"off"}
defaultValue={10000}
css={{
"-moz-appearance": "textfield",
"&::-webkit-outer-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
"&::-webkit-inner-spin-button ": {
"-webkit-appearance": "none",
margin: 0,
},
}}
/>
<Button
size="xs"
variant="primary"
outline
isLoading={estimateLoading}
css={{
position: "absolute",
right: "$2",
fontSize: "$xs",
cursor: "pointer",
alignContent: "center",
display: "flex",
}}
onClick={async (e) => {
e.preventDefault();
setEstimateLoading(true);
const formValues = getValues();
try {
const tx = await prepareDeployHookTx(
account,
formValues
);
if (tx) {
const res = await estimateFee(tx, account);
if (res && res.base_fee) {
setValue("Fee", res.base_fee);
}
}
} catch (err) {}
setEstimateLoading(false);
}}
>
Suggest
</Button>
</Box>
{errors.Fee?.type === "required" && (
<Box css={{ display: "inline", color: "$red11" }}>
Fee is required
</Box>
)}
</Box>
{/* <Box css={{ width: "100%" }}>
<label style={{ marginBottom: "10px", display: "block" }}>
Hook Grants
</label>
@@ -269,38 +360,41 @@ export const SetHookDialog: React.FC<{ account: IAccount }> = ({ account }) => {
</Button>
</Stack>
</Box> */}
</Stack>
</DialogDescription>
</Stack>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
{/* <DialogClose asChild> */}
<Button
variant="primary"
type="submit"
isLoading={account.isLoading}
<Flex
css={{
marginTop: 25,
justifyContent: "flex-end",
gap: "$3",
}}
>
Set Hook
</Button>
{/* </DialogClose> */}
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</form>
</DialogContent>
</Dialog>
);
};
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
{/* <DialogClose asChild> */}
<Button
variant="primary"
type="submit"
isLoading={account.isLoading}
>
Set Hook
</Button>
{/* </DialogClose> */}
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</form>
</DialogContent>
</Dialog>
);
}
);
SetHookDialog.displayName = "SetHookDialog";
export default SetHookDialog;

12057
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,10 +62,10 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
<meta property="og:url" content={`${origin}${router.asPath}`} />
<title>XRPL Hooks Builder</title>
<meta property="og:title" content="XRPL Hooks Editor" />
<meta name="twitter:title" content="XRPL Hooks Editor" />
<meta property="og:title" content="XRPL Hooks Builder" />
<meta name="twitter:title" content="XRPL Hooks Builder" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@xrpllabs" />
<meta name="twitter:site" content="@XRPLF" />
<meta
name="description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
@@ -101,7 +101,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161618" />
<meta name="application-name" content="XRPL Hooks Editor" />
<meta name="application-name" content="XRPL Hooks Builder" />
<meta name="msapplication-TileColor" content="#c10ad0" />
<meta
name="theme-color"

View File

@@ -6,13 +6,14 @@ import calculateHookOn, { TTS } from "../../utils/hookOnCalculator";
import { SetHookData } from "../../components/SetHookDialog";
import { Link } from "../../components";
import { ref } from "valtio";
import estimateFee from "../../utils/estimateFee";
export const sha256 = async (string: string) => {
const utf8 = new TextEncoder().encode(string);
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map(bytes => bytes.toString(16).padStart(2, "0"))
.map((bytes) => bytes.toString(16).padStart(2, "0"))
.join("");
return hashHex;
};
@@ -49,11 +50,7 @@ function arrayBufferToHex(arrayBuffer?: ArrayBuffer | null) {
return result;
}
/* deployHook function turns the wasm binary into
* hex string, signs the transaction and deploys it to
* Hooks testnet.
*/
export const deployHook = async (
export const prepareDeployHookTx = async (
account: IAccount & { name?: string },
data: SetHookData
) => {
@@ -72,12 +69,12 @@ export const deployHook = async (
return;
}
const HookNamespace = (await sha256(data.HookNamespace)).toUpperCase();
const hookOnValues: (keyof TTS)[] = data.Invoke.map(tt => tt.value);
const hookOnValues: (keyof TTS)[] = data.Invoke.map((tt) => tt.value);
const { HookParameters } = data;
const filteredHookParameters = HookParameters.filter(
hp =>
(hp) =>
hp.HookParameter.HookParameterName && hp.HookParameter.HookParameterValue
)?.map(aa => ({
)?.map((aa) => ({
HookParameter: {
HookParameterName: toHex(aa.HookParameter.HookParameterName || ""),
HookParameterValue: aa.HookParameter.HookParameterValue || "",
@@ -92,13 +89,12 @@ export const deployHook = async (
// }
// }
// });
if (typeof window !== "undefined") {
const tx = {
Account: account.address,
TransactionType: "SetHook",
Sequence: account.sequence,
Fee: "100000",
Fee: data.Fee,
Hooks: [
{
Hook: {
@@ -117,18 +113,39 @@ export const deployHook = async (
},
],
};
return tx;
}
};
/* deployHook function turns the wasm binary into
* hex string, signs the transaction and deploys it to
* Hooks testnet.
*/
export const deployHook = async (
account: IAccount & { name?: string },
data: SetHookData
) => {
if (typeof window !== "undefined") {
const tx = await prepareDeployHookTx(account, data);
if (!tx) {
return;
}
if (!state.client) {
return;
}
const keypair = derive.familySeed(account.secret);
const { signedTransaction } = sign(tx, keypair);
const currentAccount = state.accounts.find(
acc => acc.address === account.address
(acc) => acc.address === account.address
);
if (currentAccount) {
currentAccount.isLoading = true;
}
let submitRes;
try {
submitRes = await state.client.send({
submitRes = await state.client?.send({
command: "submit",
tx_blob: signedTransaction,
});
@@ -183,7 +200,7 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
return;
}
const currentAccount = state.accounts.find(
acc => acc.address === account.address
(acc) => acc.address === account.address
);
if (currentAccount?.isLoading || !currentAccount?.hooks.length) {
return;
@@ -205,6 +222,14 @@ export const deleteHook = async (account: IAccount & { name?: string }) => {
};
const keypair = derive.familySeed(account.secret);
try {
// Update tx Fee value with network estimation
const res = await estimateFee(tx, account);
tx["Fee"] = res?.base_fee ? res?.base_fee : "1000";
} catch (err) {
// use default value what you defined earlier
console.log(err);
}
const { signedTransaction } = sign(tx, keypair);
if (currentAccount) {

View File

@@ -24,10 +24,7 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
Fee, // TODO auto-fillable
...opts
};
const currAcc = state.accounts.find(acc => acc.address === account.address);
if (currAcc) {
currAcc.sequence = account.sequence + 1;
}
const { logPrefix = '' } = options || {}
try {
const signedAccount = derive.familySeed(account.secret);
@@ -47,6 +44,10 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
message: `${logPrefix}[${response.error || response.engine_result}] ${response.error_exception || response.engine_result_message}`,
});
}
const currAcc = state.accounts.find(acc => acc.address === account.address);
if (currAcc && response.account_sequence_next) {
currAcc.sequence = response.account_sequence_next;
}
} catch (err) {
console.error(err);
state.transactionLogs.push({

View File

@@ -35,6 +35,10 @@ export interface IAccount {
hooks: string[];
isLoading: boolean;
version?: string;
error?: {
message: string;
code: string;
} | null;
}
export interface ILog {
@@ -109,7 +113,7 @@ let initialState: IState = {
accounts: [],
compileOptions: {
optimizationLevel: '-O0',
strip: false
strip: true
}
};

25
utils/estimateFee.ts Normal file
View File

@@ -0,0 +1,25 @@
import { derive, sign } from "xrpl-accountlib"
import state, { IAccount } from "../state"
const estimateFee = async (tx: Record<string, unknown>, account: IAccount): Promise<null | { base_fee: string, median_fee: string; minimum_fee: string; open_ledger_fee: string; }> => {
const copyTx = JSON.parse(JSON.stringify(tx))
delete copyTx['SigningPubKey']
if (!copyTx.Fee) {
copyTx.Fee = '1000'
}
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(copyTx, keypair);
try {
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
if (res && res.drops) {
return res.drops;
}
return null
} catch (err) {
console.log(err)
return null
}
}
export default estimateFee