Fee hints in transactions.

This commit is contained in:
muzam1l
2022-05-27 16:44:01 +05:30
parent 49dfd43220
commit a21350770e
5 changed files with 111 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
import { Play } from "phosphor-react"; import { Play } from "phosphor-react";
import { FC, useCallback, useEffect, useMemo } from "react"; import { FC, useCallback, useEffect, useMemo, useState } from "react";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import state from "../../state"; import state from "../../state";
import { import {
@@ -14,6 +14,7 @@ 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 estimateFee from "../../utils/estimateFee";
export interface TransactionProps { export interface TransactionProps {
header: string; header: string;
@@ -76,13 +77,18 @@ 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;
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 || "", txState);
if (!pst) return; if (!pst) return;
st = setState(pst); st = setState(pst);
@@ -102,7 +108,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 +122,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 +145,25 @@ const Transaction: FC<TransactionProps> = ({
[editorSavedValue, editorSettings.tabSize, prepareOptions] [editorSavedValue, editorSettings.tabSize, prepareOptions]
); );
const [estimatedFee, setEstimatedFee] = useState<string>();
useEffect(() => {
const ptx = prepareOptions(txState);
const account = accounts.find(
acc => acc.address === selectedAccount?.value
);
if (!account) return;
ptx.Account = account.address;
ptx.Sequence = account.sequence;
console.log("estimating fee...");
estimateFee(ptx, account, { silent: true })
.then(res => res?.base_fee)
.then(fee => {
setEstimatedFee(fee)
});
}, [accounts, prepareOptions, selectedAccount?.value, 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 +172,10 @@ const Transaction: FC<TransactionProps> = ({
header={header} header={header}
state={txState} state={txState}
setState={setState} setState={setState}
estimatedFee={estimatedFee}
/> />
) : ( ) : (
<TxUI state={txState} setState={setState} /> <TxUI state={txState} setState={setState} estimatedFee={estimatedFee} />
)} )}
<Flex <Flex
row row

View File

@@ -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;
estimatedFee?: string
} }
export const TxJson: FC<JsonProps> = ({ export const TxJson: FC<JsonProps> = ({
@@ -36,6 +37,7 @@ export const TxJson: FC<JsonProps> = ({
state: txState, state: txState,
header, header,
setState, setState,
estimatedFee
}) => { }) => {
const { editorSettings, accounts } = useSnapshot(state); const { editorSettings, accounts } = useSnapshot(state);
const { editorValue = value, selectedTransaction } = txState; const { editorValue = value, selectedTransaction } = txState;
@@ -98,7 +100,7 @@ export const TxJson: FC<JsonProps> = ({
{} {}
); );
} }
console.log({ estimatedFee})
return [ return [
{ {
uri: "file:///main-schema.json", // id of the first schema uri: "file:///main-schema.json", // id of the first schema
@@ -130,6 +132,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,11 +146,20 @@ 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: "Above mentioned value is recommended base fee",
},
},
{ {
...amountSchema, ...amountSchema,
}, },
]; ];
}, [accounts, header, selectedTransaction?.value]); }, [accounts, estimatedFee, header, selectedTransaction?.value]);
useEffect(() => { useEffect(() => {
if (!monaco) return; if (!monaco) return;

View File

@@ -13,13 +13,19 @@ import {
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 { Label } from "..";
interface UIProps { interface UIProps {
setState: (pTx?: Partial<TransactionState> | undefined) => void; setState: (pTx?: Partial<TransactionState> | undefined) => void;
state: TransactionState; state: TransactionState;
estimatedFee?: string;
} }
export const TxUI: FC<UIProps> = ({ state: txState, setState }) => { export const TxUI: FC<UIProps> = ({
state: txState,
setState,
estimatedFee,
}) => {
const { accounts } = useSnapshot(state); const { accounts } = useSnapshot(state);
const { const {
selectedAccount, selectedAccount,
@@ -87,7 +93,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 +180,45 @@ 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 hint =
field === "Fee" && estimatedFee
? `Suggested Fee: ${estimatedFee}`
: undefined;
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",
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 => {
setState({
txFields: {
...txFields,
[field]:
typeof _value === "object"
? { ..._value, $value: e.target.value }
: e.target.value,
},
});
}}
css={{ width: "70%", flex: "inherit" }}
/>
</Flex>
<Label
css={{ color: "$success", textAlign: "right", mt: "$1", mb: 0, fontSize: "$sm" }}
>
{hint}
</Label>
</Flex> </Flex>
); );
})} })}

View File

@@ -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
}; };

View File

@@ -1,24 +1,25 @@
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)) const copyTx = JSON.parse(JSON.stringify(tx))
delete copyTx['SigningPubKey'] delete copyTx['SigningPubKey']
if (!copyTx.Fee) { if (!copyTx.Fee) {
copyTx.Fee = '1000' copyTx.Fee = '1000'
} }
const keypair = derive.familySeed(account.secret)
const { signedTransaction } = sign(copyTx, keypair);
try { try {
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.log(err)
return null return null
} }
} }