Skip to content

Commit

Permalink
Merge pull request #1387 from emeraldpay/feature/setup-gas
Browse files Browse the repository at this point in the history
  • Loading branch information
splix authored Dec 18, 2024
2 parents e842c0a + 2f8c462 commit b508cc9
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 86 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/BackendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface BackendApi {
describeAddress(blockchain: BlockchainCode, address: string): Promise<AddressApi.DescribeResponse>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
estimateFee(blockchain: BlockchainCode, blocks: number, mode: EstimationMode): Promise<any>;
estimateTxCost(blockchain: BlockchainCode, tx: EthereumBasicTransaction): Promise<number>;
estimateGasLimit(blockchain: BlockchainCode, tx: EthereumBasicTransaction): Promise<number>;
getBalance(address: string, asset: AnyAsset, includeUtxo?: boolean): Promise<AddressBalance[]>;
getBtcTx(blockchain: BlockchainCode, hash: string): Promise<BitcoinRawTransaction | null>;
getEthReceipt(blockchain: BlockchainCode, hash: string): Promise<EthereumRawReceipt | null>;
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/IpcCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export enum IpcCommands {
// Backend API
DESCRIBE_ADDRESS = 'describe-address',
ESTIMATE_FEE = 'estimate-fee',

// estimate the gas limit for the transaction
ESTIMATE_TX = 'estimate-tx',

GET_BALANCE = 'get-balance',
GET_BTC_TX = 'get-btc-tx',
GET_ETH_RECEIPT = 'get-eth-receipt',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/transaction/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export const DEFAULT_GAS_LIMIT_ERC20 = 60000 as const;
export interface EthereumBasicTransaction {
data?: string;
from?: string;
gas?: number | string;
gas?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
to: string;
to?: string;
value?: string;
}

Expand Down
92 changes: 90 additions & 2 deletions packages/react-app/src/common/EthTxSettings/EthTxSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { CreateAmount, Unit } from '@emeraldpay/bigamount';
import { WeiAny } from '@emeraldpay/bigamount-crypto';
import { FormAccordion, FormLabel, FormRow } from '@emeraldwallet/ui';
import { Box, FormControlLabel, FormHelperText, Slider, Switch, createStyles, makeStyles } from '@material-ui/core';
import {Button, FormAccordion, FormLabel, FormRow, Input} from '@emeraldwallet/ui';
import {
Box,
FormControlLabel,
FormHelperText,
Slider,
Switch,
createStyles,
makeStyles,
TextField
} from '@material-ui/core';
import * as React from 'react';
import {useState} from "react";
import {workflow} from "@emeraldwallet/core";
import {NumberField} from "../NumberField";

const useStyles = makeStyles(
createStyles({
Expand Down Expand Up @@ -38,6 +50,9 @@ const useStyles = makeStyles(
inputField: {
flexGrow: 5,
},
gasLimitEditor: {
width: 240,
}
}),
);

Expand All @@ -54,9 +69,70 @@ interface OwnProps {
stdPriorityGasPrice: WeiAny;
lowPriorityGasPrice: WeiAny;
highPriorityGasPrice: WeiAny;
estimatedGasLimit: number;
onUse1559Change(value: boolean): void;
onMaxGasPriceChange(value: WeiAny): void;
onPriorityGasPriceChange(value: WeiAny): void;
onGasLimitChange(value: number): void;
}

type GasLimitFieldProps = {
gasLimit: number;
onOverride: (value?: number) => void;
}
const GasLimitField = ({gasLimit, onOverride}: GasLimitFieldProps) => {
const [useDefault, setUseDefault] = useState(true);
const [value, setValue] = useState(gasLimit);
const [errorText, setErrorText] = useState<string | undefined>();

const styles = useStyles();

const onSwitch = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
setUseDefault(checked);
onOverride(!checked ? value : undefined);
}

const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = parseInt(event.target.value);
setValue(newValue);
if (isNaN(newValue)) {
setErrorText('Invalid number');
} else if (newValue < 21_000) {
setErrorText('Minimum is 21,000');
} else if (newValue > 30_000_000) {
setErrorText('Maximum is 30,000,000');
} else {
setErrorText(undefined);
onOverride(newValue);
}
}

const editor = <TextField
className={styles.gasLimitEditor}
type="number"
helperText={errorText != undefined ? errorText : `Estimated value: ${gasLimit}`}
error={errorText != undefined}
maxRows={1}
multiline={false}
placeholder={`${gasLimit}`}
value={value}
onChange={handleValueChange}
/>

return (
<FormRow>
<FormLabel>Gas</FormLabel>
<Box className={styles.inputField}>
<Box>
<FormControlLabel
control={<Switch checked={useDefault} color="primary" onChange={onSwitch} />}
label={useDefault ? `Auto (${gasLimit})` : 'Manual'}
/>
{!useDefault && editor}
</Box>
</Box>
</FormRow>
)
}

const EthTxSettings: React.FC<OwnProps> = ({
Expand All @@ -72,9 +148,11 @@ const EthTxSettings: React.FC<OwnProps> = ({
stdPriorityGasPrice,
lowPriorityGasPrice,
highPriorityGasPrice,
estimatedGasLimit,
onUse1559Change,
onMaxGasPriceChange,
onPriorityGasPriceChange,
onGasLimitChange,
}) => {
const styles = useStyles();

Expand All @@ -93,6 +171,7 @@ const EthTxSettings: React.FC<OwnProps> = ({

const [currentUseStdMaxGasPrice, setCurrentUseStdMaxGasPrice] = React.useState(true);
const [currentUseStdPriorityGasPrice, setCurrentUseStdPriorityGasPrice] = React.useState(true);
const [overriddenGasLimit, setOverriddenGasLimit] = React.useState<number | undefined>(undefined);

const toWei = (decimal: number): WeiAny => WeiAny.createFor(decimal, stdMaxGasPrice.units, factory, gasPriceUnit);

Expand Down Expand Up @@ -128,6 +207,11 @@ const EthTxSettings: React.FC<OwnProps> = ({
onPriorityGasPriceChange(toWei(gasPriceDecimal));
};

const handleGasLimitOverride = (value?: number) => {
setOverriddenGasLimit(estimatedGasLimit);
onGasLimitChange(estimatedGasLimit);
}

const maxGasPriceByUnit = maxGasPrice.getNumberByUnit(gasPriceUnit).toFixed(2);
const priorityGasPriceByUnit = priorityGasPrice.getNumberByUnit(gasPriceUnit).toFixed(2);

Expand All @@ -136,6 +220,8 @@ const EthTxSettings: React.FC<OwnProps> = ({
const showMaxRange = lowMaxGasPrice.isPositive() && highMaxGasPrice.isPositive();
const showPriorityRange = lowPriorityGasPrice.isPositive() && highPriorityGasPrice.isPositive();

const currentGasLimit = overriddenGasLimit ?? estimatedGasLimit;

return (
<FormAccordion
title={
Expand All @@ -144,6 +230,7 @@ const EthTxSettings: React.FC<OwnProps> = ({
{showEip1559 ? 'EIP-1559' : 'Basic Type'} / {maxGasPriceByUnit} {gasPriceUnit.toString()}
{showEip1559 ? ' Max Gas Price' : ' Gas Price'}
{showEip1559 ? ` / ${priorityGasPriceByUnit} ${gasPriceUnit.toString()} Priority Gas Price` : null}
/ {currentGasLimit} gas
</FormRow>
}
>
Expand Down Expand Up @@ -250,6 +337,7 @@ const EthTxSettings: React.FC<OwnProps> = ({
</Box>
</FormRow>
)}
<GasLimitField gasLimit={estimatedGasLimit} onOverride={handleGasLimitOverride} />
</FormAccordion>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,11 @@ export default connect<StateProps, DispatchProps, OwnProps, IState>(
(dispatch: any, { initialAllowance, storedTx }) => ({
prepareTransaction({ action, entries, entry }) {
dispatch(txStash.actions.prepareTransaction({ action, entries, entry, initialAllowance, storedTx }));
dispatch(txStash.actions.estimateGasLimit());
},
setAsset(asset) {
dispatch(txStash.actions.setAsset(asset));
dispatch(txStash.actions.estimateGasLimit());
},
setEntry(entry, ownerAddress) {
dispatch(txStash.actions.setEntry(entry, ownerAddress));
Expand All @@ -306,6 +308,7 @@ export default connect<StateProps, DispatchProps, OwnProps, IState>(
},
setTransaction(tx) {
dispatch(txStash.actions.setTransaction(tx));
dispatch(txStash.actions.estimateGasLimit());
},
}),
)(SetupTransaction);
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export const EthereumFee: React.FC<OwnProps> = ({ createTx, feeRange, initializi
setTransaction(createTx.dump());
};

const handleGasLimitChange = (gasLimit: number): void => {
createTx.gas = gasLimit;

setTransaction(createTx.dump());
}

const { eip1559: supportEip1559 = false } = Blockchains[createTx.blockchain].params;

const isEip1559 = createTx.type === EthereumTransactionType.EIP1559;
Expand All @@ -57,9 +63,11 @@ export const EthereumFee: React.FC<OwnProps> = ({ createTx, feeRange, initializi
stdPriorityGasPrice={feeRange.stdPriorityGasPrice}
lowPriorityGasPrice={feeRange.lowPriorityGasPrice}
highPriorityGasPrice={feeRange.highPriorityGasPrice}
estimatedGasLimit={createTx.gas}
onUse1559Change={handleUseEip1559Change}
onMaxGasPriceChange={handleMaxGasPriceChange}
onPriorityGasPriceChange={handlePriorityGasPriceChange}
onGasLimitChange={handleGasLimitChange}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { workflow } from '@emeraldwallet/core';
import { FormLabel, FormRow } from '@emeraldwallet/ui';
import {workflow} from '@emeraldwallet/core';
import {FormLabel, FormRow} from '@emeraldwallet/ui';
import * as React from 'react';
import { Amount } from '../components';
import { Data, DataProvider, Handler } from '../types';
import { CommonDisplay } from './common';
import {Amount} from '../components';
import {Data, DataProvider, Handler} from '../types';
import {CommonDisplay} from './common';
import {Tooltip} from "@material-ui/core";
import {InfoOutlined} from "@material-ui/icons";

type EthereumData = Data<workflow.AnyEthereumCreateTx>;

const styles = {
row: {
alignItems: "baseline",
},
info: {
marginLeft: "10px",
height: "16px",
}
}

export abstract class EthereumCommonDisplay extends CommonDisplay {
readonly data: EthereumData;

Expand All @@ -19,15 +31,29 @@ export abstract class EthereumCommonDisplay extends CommonDisplay {
abstract render(): React.ReactElement;

renderFees(): React.ReactElement {
const { createTx } = this.data;
const { getFiatAmount } = this.dataProvider;
const {createTx} = this.data;
const {getFiatAmount} = this.dataProvider;

const fees = createTx.getFees();
const gasLimit = createTx.gas;

// for a standard ether transfer we know that it would cost exactly 21_000 gas
// but if the value is set to something different it means it a contract call, and it this case it actually
// defines just the upper limit.
let gasLimitIsDefined = false;
if (gasLimit == 21_000) {
gasLimitIsDefined = true;
}

return (
<FormRow>
<FormLabel top={2}>Fee</FormLabel>
<Amount amount={fees} fiatAmount={getFiatAmount(fees)} />
<FormRow style={styles.row}>
<FormLabel top={2}>{gasLimitIsDefined ? "Fee" : "Maximum Fee" }</FormLabel>
<Amount amount={fees} fiatAmount={getFiatAmount(fees)}/>
<Tooltip title={
gasLimitIsDefined ? "Pay for Gas " + gasLimit + " of Gas" : "Pay for Gas Limited to " + gasLimit + " of Gas"
}>
<InfoOutlined color={"secondary"} style={styles.info}/>
</Tooltip>
</FormRow>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ let actions = [
];

export default {
title: 'Create Transaction',
title: 'Create Transaction - Setup',
decorators: [
providerForStore(api, backend, actions),
],
Expand Down
Loading

0 comments on commit b508cc9

Please sign in to comment.