bridge_ui: ethereum gas estimates

Change-Id: I68db89ad9fafba700b6ad22f3870b9ac35233588
This commit is contained in:
Chase Moran 2021-09-20 15:46:02 -04:00 committed by Evan Gray
parent c67410cd15
commit d0d5ea06cd
4 changed files with 145 additions and 10 deletions

View File

@ -1,7 +1,9 @@
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
import { incrementStep, setTargetChain } from "../../store/attestSlice";
import {
selectAttestIsTargetComplete,
@ -58,8 +60,13 @@ function Target() {
</TextField>
<KeyAndBalance chainId={targetChain} />
<Alert severity="info" className={classes.alert}>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to attest this token.
<Typography>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to attest this token.{" "}
</Typography>
{targetChain === CHAIN_ID_ETH && (
<EthGasEstimateSummary methodType="createWrapped" />
)}
</Alert>
<LowBalanceWarning chainId={targetChain} />
<ButtonWithLoader

View File

@ -1,5 +1,5 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
import { useCallback, useMemo } from "react";
import { ethers } from "ethers";
import { useDispatch, useSelector } from "react-redux";
@ -24,6 +24,7 @@ import KeyAndBalance from "../KeyAndBalance";
import StepDescription from "../StepDescription";
import LowBalanceWarning from "../LowBalanceWarning";
import { Alert } from "@material-ui/lab";
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
const useStyles = makeStyles((theme) => ({
transferField: {
@ -111,8 +112,13 @@ function Target() {
</>
) : null}
<Alert severity="info" className={classes.alert}>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your NFT.
<Typography>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your NFT.
</Typography>
{targetChain === CHAIN_ID_ETH && (
<EthGasEstimateSummary methodType="nft" />
)}
</Alert>
<LowBalanceWarning chainId={targetChain} />
<ButtonWithLoader

View File

@ -1,10 +1,11 @@
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import useIsWalletReady from "../../hooks/useIsWalletReady";
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
import {
selectTransferIsTargetComplete,
selectTransferShouldLockFields,
@ -112,8 +113,13 @@ function Target() {
disabled={true}
/>
<Alert severity="info" className={classes.alert}>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your tokens.
<Typography>
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your tokens.
</Typography>
{targetChain === CHAIN_ID_ETH && (
<EthGasEstimateSummary methodType="transfer" />
)}
</Alert>
<LowBalanceWarning chainId={targetChain} />
<ButtonWithLoader

View File

@ -6,6 +6,8 @@ import {
} from "@certusone/wormhole-sdk";
import { Provider } from "@certusone/wormhole-sdk/node_modules/@ethersproject/abstract-provider";
import { formatUnits } from "@ethersproject/units";
import { Typography } from "@material-ui/core";
import { LocalGasStation } from "@material-ui/icons";
import { Connection, PublicKey } from "@solana/web3.js";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
@ -13,6 +15,14 @@ import { SOLANA_HOST } from "../utils/consts";
import { getMultipleAccountsRPC } from "../utils/solana";
import useIsWalletReady from "./useIsWalletReady";
export type GasEstimate = {
currentGasPrice: string;
lowEstimate: string;
highEstimate: string;
};
export type MethodType = "nft" | "createWrapped" | "transfer";
//It's difficult to project how many fees the user will accrue during the
//workflow, as a variable number of transactions can be sent, and different
//execution paths can be hit in the smart contracts, altering gas used.
@ -131,3 +141,109 @@ export default function useTransactionFees(chainId: ChainId) {
return results;
}
export function useEthereumGasPrice(contract: MethodType) {
const { provider } = useEthereumProvider();
const { isReady } = useIsWalletReady(CHAIN_ID_ETH);
const [estimateResults, setEstimateResults] = useState<GasEstimate | null>(
null
);
useEffect(() => {
if (provider && isReady && !estimateResults) {
getGasEstimates(provider, contract).then(
(results) => {
setEstimateResults(results);
},
(error) => {
console.log(error);
}
);
}
}, [provider, isReady, estimateResults, contract]);
const results = useMemo(() => estimateResults, [estimateResults]);
return results;
}
export function EthGasEstimateSummary({
methodType,
}: {
methodType: MethodType;
}) {
const estimate = useEthereumGasPrice(methodType);
if (!estimate) {
return null;
}
return (
<Typography
component="div"
style={{
display: "flex",
alignItems: "center",
marginTop: 8,
flexWrap: "wrap",
}}
>
<div style={{ display: "flex", alignItems: "center" }}>
<LocalGasStation fontSize="inherit" />
&nbsp;{estimate.currentGasPrice}
</div>
<div>&nbsp;&nbsp;&nbsp;</div>
<div>
Est. Fees: {estimate.lowEstimate} - {estimate.highEstimate} ETH
</div>
</Typography>
);
}
const estimatesByContract = {
transfer: {
lowGasEstimate: BigInt(80000),
highGasEstimate: BigInt(130000),
},
nft: {
lowGasEstimate: BigInt(350000),
highGasEstimate: BigInt(500000),
},
createWrapped: {
lowGasEstimate: BigInt(450000),
highGasEstimate: BigInt(700000),
},
};
export async function getGasEstimates(
provider: Provider,
contract: MethodType
): Promise<GasEstimate | null> {
const lowEstimateGasAmount = estimatesByContract[contract].lowGasEstimate;
const highEstimateGasAmount = estimatesByContract[contract].highGasEstimate;
let lowEstimate;
let highEstimate;
let currentGasPrice;
if (provider) {
const priceInWei = await provider.getGasPrice();
if (priceInWei) {
lowEstimate = parseFloat(
formatUnits(lowEstimateGasAmount * priceInWei.toBigInt(), "ether")
).toFixed(4);
highEstimate = parseFloat(
formatUnits(highEstimateGasAmount * priceInWei.toBigInt(), "ether")
).toFixed(4);
currentGasPrice = parseFloat(formatUnits(priceInWei, "gwei")).toFixed(0);
}
}
const output =
currentGasPrice && highEstimate && lowEstimate
? {
currentGasPrice,
lowEstimate,
highEstimate,
}
: null;
return output;
}