Skip to content

Commit

Permalink
feat(trading): gas fee estimation for withdraw transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
asiaznik committed Jan 25, 2024
1 parent baf9875 commit b366691
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 11 deletions.
22 changes: 22 additions & 0 deletions libs/assets/src/lib/assets-data-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AssetsDocument, type AssetsQuery } from './__generated__/Assets';
import { AssetStatus } from '@vegaprotocol/types';
import { type Asset } from './asset-data-provider';
import { DENY_LIST } from './constants';
import { type AssetFieldsFragment } from './__generated__/Asset';

export interface BuiltinAssetSource {
__typename: 'BuiltinAsset';
Expand Down Expand Up @@ -88,3 +89,24 @@ export const useEnabledAssets = () => {
variables: undefined,
});
};

/** Wrapped ETH symbol */
const WETH = 'WETH';
type WETHDetails = Pick<AssetFieldsFragment, 'symbol' | 'decimals' | 'quantum'>;
/**
* Tries to find WETH asset configuration on Vega in order to provide its
* details, otherwise it returns hardcoded values.
*/
export const useWETH = (): WETHDetails => {
const { data } = useAssetsDataProvider();
if (data) {
const weth = data.find((a) => a.symbol.toUpperCase() === WETH);
if (weth) return weth;
}

return {
symbol: WETH,
decimals: 18,
quantum: '500000000000000', // 1 WETH ~= 2000 qUSD
};
};
5 changes: 4 additions & 1 deletion libs/i18n/src/locales/en/withdraws.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
"Withdrawals of {{threshold}} {{symbol}} or more will be delayed for {{delay}}.": "Withdrawals of {{threshold}} {{symbol}} or more will be delayed for {{delay}}.",
"Withdrawals ready": "Withdrawals ready",
"You have no assets to withdraw": "You have no assets to withdraw",
"Your funds have been unlocked for withdrawal - <0>View in block explorer<0>": "Your funds have been unlocked for withdrawal - <0>View in block explorer<0>"
"Your funds have been unlocked for withdrawal - <0>View in block explorer<0>": "Your funds have been unlocked for withdrawal - <0>View in block explorer<0>",
"Gas fee": "Gas fee",
"Estimated gas fee for the withdrawal transaction (refreshes each 15 seconds)": "Estimated gas fee for the withdrawal transaction (refreshes each 15 seconds)",
"It seems that the current gas prices are exceeding the amount you're trying to withdraw": "It seems that the current gas prices are exceeding the amount you're trying to withdraw"
}
20 changes: 20 additions & 0 deletions libs/utils/src/lib/format/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
quantumDecimalPlaces,
toDecimal,
toNumberParts,
toQUSD,
} from './number';

describe('number utils', () => {
Expand Down Expand Up @@ -246,3 +247,22 @@ describe('getUnlimitedThreshold', () => {
}
);
});

describe('toQUSD', () => {
it.each([
[0, 0, 0],
[1, 1, 1],
[1, 10, 0.1],
[1, 100, 0.01],
// real life examples
[1000000, 1000000, 1], // USDC -> 1 USDC ~= 1 qUSD
[500000, 1000000, 0.5], // USDC => 0.6 USDC ~= 0.5 qUSD
[1e18, 1e18, 1], // VEGA -> 1 VEGA ~= 1 qUSD
[123.45e18, 1e18, 123.45], // VEGA -> 1 VEGA ~= 1 qUSD
[1e18, 5e14, 2000], // WETH -> 1 WETH ~= 2000 qUSD
[1e9, 5e14, 0.000002], // gwei -> 1 gwei ~= 0.000002 qUSD
[50000e9, 5e14, 0.1], // gwei -> 50000 gwei ~= 0.1 qUSD
])('converts (%d, %d) to %d qUSD', (amount, quantum, expected) => {
expect(toQUSD(amount, quantum).toNumber()).toEqual(expected);
});
});
23 changes: 22 additions & 1 deletion libs/utils/src/lib/format/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function toDecimal(numberOfDecimals: number) {
}

export function toBigNum(
rawValue: string | number,
rawValue: string | number | BigNumber,
decimals: number
): BigNumber {
const divides = new BigNumber(10).exponentiatedBy(decimals);
Expand Down Expand Up @@ -209,3 +209,24 @@ export const formatNumberRounded = (num: BigNumber) => {

return value;
};

/**
* Converts given amount in one asset (determined by raw amount
* and quantum values) to qUSD.
* @param amount The raw amount
* @param quantum The quantum value of the asset.
*/
export const toQUSD = (
amount: string | number | BigNumber,
quantum: string | number
) => {
const value = new BigNumber(amount);
let q = new BigNumber(quantum);

if (q.isNaN() || q.isLessThanOrEqualTo(0)) {
q = new BigNumber(1);
}

const qUSD = value.dividedBy(q);
return qUSD;
};
17 changes: 9 additions & 8 deletions libs/web3/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export * from './lib/__generated__/TransactionResult';
export * from './lib/__generated__/WithdrawalApproval';
export * from './lib/constants';
export * from './lib/default-web3-provider';
export * from './lib/eip-1193-custom-bridge';
export * from './lib/ethereum-error';
export * from './lib/ethereum-transaction-dialog';
export * from './lib/types';
export * from './lib/url-connector';
export * from './lib/use-bridge-contract';
export * from './lib/use-eager-connect';
Expand All @@ -15,23 +19,20 @@ export * from './lib/use-ethereum-transaction';
export * from './lib/use-ethereum-withdraw-approval-toasts';
export * from './lib/use-ethereum-withdraw-approvals-manager';
export * from './lib/use-ethereum-withdraw-approvals-store';
export * from './lib/use-gas-price';
export * from './lib/use-get-withdraw-delay';
export * from './lib/use-get-withdraw-threshold';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-transaction-result';
export * from './lib/use-vega-transaction-manager';
export * from './lib/use-vega-transaction-store';
export * from './lib/use-vega-transaction-toasts';
export * from './lib/use-vega-transaction-updater';
export * from './lib/use-web3-disconnect';
export * from './lib/web3-connect-dialog';
export * from './lib/web3-connect-store';
export * from './lib/web3-connectors';
export * from './lib/web3-provider';
export * from './lib/withdrawal-approval-dialog';
export * from './lib/withdrawal-approval-status';
export * from './lib/default-web3-provider';
export * from './lib/use-vega-transaction-manager';
export * from './lib/use-vega-transaction-store';
export * from './lib/use-vega-transaction-updater';
export * from './lib/use-transaction-result';
export * from './lib/types';
export * from './lib/__generated__/TransactionResult';
export * from './lib/__generated__/WithdrawalApproval';
85 changes: 85 additions & 0 deletions libs/web3/src/lib/use-gas-price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect, useState } from 'react';
import { useWeb3React } from '@web3-react/core';

import { type BigNumber } from 'ethers';
import { useEthereumConfig } from './use-ethereum-config';

const DEFAULT_INTERVAL = 15000; // 15 seconds

export enum ContractMethod {
DEPOSIT_ASSET = '0xf7683932',
EXEMPT_DEPOSITOR = '0xb76fbb75',
GLOBAL_RESUME = '0xd72ed529',
GLOBAL_STOP = '0x9dfd3c88',
LIST_ASSET = '0x0ff3562c',
REMOVE_ASSET = '0xc76de358',
REVOKE_EXEMPT_DEPOSITOR = '0x6a1c6fa4',
SET_ASSET_LIMITS = '0x41fb776d',
SET_WITHDRAW_DELAY = '0x5a246728',
WITHDRAW_ASSET = '0x3ad90635',
}

export type GasData = {
/** The base (minimum) price of 1 unit of gas */
basePrice: BigNumber;
/** The maximum price of 1 unit of gas */
maxPrice: BigNumber;
/** The amount of gas (units) needed to process a transaction */
gas: BigNumber;
};

/**
* Gets the "current" gas price from the ethereum network.
* @returns gwei
*/
export const useGasPrice = (
method: ContractMethod,
interval = DEFAULT_INTERVAL
): GasData | undefined => {
const [gas, setGas] = useState<GasData | undefined>(undefined);
const { provider, account } = useWeb3React();
const { config } = useEthereumConfig();

useEffect(() => {
const estimate = async () => {
if (!provider || !config || !account) return;

try {
const data = await provider.getFeeData();
const estGasAmount = await provider.estimateGas({
to: account,
from: config?.collateral_bridge_contract.address,
data: method,
});

if (data.lastBaseFeePerGas && data.maxFeePerGas) {
const fees: GasData = {
basePrice: data.lastBaseFeePerGas,
maxPrice: data.maxFeePerGas,
gas: estGasAmount,
};
setGas(fees);
}
} catch (err) {
// NOOP - could not get the estimated gas or the fee data from
// the network. This could happen if there's an issue with transaction
// request parameters (e.g. to/from mismatch)
}
};

estimate();

// Gets another estimation in [interval] ms.
let i: ReturnType<typeof setInterval>;
if (interval > 0) {
i = setInterval(() => {
estimate();
}, interval);
}
return () => {
if (i) clearInterval(i);
};
}, [account, config, interval, method, provider]);

return gas;
};
5 changes: 5 additions & 0 deletions libs/withdraws/src/lib/withdraw-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import { formatDistanceToNow } from 'date-fns';
import { useForm, Controller, useWatch } from 'react-hook-form';
import { WithdrawLimits } from './withdraw-limits';
import {
ContractMethod,
ETHEREUM_EAGER_CONNECT,
useGasPrice,
useWeb3ConnectStore,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
Expand Down Expand Up @@ -140,6 +142,8 @@ export const WithdrawForm = ({

const amount = useWatch({ name: 'amount', control });

const gasPrice = useGasPrice(ContractMethod.WITHDRAW_ASSET);

const onSubmit = async (fields: FormFields) => {
if (!selectedAsset) {
throw new Error('Asset not selected');
Expand Down Expand Up @@ -247,6 +251,7 @@ export const WithdrawForm = ({
delay={delay}
balance={balance}
asset={selectedAsset}
gas={gasPrice}
/>
</div>
)}
Expand Down
95 changes: 94 additions & 1 deletion libs/withdraws/src/lib/withdraw-limits.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Asset } from '@vegaprotocol/assets';
import { CompactNumber } from '@vegaprotocol/react-helpers';
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from '@vegaprotocol/assets';
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT, useWETH } from '@vegaprotocol/assets';
import {
KeyValueTable,
KeyValueTableRow,
Expand All @@ -9,13 +9,18 @@ import {
import BigNumber from 'bignumber.js';
import { formatDistanceToNow } from 'date-fns';
import { useT } from './use-t';
import { type GasData } from '@vegaprotocol/web3';
import { formatNumber, removeDecimal, toQUSD } from '@vegaprotocol/utils';
import { utils } from 'ethers';
import classNames from 'classnames';

interface WithdrawLimitsProps {
amount: string;
threshold: BigNumber | undefined;
balance: BigNumber;
delay: number | undefined;
asset: Asset;
gas?: GasData;
}

export const WithdrawLimits = ({
Expand All @@ -24,6 +29,7 @@ export const WithdrawLimits = ({
balance,
delay,
asset,
gas,
}: WithdrawLimitsProps) => {
const t = useT();
const delayTime =
Expand Down Expand Up @@ -64,6 +70,22 @@ export const WithdrawLimits = ({
label: t('Delay time'),
value: threshold && delay ? delayTime : '-',
},
{
key: 'GAS_FEE',
tooltip: t(
'Estimated gas fee for the withdrawal transaction (refreshes each 15 seconds)'
),
label: t('Gas fee'),
value: (
<GasPrice
gasPrice={gas}
amount={{
value: removeDecimal(amount, asset.decimals),
quantum: asset.quantum,
}}
/>
),
},
];

return (
Expand Down Expand Up @@ -91,3 +113,74 @@ export const WithdrawLimits = ({
</KeyValueTable>
);
};

const GasPrice = ({
gasPrice,
amount,
}: {
gasPrice: WithdrawLimitsProps['gas'];
amount: { value: string; quantum: string };
}) => {
const t = useT();
const { quantum: wethQuantum } = useWETH();
const { value, quantum } = amount;
if (gasPrice) {
const { basePrice, maxPrice, gas } = gasPrice;
const b = basePrice.mul(gas);
const m = maxPrice.mul(gas);

const bGwei = utils.formatUnits(b, 'gwei');
const mGwei = utils.formatUnits(m, 'gwei');

const bEther = utils.formatUnits(b, 'ether');
const mEther = utils.formatUnits(m, 'ether');

const bQUSD = toQUSD(b.toNumber(), wethQuantum);
const mQUSD = toQUSD(m.toNumber(), wethQuantum);

const vQUSD = toQUSD(value, quantum);

const isExpensive =
!vQUSD.isLessThanOrEqualTo(0) && vQUSD.isLessThanOrEqualTo(mQUSD);
const expensiveClassNames = {
'text-vega-red-500': isExpensive && vQUSD.isLessThanOrEqualTo(bQUSD),
'text-vega-orange-500':
isExpensive &&
vQUSD.isGreaterThan(bQUSD) &&
vQUSD.isLessThanOrEqualTo(mQUSD),
};

return (
<div className={classNames('flex flex-col items-end self-end')}>
<Tooltip
description={
<div className="flex flex-col gap-1">
{isExpensive && (
<span className={classNames(expensiveClassNames)}>
{t(
"It seems that the current gas prices are exceeding the amount you're trying to withdraw"
)}{' '}
<strong>(~{vQUSD.toFixed(2)} qUSD)</strong>.
</span>
)}
<span>
{bEther} ETH - {mEther} ETH
</span>
</div>
}
>
<span className={classNames(expensiveClassNames)}>
{formatNumber(bGwei, 0)} gwei
<span className="text-xs"> - {formatNumber(mGwei, 0)} gwei</span>
</span>
</Tooltip>

<span className="text-muted text-xs">
~{formatNumber(bQUSD, 2)} qUSD - ~{formatNumber(mQUSD, 2)} qUSD
</span>
</div>
);
}

return '-';
};

0 comments on commit b366691

Please sign in to comment.