Merge pull request #196 from XRPLF/feat/fee-hint
Fee hints in transactions.
This commit is contained in:
@@ -14,6 +14,8 @@ import Button from "../Button";
|
|||||||
import Flex from "../Flex";
|
import Flex from "../Flex";
|
||||||
import { TxJson } from "./json";
|
import { TxJson } from "./json";
|
||||||
import { TxUI } from "./ui";
|
import { TxUI } from "./ui";
|
||||||
|
import { default as _estimateFee } from "../../utils/estimateFee";
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface TransactionProps {
|
export interface TransactionProps {
|
||||||
header: string;
|
header: string;
|
||||||
@@ -76,13 +78,19 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setState({ txIsDisabled: false });
|
setState({ txIsDisabled: false });
|
||||||
}
|
}
|
||||||
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading]);
|
}, [
|
||||||
|
selectedAccount?.value,
|
||||||
|
selectedTransaction?.value,
|
||||||
|
setState,
|
||||||
|
txIsLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
const submitTest = useCallback(async () => {
|
const submitTest = useCallback(async () => {
|
||||||
let st: TransactionState | undefined;
|
let st: TransactionState | undefined;
|
||||||
|
const tt = txState.selectedTransaction?.value;
|
||||||
if (viewType === "json") {
|
if (viewType === "json") {
|
||||||
// save the editor state first
|
// save the editor state first
|
||||||
const pst = prepareState(editorValue || '', txState);
|
const pst = prepareState(editorValue || "", tt);
|
||||||
if (!pst) return;
|
if (!pst) return;
|
||||||
|
|
||||||
st = setState(pst);
|
st = setState(pst);
|
||||||
@@ -102,7 +110,7 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
const options = prepareOptions(st);
|
const options = prepareOptions(st);
|
||||||
|
|
||||||
if (options.Destination === null) {
|
if (options.Destination === null) {
|
||||||
throw Error("Destination account cannot be null")
|
throw Error("Destination account cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendTransaction(account, options, { logPrefix });
|
await sendTransaction(account, options, { logPrefix });
|
||||||
@@ -116,7 +124,17 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState({ txIsLoading: false });
|
setState({ txIsLoading: false });
|
||||||
}, [viewType, accounts, txIsDisabled, setState, header, editorValue, txState, selectedAccount?.value, prepareOptions]);
|
}, [
|
||||||
|
viewType,
|
||||||
|
accounts,
|
||||||
|
txIsDisabled,
|
||||||
|
setState,
|
||||||
|
header,
|
||||||
|
editorValue,
|
||||||
|
txState,
|
||||||
|
selectedAccount?.value,
|
||||||
|
prepareOptions,
|
||||||
|
]);
|
||||||
|
|
||||||
const resetState = useCallback(() => {
|
const resetState = useCallback(() => {
|
||||||
modifyTransaction(header, { viewType }, { replaceState: true });
|
modifyTransaction(header, { viewType }, { replaceState: true });
|
||||||
@@ -129,6 +147,31 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
[editorSavedValue, editorSettings.tabSize, prepareOptions]
|
[editorSavedValue, editorSettings.tabSize, prepareOptions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const estimateFee = useCallback(
|
||||||
|
async (st?: TransactionState, opts?: { silent?: boolean }) => {
|
||||||
|
const state = st || txState;
|
||||||
|
const ptx = prepareOptions(state);
|
||||||
|
const account = accounts.find(
|
||||||
|
acc => acc.address === state.selectedAccount?.value
|
||||||
|
);
|
||||||
|
if (!account) {
|
||||||
|
if (!opts?.silent) {
|
||||||
|
toast.error("Please select account from the list.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
ptx.Account = account.address;
|
||||||
|
ptx.Sequence = account.sequence;
|
||||||
|
|
||||||
|
const res = await _estimateFee(ptx, account, opts);
|
||||||
|
const fee = res?.base_fee;
|
||||||
|
setState({ estimatedFee: fee });
|
||||||
|
return fee;
|
||||||
|
},
|
||||||
|
[accounts, prepareOptions, setState, txState]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
<Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
|
||||||
{viewType === "json" ? (
|
{viewType === "json" ? (
|
||||||
@@ -137,9 +180,10 @@ const Transaction: FC<TransactionProps> = ({
|
|||||||
header={header}
|
header={header}
|
||||||
state={txState}
|
state={txState}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
|
estimateFee={estimateFee}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TxUI state={txState} setState={setState} />
|
<TxUI state={txState} setState={setState} estimateFee={estimateFee} />
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ interface JsonProps {
|
|||||||
header?: string;
|
header?: string;
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
||||||
state: TransactionState;
|
state: TransactionState;
|
||||||
|
estimateFee?: () => Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxJson: FC<JsonProps> = ({
|
export const TxJson: FC<JsonProps> = ({
|
||||||
@@ -38,22 +39,37 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
setState,
|
setState,
|
||||||
}) => {
|
}) => {
|
||||||
const { editorSettings, accounts } = useSnapshot(state);
|
const { editorSettings, accounts } = useSnapshot(state);
|
||||||
const { editorValue = value, selectedTransaction } = txState;
|
const { editorValue = value, estimatedFee } = txState;
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [hasUnsaved, setHasUnsaved] = useState(false);
|
const [hasUnsaved, setHasUnsaved] = useState(false);
|
||||||
|
const [currTxType, setCurrTxType] = useState<string | undefined>(
|
||||||
|
txState.selectedTransaction?.value
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState({ editorValue: value });
|
setState({ editorValue: value });
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const parsed = parseJSON(editorValue);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const tt = parsed.TransactionType;
|
||||||
|
const tx = transactionsData.find(t => t.TransactionType === tt);
|
||||||
|
if (tx) setCurrTxType(tx.TransactionType);
|
||||||
|
else {
|
||||||
|
setCurrTxType(undefined);
|
||||||
|
}
|
||||||
|
}, [editorValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorValue === value) setHasUnsaved(false);
|
if (editorValue === value) setHasUnsaved(false);
|
||||||
else setHasUnsaved(true);
|
else setHasUnsaved(true);
|
||||||
}, [editorValue, value]);
|
}, [editorValue, value]);
|
||||||
|
|
||||||
const saveState = (value: string, txState: TransactionState) => {
|
const saveState = (value: string, transactionType?: string) => {
|
||||||
const tx = prepareState(value, txState);
|
const tx = prepareState(value, transactionType);
|
||||||
if (tx) setState(tx);
|
if (tx) setState(tx);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,7 +84,7 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
const onExit = (value: string) => {
|
const onExit = (value: string) => {
|
||||||
const options = parseJSON(value);
|
const options = parseJSON(value);
|
||||||
if (options) {
|
if (options) {
|
||||||
saveState(value, txState);
|
saveState(value, currTxType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showAlert("Error!", {
|
showAlert("Error!", {
|
||||||
@@ -82,9 +98,10 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
const path = `file:///${header}`;
|
const path = `file:///${header}`;
|
||||||
const monaco = useMonaco();
|
const monaco = useMonaco();
|
||||||
|
|
||||||
const getSchemas = useCallback((): any[] => {
|
const getSchemas = useCallback(async (): Promise<any[]> => {
|
||||||
const tt = selectedTransaction?.value;
|
const txObj = transactionsData.find(
|
||||||
const txObj = transactionsData.find(td => td.TransactionType === tt);
|
td => td.TransactionType === currTxType
|
||||||
|
);
|
||||||
|
|
||||||
let genericSchemaProps: any;
|
let genericSchemaProps: any;
|
||||||
if (txObj) {
|
if (txObj) {
|
||||||
@@ -98,7 +115,6 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
uri: "file:///main-schema.json", // id of the first schema
|
uri: "file:///main-schema.json", // id of the first schema
|
||||||
@@ -130,6 +146,9 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
Amount: {
|
Amount: {
|
||||||
$ref: "file:///amount-schema.json",
|
$ref: "file:///amount-schema.json",
|
||||||
},
|
},
|
||||||
|
Fee: {
|
||||||
|
$ref: "file:///fee-schema.json",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -141,17 +160,30 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
enum: accounts.map(acc => acc.address),
|
enum: accounts.map(acc => acc.address),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
uri: "file:///fee-schema.json",
|
||||||
|
schema: {
|
||||||
|
type: "string",
|
||||||
|
title: "Fee type",
|
||||||
|
const: estimatedFee,
|
||||||
|
description: estimatedFee
|
||||||
|
? "Above mentioned value is recommended base fee"
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...amountSchema,
|
...amountSchema,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [accounts, header, selectedTransaction?.value]);
|
}, [accounts, currTxType, estimatedFee, header]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!monaco) return;
|
if (!monaco) return;
|
||||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
getSchemas().then(schemas => {
|
||||||
validate: true,
|
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||||
schemas: getSchemas(),
|
validate: true,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, [getSchemas, monaco]);
|
}, [getSchemas, monaco]);
|
||||||
|
|
||||||
@@ -184,19 +216,13 @@ export const TxJson: FC<JsonProps> = ({
|
|||||||
// register onExit cb
|
// register onExit cb
|
||||||
const model = editor.getModel();
|
const model = editor.getModel();
|
||||||
model?.onWillDispose(() => onExit(model.getValue()));
|
model?.onWillDispose(() => onExit(model.getValue()));
|
||||||
|
|
||||||
// set json defaults
|
|
||||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
||||||
validate: true,
|
|
||||||
schemas: getSchemas(),
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
theme={theme === "dark" ? "dark" : "light"}
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
/>
|
/>
|
||||||
{hasUnsaved && (
|
{hasUnsaved && (
|
||||||
<Text muted small css={{ position: "absolute", bottom: 0, right: 0 }}>
|
<Text muted small css={{ position: "absolute", bottom: 0, right: 0 }}>
|
||||||
This file has unsaved changes.{" "}
|
This file has unsaved changes.{" "}
|
||||||
<Link onClick={() => saveState(editorValue, txState)}>save</Link>{" "}
|
<Link onClick={() => saveState(editorValue, currTxType)}>save</Link>{" "}
|
||||||
<Link onClick={discardChanges}>discard</Link>
|
<Link onClick={discardChanges}>discard</Link>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback, useState } from "react";
|
||||||
import Container from "../Container";
|
import Container from "../Container";
|
||||||
import Flex from "../Flex";
|
import Flex from "../Flex";
|
||||||
import Input from "../Input";
|
import Input from "../Input";
|
||||||
@@ -9,17 +9,26 @@ import {
|
|||||||
TransactionState,
|
TransactionState,
|
||||||
transactionsData,
|
transactionsData,
|
||||||
TxFields,
|
TxFields,
|
||||||
|
getTxFields,
|
||||||
} from "../../state/transactions";
|
} from "../../state/transactions";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
import state from "../../state";
|
import state from "../../state";
|
||||||
import { streamState } from "../DebugStream";
|
import { streamState } from "../DebugStream";
|
||||||
|
import { Button } from "..";
|
||||||
|
|
||||||
interface UIProps {
|
interface UIProps {
|
||||||
setState: (pTx?: Partial<TransactionState> | undefined) => void;
|
setState: (
|
||||||
|
pTx?: Partial<TransactionState> | undefined
|
||||||
|
) => TransactionState | undefined;
|
||||||
state: TransactionState;
|
state: TransactionState;
|
||||||
|
estimateFee?: (...arg: any) => Promise<string | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
export const TxUI: FC<UIProps> = ({
|
||||||
|
state: txState,
|
||||||
|
setState,
|
||||||
|
estimateFee,
|
||||||
|
}) => {
|
||||||
const { accounts } = useSnapshot(state);
|
const { accounts } = useSnapshot(state);
|
||||||
const {
|
const {
|
||||||
selectedAccount,
|
selectedAccount,
|
||||||
@@ -45,32 +54,54 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
}))
|
}))
|
||||||
.filter(acc => acc.value !== selectedAccount?.value);
|
.filter(acc => acc.value !== selectedAccount?.value);
|
||||||
|
|
||||||
const resetOptions = (tt: string) => {
|
const [feeLoading, setFeeLoading] = useState(false);
|
||||||
const txFields: TxFields | undefined = transactionsData.find(
|
|
||||||
tx => tx.TransactionType === tt
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!txFields) return setState({ txFields: {} });
|
const resetOptions = useCallback(
|
||||||
|
(tt: string) => {
|
||||||
const _txFields = Object.keys(txFields)
|
const fields = getTxFields(tt);
|
||||||
.filter(key => !["TransactionType", "Account", "Sequence"].includes(key))
|
if (!fields.Destination) setState({ selectedDestAccount: null });
|
||||||
.reduce<TxFields>(
|
return setState({ txFields: fields });
|
||||||
(tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf),
|
},
|
||||||
{}
|
[setState]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!_txFields.Destination) setState({ selectedDestAccount: null });
|
|
||||||
setState({ txFields: _txFields });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSetAccount = (acc: SelectOption) => {
|
const handleSetAccount = (acc: SelectOption) => {
|
||||||
setState({ selectedAccount: acc });
|
setState({ selectedAccount: acc });
|
||||||
streamState.selectedAccount = acc;
|
streamState.selectedAccount = acc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetField = useCallback(
|
||||||
|
(field: keyof TxFields, value: string, opFields?: TxFields) => {
|
||||||
|
const fields = opFields || txFields;
|
||||||
|
const obj = fields[field];
|
||||||
|
setState({
|
||||||
|
txFields: {
|
||||||
|
...fields,
|
||||||
|
[field]: typeof obj === "object" ? { ...obj, $value: value } : value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setState, txFields]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEstimateFee = useCallback(
|
||||||
|
async (state?: TransactionState, silent?: boolean) => {
|
||||||
|
setFeeLoading(true);
|
||||||
|
|
||||||
|
const fee = await estimateFee?.(state, { silent });
|
||||||
|
if (fee) handleSetField("Fee", fee, state?.txFields);
|
||||||
|
|
||||||
|
setFeeLoading(false);
|
||||||
|
},
|
||||||
|
[estimateFee, handleSetField]
|
||||||
|
);
|
||||||
|
|
||||||
const handleChangeTxType = (tt: SelectOption) => {
|
const handleChangeTxType = (tt: SelectOption) => {
|
||||||
setState({ selectedTransaction: tt });
|
setState({ selectedTransaction: tt });
|
||||||
resetOptions(tt.value);
|
|
||||||
|
const newState = resetOptions(tt.value);
|
||||||
|
|
||||||
|
handleEstimateFee(newState, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const specialFields = ["TransactionType", "Account", "Destination"];
|
const specialFields = ["TransactionType", "Account", "Destination"];
|
||||||
@@ -87,7 +118,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
height: "calc(100% - 45px)",
|
height: "calc(100% - 45px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
<Flex column fluid css={{ height: "100%", overflowY: "auto", pr: "$1" }}>
|
||||||
<Flex
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
@@ -174,36 +205,49 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isXrp = typeof _value === "object" && _value.$type === "xrp";
|
let isXrp = typeof _value === "object" && _value.$type === "xrp";
|
||||||
|
|
||||||
|
const isFee = field === "Fee";
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex column key={field} css={{ mb: "$2", pr: "1px" }}>
|
||||||
key={field}
|
<Flex
|
||||||
row
|
row
|
||||||
fluid
|
fluid
|
||||||
css={{
|
css={{
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
mb: "$3",
|
position: "relative",
|
||||||
pr: "1px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text muted css={{ mr: "$3" }}>
|
|
||||||
{field + (isXrp ? " (XRP)" : "")}:{" "}
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
value={value}
|
|
||||||
onChange={e => {
|
|
||||||
setState({
|
|
||||||
txFields: {
|
|
||||||
...txFields,
|
|
||||||
[field]:
|
|
||||||
typeof _value === "object"
|
|
||||||
? { ..._value, $value: e.target.value }
|
|
||||||
: e.target.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
css={{ width: "70%", flex: "inherit" }}
|
>
|
||||||
/>
|
<Text muted css={{ mr: "$3" }}>
|
||||||
|
{field + (isXrp ? " (XRP)" : "")}:{" "}
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={e => {
|
||||||
|
handleSetField(field, e.target.value);
|
||||||
|
}}
|
||||||
|
css={{ width: "70%", flex: "inherit" }}
|
||||||
|
/>
|
||||||
|
{isFee && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
outline
|
||||||
|
isLoading={feeLoading}
|
||||||
|
css={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "$2",
|
||||||
|
fontSize: "$xs",
|
||||||
|
cursor: "pointer",
|
||||||
|
alignContent: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
onClick={() => handleEstimateFee()}
|
||||||
|
>
|
||||||
|
Suggest
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
|
|||||||
const { Fee = "1000", ...opts } = txOptions
|
const { Fee = "1000", ...opts } = txOptions
|
||||||
const tx: TransactionOptions = {
|
const tx: TransactionOptions = {
|
||||||
Account: account.address,
|
Account: account.address,
|
||||||
Sequence: account.sequence, // TODO auto-fillable
|
Sequence: account.sequence,
|
||||||
Fee, // TODO auto-fillable
|
Fee, // TODO auto-fillable default
|
||||||
...opts
|
...opts
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export interface TransactionState {
|
|||||||
txFields: TxFields;
|
txFields: TxFields;
|
||||||
viewType: 'json' | 'ui',
|
viewType: 'json' | 'ui',
|
||||||
editorSavedValue: null | string,
|
editorSavedValue: null | string,
|
||||||
editorValue?: string
|
editorValue?: string,
|
||||||
|
estimatedFee?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ export const modifyTransaction = (
|
|||||||
Object.keys(partialTx).forEach(k => {
|
Object.keys(partialTx).forEach(k => {
|
||||||
// Typescript mess here, but is definetly safe!
|
// Typescript mess here, but is definetly safe!
|
||||||
const s = tx.state as any;
|
const s = tx.state as any;
|
||||||
const p = partialTx as any;
|
const p = partialTx as any; // ? Make copy
|
||||||
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
if (!deepEqual(s[k], p[k])) s[k] = p[k];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ export const prepareTransaction = (data: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// editor value to state
|
// editor value to state
|
||||||
export const prepareState = (value: string, txState: TransactionState) => {
|
export const prepareState = (value: string, transactionType?: string) => {
|
||||||
const options = parseJSON(value);
|
const options = parseJSON(value);
|
||||||
if (!options) {
|
if (!options) {
|
||||||
showAlert("Error!", {
|
showAlert("Error!", {
|
||||||
@@ -151,7 +152,7 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
|
|
||||||
const { Account, TransactionType, Destination, ...rest } = options;
|
const { Account, TransactionType, Destination, ...rest } = options;
|
||||||
let tx: Partial<TransactionState> = {};
|
let tx: Partial<TransactionState> = {};
|
||||||
const { txFields } = txState
|
const txFields = getTxFields(transactionType)
|
||||||
|
|
||||||
if (Account) {
|
if (Account) {
|
||||||
const acc = state.accounts.find(acc => acc.address === Account);
|
const acc = state.accounts.find(acc => acc.address === Account);
|
||||||
@@ -206,7 +207,7 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
if (isXrp) {
|
if (isXrp) {
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
$type: "xrp",
|
$type: "xrp",
|
||||||
$value: +value / 1000000, // TODO maybe use bigint?
|
$value: +value / 1000000, // ! maybe use bigint?
|
||||||
};
|
};
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
rest[field] = {
|
rest[field] = {
|
||||||
@@ -222,4 +223,24 @@ export const prepareState = (value: string, txState: TransactionState) => {
|
|||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTxFields = (tt?: string) => {
|
||||||
|
const txFields: TxFields | undefined = transactionsData.find(
|
||||||
|
tx => tx.TransactionType === tt
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!txFields) return {}
|
||||||
|
|
||||||
|
let _txFields = Object.keys(txFields)
|
||||||
|
.filter(
|
||||||
|
key => !["TransactionType", "Account", "Sequence"].includes(key)
|
||||||
|
)
|
||||||
|
.reduce<TxFields>(
|
||||||
|
(tf, key) => (
|
||||||
|
(tf[key as keyof TxFields] = (txFields as any)[key]), tf
|
||||||
|
),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return _txFields
|
||||||
|
}
|
||||||
|
|
||||||
export { transactionsData }
|
export { transactionsData }
|
||||||
|
|||||||
@@ -1,24 +1,29 @@
|
|||||||
|
import toast from 'react-hot-toast';
|
||||||
import { derive, sign } from "xrpl-accountlib"
|
import { derive, sign } from "xrpl-accountlib"
|
||||||
import state, { IAccount } from "../state"
|
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 estimateFee = async (tx: Record<string, unknown>, account: IAccount, opts: { silent?: boolean } = {}): 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 {
|
try {
|
||||||
|
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);
|
||||||
|
|
||||||
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
const res = await state.client?.send({ command: 'fee', tx_blob: signedTransaction })
|
||||||
if (res && res.drops) {
|
if (res && res.drops) {
|
||||||
return res.drops;
|
return res.drops;
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
if (!opts.silent) {
|
||||||
|
console.error(err)
|
||||||
|
toast.error("Cannot estimate fee.") // ? Some better msg
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user