Fee hints in transactions.
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
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 state from "../../state";
 | 
			
		||||
import {
 | 
			
		||||
@@ -14,6 +14,7 @@ import Button from "../Button";
 | 
			
		||||
import Flex from "../Flex";
 | 
			
		||||
import { TxJson } from "./json";
 | 
			
		||||
import { TxUI } from "./ui";
 | 
			
		||||
import estimateFee from "../../utils/estimateFee";
 | 
			
		||||
 | 
			
		||||
export interface TransactionProps {
 | 
			
		||||
  header: string;
 | 
			
		||||
@@ -76,13 +77,18 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
    } else {
 | 
			
		||||
      setState({ txIsDisabled: false });
 | 
			
		||||
    }
 | 
			
		||||
  }, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading]);
 | 
			
		||||
  }, [
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    setState,
 | 
			
		||||
    txIsLoading,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const submitTest = useCallback(async () => {
 | 
			
		||||
    let st: TransactionState | undefined;
 | 
			
		||||
    if (viewType === "json") {
 | 
			
		||||
      // save the editor state first
 | 
			
		||||
      const pst = prepareState(editorValue || '', txState);
 | 
			
		||||
      const pst = prepareState(editorValue || "", txState);
 | 
			
		||||
      if (!pst) return;
 | 
			
		||||
 | 
			
		||||
      st = setState(pst);
 | 
			
		||||
@@ -102,7 +108,7 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
      const options = prepareOptions(st);
 | 
			
		||||
 | 
			
		||||
      if (options.Destination === null) {
 | 
			
		||||
        throw Error("Destination account cannot be null")
 | 
			
		||||
        throw Error("Destination account cannot be null");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await sendTransaction(account, options, { logPrefix });
 | 
			
		||||
@@ -116,7 +122,17 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    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(() => {
 | 
			
		||||
    modifyTransaction(header, { viewType }, { replaceState: true });
 | 
			
		||||
@@ -129,6 +145,25 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
    [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 (
 | 
			
		||||
    <Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
 | 
			
		||||
      {viewType === "json" ? (
 | 
			
		||||
@@ -137,9 +172,10 @@ const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
          header={header}
 | 
			
		||||
          state={txState}
 | 
			
		||||
          setState={setState}
 | 
			
		||||
          estimatedFee={estimatedFee}
 | 
			
		||||
        />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <TxUI state={txState} setState={setState} />
 | 
			
		||||
        <TxUI state={txState} setState={setState} estimatedFee={estimatedFee} />
 | 
			
		||||
      )}
 | 
			
		||||
      <Flex
 | 
			
		||||
        row
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ interface JsonProps {
 | 
			
		||||
  header?: string;
 | 
			
		||||
  setState: (pTx?: Partial<TransactionState> | undefined) => void;
 | 
			
		||||
  state: TransactionState;
 | 
			
		||||
  estimatedFee?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TxJson: FC<JsonProps> = ({
 | 
			
		||||
@@ -36,6 +37,7 @@ export const TxJson: FC<JsonProps> = ({
 | 
			
		||||
  state: txState,
 | 
			
		||||
  header,
 | 
			
		||||
  setState,
 | 
			
		||||
  estimatedFee
 | 
			
		||||
}) => {
 | 
			
		||||
  const { editorSettings, accounts } = useSnapshot(state);
 | 
			
		||||
  const { editorValue = value, selectedTransaction } = txState;
 | 
			
		||||
@@ -98,7 +100,7 @@ export const TxJson: FC<JsonProps> = ({
 | 
			
		||||
        {}
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log({  estimatedFee})
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        uri: "file:///main-schema.json", // id of the first schema
 | 
			
		||||
@@ -130,6 +132,9 @@ export const TxJson: FC<JsonProps> = ({
 | 
			
		||||
            Amount: {
 | 
			
		||||
              $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),
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        uri: "file:///fee-schema.json",
 | 
			
		||||
        schema: {
 | 
			
		||||
          type: "string",
 | 
			
		||||
          title: "Fee type",
 | 
			
		||||
          const: estimatedFee,
 | 
			
		||||
          description: "Above mentioned value is recommended base fee",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        ...amountSchema,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, [accounts, header, selectedTransaction?.value]);
 | 
			
		||||
  }, [accounts, estimatedFee, header, selectedTransaction?.value]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!monaco) return;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,19 @@ import {
 | 
			
		||||
import { useSnapshot } from "valtio";
 | 
			
		||||
import state from "../../state";
 | 
			
		||||
import { streamState } from "../DebugStream";
 | 
			
		||||
import { Label } from "..";
 | 
			
		||||
 | 
			
		||||
interface UIProps {
 | 
			
		||||
  setState: (pTx?: Partial<TransactionState> | undefined) => void;
 | 
			
		||||
  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 {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
@@ -87,7 +93,7 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
 | 
			
		||||
        height: "calc(100% - 45px)",
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
 | 
			
		||||
      <Flex column fluid css={{ height: "100%", overflowY: "auto", pr: "$1" }}>
 | 
			
		||||
        <Flex
 | 
			
		||||
          row
 | 
			
		||||
          fluid
 | 
			
		||||
@@ -174,36 +180,45 @@ export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          let isXrp = typeof _value === "object" && _value.$type === "xrp";
 | 
			
		||||
 | 
			
		||||
          const hint =
 | 
			
		||||
            field === "Fee" && estimatedFee
 | 
			
		||||
              ? `Suggested Fee: ${estimatedFee}`
 | 
			
		||||
              : undefined;
 | 
			
		||||
          return (
 | 
			
		||||
            <Flex
 | 
			
		||||
              key={field}
 | 
			
		||||
              row
 | 
			
		||||
              fluid
 | 
			
		||||
              css={{
 | 
			
		||||
                justifyContent: "flex-end",
 | 
			
		||||
                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,
 | 
			
		||||
                    },
 | 
			
		||||
                  });
 | 
			
		||||
            <Flex column key={field} css={{ mb: "$2", pr: "1px" }}>
 | 
			
		||||
              <Flex
 | 
			
		||||
                row
 | 
			
		||||
                fluid
 | 
			
		||||
                css={{
 | 
			
		||||
                  justifyContent: "flex-end",
 | 
			
		||||
                  alignItems: "center",
 | 
			
		||||
                }}
 | 
			
		||||
                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>
 | 
			
		||||
          );
 | 
			
		||||
        })}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,8 @@ export const sendTransaction = async (account: IAccount, txOptions: TransactionO
 | 
			
		||||
    const { Fee = "1000", ...opts } = txOptions
 | 
			
		||||
    const tx: TransactionOptions = {
 | 
			
		||||
        Account: account.address,
 | 
			
		||||
        Sequence: account.sequence, // TODO auto-fillable
 | 
			
		||||
        Fee,  // TODO auto-fillable
 | 
			
		||||
        Sequence: account.sequence,
 | 
			
		||||
        Fee,  // TODO auto-fillable default
 | 
			
		||||
        ...opts
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +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 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 {
 | 
			
		||||
    const keypair = derive.familySeed(account.secret)
 | 
			
		||||
    const { signedTransaction } = sign(copyTx, keypair);
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
    if (!opts.silent)
 | 
			
		||||
      console.log(err)
 | 
			
		||||
    return null
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user