parent
658f7640ff
commit
d629d8d27a
|
@ -16,6 +16,7 @@ import {
|
|||
getErc20Balance,
|
||||
swapErc20ToSpl,
|
||||
useEthAccount,
|
||||
estimateErc20SwapFees,
|
||||
} from '../utils/swap/eth';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
|
@ -26,7 +27,8 @@ import CircularProgress from '@material-ui/core/CircularProgress';
|
|||
import Link from '@material-ui/core/Link';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { DialogContentText } from '@material-ui/core';
|
||||
import { DialogContentText, Tooltip } from '@material-ui/core';
|
||||
import { EthFeeEstimate } from './EthFeeEstimate';
|
||||
|
||||
export default function DepositDialog({
|
||||
open,
|
||||
|
@ -117,6 +119,7 @@ export default function DepositDialog({
|
|||
<SolletSwapDepositAddress
|
||||
balanceInfo={balanceInfo}
|
||||
swapInfo={swapInfo}
|
||||
ethAccount={ethAccount}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
@ -127,11 +130,41 @@ export default function DepositDialog({
|
|||
);
|
||||
}
|
||||
|
||||
function SolletSwapDepositAddress({ balanceInfo, swapInfo }) {
|
||||
function SolletSwapDepositAddress({ balanceInfo, swapInfo, ethAccount }) {
|
||||
const [ethBalance] = useAsyncData(
|
||||
() => getErc20Balance(ethAccount),
|
||||
'ethBalance',
|
||||
{
|
||||
refreshInterval: 2000,
|
||||
},
|
||||
);
|
||||
|
||||
const ethFeeData = useAsyncData(
|
||||
swapInfo.coin &&
|
||||
(() =>
|
||||
estimateErc20SwapFees({
|
||||
erc20Address: swapInfo.coin.erc20Contract,
|
||||
swapAddress: swapInfo.address,
|
||||
ethAccount,
|
||||
})),
|
||||
'depositEthFee',
|
||||
{
|
||||
refreshInterval: 2000,
|
||||
},
|
||||
);
|
||||
|
||||
if (!swapInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ethFeeEstimate = Array.isArray(ethFeeData[0])
|
||||
? ethFeeData[0].reduce((acc, elem) => acc + elem)
|
||||
: ethFeeData[0];
|
||||
const insufficientEthBalance =
|
||||
typeof ethBalance === 'number' &&
|
||||
typeof ethFeeEstimate === 'number' &&
|
||||
ethBalance < ethFeeEstimate;
|
||||
|
||||
const { blockchain, address, memo, coin } = swapInfo;
|
||||
const { mint, tokenName } = balanceInfo;
|
||||
|
||||
|
@ -160,7 +193,17 @@ function SolletSwapDepositAddress({ balanceInfo, swapInfo }) {
|
|||
converted to {mint ? 'SPL' : 'native'} {tokenName} via MetaMask. To
|
||||
convert, you must already have SOL in your wallet.
|
||||
</DialogContentText>
|
||||
<MetamaskDeposit swapInfo={swapInfo} />
|
||||
<DialogContentText>
|
||||
Estimated withdrawal transaction fee:
|
||||
<EthFeeEstimate
|
||||
ethFeeData={ethFeeData}
|
||||
insufficientEthBalance={insufficientEthBalance}
|
||||
/>
|
||||
</DialogContentText>
|
||||
<MetamaskDeposit
|
||||
swapInfo={swapInfo}
|
||||
insufficientEthBalance={insufficientEthBalance}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -168,7 +211,7 @@ function SolletSwapDepositAddress({ balanceInfo, swapInfo }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function MetamaskDeposit({ swapInfo }) {
|
||||
function MetamaskDeposit({ swapInfo, insufficientEthBalance }) {
|
||||
const ethAccount = useEthAccount();
|
||||
const [amount, setAmount] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
@ -219,6 +262,28 @@ function MetamaskDeposit({ swapInfo }) {
|
|||
}
|
||||
|
||||
if (!submitted) {
|
||||
let convertButton = (
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={submit}
|
||||
disabled={insufficientEthBalance}
|
||||
>
|
||||
Convert
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (insufficientEthBalance) {
|
||||
convertButton = (
|
||||
<Tooltip
|
||||
title="Insufficient ETH for withdrawal transaction fee"
|
||||
placement="top"
|
||||
>
|
||||
<span>{convertButton}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'baseline' }}>
|
||||
<TextField
|
||||
|
@ -245,9 +310,7 @@ function MetamaskDeposit({ swapInfo }) {
|
|||
) : null
|
||||
}
|
||||
/>
|
||||
<Button color="primary" style={{ marginLeft: 8 }} onClick={submit}>
|
||||
Convert
|
||||
</Button>
|
||||
{convertButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import { DialogContentText } from "@material-ui/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useConnection } from "../utils/connection";
|
||||
import { priceStore, serumMarkets } from "../utils/markets";
|
||||
|
||||
function FeeContentText({ ethFee, ethPrice, warning = false, prefix = "", bold = false }) {
|
||||
let usdFeeEstimate = ethPrice !== undefined ? ethPrice * ethFee : null;
|
||||
|
||||
return (
|
||||
<DialogContentText
|
||||
color={warning ? 'secondary' : 'textPrimary'}
|
||||
// @ts-ignore
|
||||
style={{ marginBottom: '0px', fontWeight: bold ? '500' : undefined }}
|
||||
>
|
||||
{prefix}
|
||||
{ethFee.toFixed(4)}
|
||||
{' ETH'}
|
||||
{usdFeeEstimate && ` (${usdFeeEstimate.toFixed(2)} USD)`}
|
||||
</DialogContentText>
|
||||
);
|
||||
}
|
||||
|
||||
export function EthFeeEstimate({ ethFeeData, insufficientEthBalance }) {
|
||||
let [ethFeeEstimate, loaded, error] = ethFeeData;
|
||||
const [ethPrice, setEthPrice] = useState<number | undefined>(undefined);
|
||||
const connection = useConnection();
|
||||
useEffect(() => {
|
||||
if (ethPrice === undefined) {
|
||||
let m = serumMarkets['ETH'];
|
||||
priceStore.getPrice(connection, m.name).then(setEthPrice);
|
||||
}
|
||||
}, [ethPrice, connection]);
|
||||
|
||||
if (!loaded && !error) {
|
||||
return (
|
||||
<DialogContentText color="textPrimary">Loading...</DialogContentText>
|
||||
);
|
||||
} else if (error) {
|
||||
return (
|
||||
<DialogContentText color="textPrimary">
|
||||
Unable to estimate
|
||||
</DialogContentText>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(ethFeeEstimate)) {
|
||||
const [approveFee, swapFee] = ethFeeEstimate;
|
||||
return (
|
||||
<DialogContentText>
|
||||
<FeeContentText ethFee={approveFee} ethPrice={ethPrice} prefix={"Approve: "} />
|
||||
<FeeContentText ethFee={swapFee} ethPrice={ethPrice} prefix={"Swap: "} />
|
||||
<FeeContentText
|
||||
warning={insufficientEthBalance}
|
||||
ethFee={approveFee + swapFee}
|
||||
ethPrice={ethPrice}
|
||||
prefix={"Total: "}
|
||||
bold
|
||||
/>
|
||||
</DialogContentText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FeeContentText
|
||||
warning={insufficientEthBalance}
|
||||
ethFee={ethFeeEstimate}
|
||||
ethPrice={ethPrice}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -21,7 +21,6 @@ import {
|
|||
useEthAccount,
|
||||
withdrawEth,
|
||||
} from '../utils/swap/eth';
|
||||
import { serumMarkets, priceStore } from '../utils/markets';
|
||||
import { useConnection, useIsProdNetwork } from '../utils/connection';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
|
@ -36,6 +35,7 @@ import {
|
|||
} from '../utils/tokens/instructions';
|
||||
import { parseTokenAccountData } from '../utils/tokens/data';
|
||||
import { Switch, Tooltip } from '@material-ui/core';
|
||||
import { EthFeeEstimate } from './EthFeeEstimate';
|
||||
|
||||
const WUSDC_MINT = new PublicKey(
|
||||
'BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW',
|
||||
|
@ -411,7 +411,7 @@ function SendSwapDialog({
|
|||
{blockchain === 'eth' && (
|
||||
<DialogContentText>
|
||||
Estimated withdrawal transaction fee:
|
||||
<EthWithdrawalFeeEstimate
|
||||
<EthFeeEstimate
|
||||
ethFeeData={ethFeeData}
|
||||
insufficientEthBalance={insufficientEthBalance}
|
||||
/>
|
||||
|
@ -631,42 +631,3 @@ function EthWithdrawalCompleterItem({ ethAccount, swap }) {
|
|||
}, [withdrawal.txid, withdrawal.status]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function EthWithdrawalFeeEstimate({
|
||||
ethFeeData,
|
||||
insufficientEthBalance,
|
||||
}) {
|
||||
let [ethFeeEstimate, loaded, error] = ethFeeData;
|
||||
const [ethPrice, setEthPrice] = useState(null);
|
||||
const connection = useConnection();
|
||||
useEffect(() => {
|
||||
if (ethPrice === null) {
|
||||
let m = serumMarkets['ETH'];
|
||||
priceStore.getPrice(connection, m.name).then(setEthPrice);
|
||||
}
|
||||
}, [ethPrice, connection]);
|
||||
|
||||
if (!loaded && !error) {
|
||||
return (
|
||||
<DialogContentText color="textPrimary">Loading...</DialogContentText>
|
||||
);
|
||||
} else if (error) {
|
||||
return (
|
||||
<DialogContentText color="textPrimary">
|
||||
Unable to estimate
|
||||
</DialogContentText>
|
||||
);
|
||||
}
|
||||
|
||||
let usdFeeEstimate = ethPrice !== null ? ethPrice * ethFeeEstimate : null;
|
||||
|
||||
return (
|
||||
<DialogContentText
|
||||
color={insufficientEthBalance ? 'secondary' : 'textPrimary'}
|
||||
>
|
||||
{ethFeeEstimate.toFixed(4)}
|
||||
{' ETH'}
|
||||
{usdFeeEstimate && ` (${usdFeeEstimate.toFixed(2)} USD)`}
|
||||
</DialogContentText>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class PriceStore {
|
|||
this.cache = {};
|
||||
}
|
||||
|
||||
async getPrice(connection, marketName) {
|
||||
async getPrice(connection, marketName): Promise<number | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (connection._rpcEndpoint !== MAINNET_URL) {
|
||||
resolve(undefined);
|
||||
|
|
|
@ -6,6 +6,8 @@ import Button from '@material-ui/core/Button';
|
|||
import { useCallAsync } from '../notifications';
|
||||
|
||||
const web3 = new Web3(window.ethereum);
|
||||
// Change to use estimated gas limit
|
||||
const SUGGESTED_GAS_LIMIT = 200000;
|
||||
|
||||
export function useEthAccount() {
|
||||
const [account, setAccount] = useState(null);
|
||||
|
@ -37,6 +39,42 @@ export async function getErc20Balance(account, erc20Address) {
|
|||
return parseInt(value, 10) / 10 ** parseInt(decimals, 10);
|
||||
}
|
||||
|
||||
export async function estimateErc20SwapFees({
|
||||
erc20Address,
|
||||
swapAddress,
|
||||
ethAccount,
|
||||
}) {
|
||||
if (!erc20Address) {
|
||||
return estimateEthSwapFees({ swapAddress });
|
||||
}
|
||||
|
||||
const erc20 = new web3.eth.Contract(ERC20_ABI, erc20Address);
|
||||
const decimals = parseInt(await erc20.methods.decimals().call(), 10);
|
||||
|
||||
const approveAmount = addDecimals('100000000', decimals);
|
||||
|
||||
let approveEstimatedGas = await erc20.methods
|
||||
.approve(swapAddress, approveAmount)
|
||||
.estimateGas({ from: ethAccount });
|
||||
// Account for Metamask over-estimation
|
||||
approveEstimatedGas *= 1.5;
|
||||
|
||||
// Use estimated gas limit for now
|
||||
const swapEstimatedGas = SUGGESTED_GAS_LIMIT;
|
||||
|
||||
const gasPrice = (await web3.eth.getGasPrice()) * 1e-18;
|
||||
|
||||
return [approveEstimatedGas * gasPrice, swapEstimatedGas * gasPrice];
|
||||
}
|
||||
|
||||
export async function estimateEthSwapFees() {
|
||||
const estimatedGas = SUGGESTED_GAS_LIMIT;
|
||||
|
||||
const gasPrice = (await web3.eth.getGasPrice()) * 1e-18;
|
||||
|
||||
return estimatedGas * gasPrice;
|
||||
}
|
||||
|
||||
export async function swapErc20ToSpl({
|
||||
ethAccount,
|
||||
erc20Address,
|
||||
|
@ -70,7 +108,7 @@ export async function swapErc20ToSpl({
|
|||
|
||||
const swapTx = swap.methods
|
||||
.swapErc20(erc20Address, destination, encodedAmount)
|
||||
.send({ from: ethAccount, gasLimit: 200000 });
|
||||
.send({ from: ethAccount, gasLimit: SUGGESTED_GAS_LIMIT });
|
||||
const swapTxid = await waitForTxid(swapTx);
|
||||
|
||||
onStatusChange({ step: 2, txid: swapTxid, confirms: 0 });
|
||||
|
@ -157,7 +195,7 @@ export async function withdrawEth(from, withdrawal, callAsync) {
|
|||
return;
|
||||
}
|
||||
pendingNonces.add(nonce);
|
||||
await callAsync(method.send({ from, gasLimit: 200000 }), {
|
||||
await callAsync(method.send({ from, gasLimit: SUGGESTED_GAS_LIMIT }), {
|
||||
progressMessage: `Completing ${withdrawal.coin.ticker} transfer...`,
|
||||
});
|
||||
pendingNonces.delete(nonce);
|
||||
|
|
Loading…
Reference in New Issue