mirror of
https://github.com/RabbyHub/Rabby.git
synced 2026-04-17 03:01:32 -04:00
feat: estimate gas (#2871)
* feat: estimate gas * fix * fix: gas price * chore: @rabby-wallet/rabby-api@0.9.17
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
"@rabby-wallet/gnosis-sdk": "1.4.2",
|
||||
"@rabby-wallet/page-provider": "0.4.9",
|
||||
"@rabby-wallet/rabby-action": "0.1.8",
|
||||
"@rabby-wallet/rabby-api": "0.9.16",
|
||||
"@rabby-wallet/rabby-api": "0.9.17",
|
||||
"@rabby-wallet/rabby-security-engine": "2.0.7",
|
||||
"@rabby-wallet/rabby-swap": "0.0.51",
|
||||
"@rabby-wallet/widgets": "1.0.9",
|
||||
|
||||
@@ -101,6 +101,7 @@ export const OfflineChainNotify = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={e.id}
|
||||
className={clsx(
|
||||
'hidden last:flex',
|
||||
'w-full h-auto min-h-[32px] bg-rabby-orange-light',
|
||||
|
||||
@@ -173,16 +173,10 @@ export const DexQuoteItem = (
|
||||
let disable = false;
|
||||
let receivedTokenUsd: React.ReactNode = null;
|
||||
let diffUsd: React.ReactNode = null;
|
||||
const balanceChangeReceiveTokenAmount = preExecResult
|
||||
? preExecResult.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(item) => isSameAddress(item.id, receiveToken.id)
|
||||
)?.amount || '0'
|
||||
: '0';
|
||||
const actualReceiveAmount = inSufficient
|
||||
? new BigNumber(quote?.toTokenAmount || 0)
|
||||
.div(10 ** (quote?.toTokenDecimals || receiveToken.decimals))
|
||||
.toString()
|
||||
: balanceChangeReceiveTokenAmount;
|
||||
const actualReceiveAmount = new BigNumber(quote?.toTokenAmount || 0)
|
||||
.div(10 ** (quote?.toTokenDecimals || receiveToken.decimals))
|
||||
.toString();
|
||||
|
||||
if (actualReceiveAmount || dexId === 'WrapToken') {
|
||||
const receiveAmount =
|
||||
actualReceiveAmount || (dexId === 'WrapToken' ? payAmount : 0);
|
||||
@@ -290,18 +284,10 @@ export const DexQuoteItem = (
|
||||
sortIncludeGasFee,
|
||||
]);
|
||||
|
||||
const CheckIcon = useCallback(() => {
|
||||
if (disabled || loading || !quote?.tx || !preExecResult?.swapPreExecTx) {
|
||||
return null;
|
||||
}
|
||||
return <CheckedIcon />;
|
||||
}, [disabled, loading, quote?.tx, preExecResult?.swapPreExecTx]);
|
||||
|
||||
const gasFeeTooHight = useMemo(() => {
|
||||
return (
|
||||
new BigNumber(preExecResult?.swapPreExecTx?.gas?.gas_used || 0).gte(
|
||||
GAS_USE_AMOUNT_LIMIT
|
||||
) && chain === CHAINS_ENUM.ETH
|
||||
new BigNumber(preExecResult?.gasUsed || 0).gte(GAS_USE_AMOUNT_LIMIT) &&
|
||||
chain === CHAINS_ENUM.ETH
|
||||
);
|
||||
}, [preExecResult, chain]);
|
||||
|
||||
@@ -315,9 +301,9 @@ export const DexQuoteItem = (
|
||||
}
|
||||
if (disabled) return;
|
||||
const actualReceiveAmount =
|
||||
preExecResult?.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(item) => isSameAddress(item.id, receiveToken.id)
|
||||
)?.amount || 0;
|
||||
new BigNumber(quote?.toTokenAmount || 0)
|
||||
.div(10 ** (quote?.toTokenDecimals || receiveToken.decimals))
|
||||
.toString() || 0;
|
||||
setActiveProvider?.({
|
||||
manualClick: true,
|
||||
name: dexId,
|
||||
@@ -449,10 +435,10 @@ export const DexQuoteItem = (
|
||||
hideConer
|
||||
/>
|
||||
)}
|
||||
<div className="ml-6 mr-4 flex items-center">
|
||||
<div className="ml-6 flex items-center">
|
||||
{receiveOrErrorContent}
|
||||
</div>
|
||||
<CheckIcon />
|
||||
{/* <CheckIcon /> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -59,10 +59,14 @@ export const Quotes = ({
|
||||
if (!quote.preExecResult) {
|
||||
return new BigNumber(Number.MIN_SAFE_INTEGER);
|
||||
}
|
||||
const receiveTokenAmount =
|
||||
quote?.preExecResult.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(item) => isSameAddress(item.id, other.receiveToken.id)
|
||||
)?.amount || 0;
|
||||
const receiveTokenAmount = new BigNumber(
|
||||
quote?.data?.toTokenAmount || 0
|
||||
)
|
||||
.div(
|
||||
10 **
|
||||
(quote?.data?.toTokenDecimals || other.receiveToken.decimals)
|
||||
)
|
||||
.toString();
|
||||
if (sortIncludeGasFee) {
|
||||
return new BigNumber(receiveTokenAmount)
|
||||
.times(price)
|
||||
@@ -79,11 +83,13 @@ export const Quotes = ({
|
||||
|
||||
const [bestQuoteAmount, bestQuoteGasUsd] = useMemo(() => {
|
||||
const bestQuote = sortedList?.[0];
|
||||
const receiveTokenAmount = bestQuote?.preExecResult
|
||||
? bestQuote.preExecResult.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(item) => isSameAddress(item.id, other.receiveToken.id)
|
||||
)?.amount || 0
|
||||
: 0;
|
||||
const receiveTokenAmount =
|
||||
new BigNumber(bestQuote?.data?.toTokenAmount || 0)
|
||||
.div(
|
||||
10 **
|
||||
(bestQuote?.data?.toTokenDecimals || other.receiveToken.decimals)
|
||||
)
|
||||
.toString() || '0';
|
||||
|
||||
return [
|
||||
inSufficient
|
||||
@@ -119,9 +125,12 @@ export const Quotes = ({
|
||||
name={dex?.name}
|
||||
isBestQuote
|
||||
bestQuoteAmount={`${
|
||||
dex?.preExecResult?.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(token) => isSameAddress(token.id, other.receiveToken.id)
|
||||
)?.amount || '0'
|
||||
new BigNumber(dex?.data?.toTokenAmount || 0)
|
||||
.div(
|
||||
10 **
|
||||
(dex?.data?.toTokenDecimals || other.receiveToken.decimals)
|
||||
)
|
||||
.toString() || '0'
|
||||
}`}
|
||||
bestQuoteGasUsd={bestQuoteGasUsd}
|
||||
isLoading={dex.loading}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { DEX, ETH_USDT_CONTRACT, SWAP_FEE_ADDRESS } from '@/constant';
|
||||
import { formatUsdValue, isSameAddress, useWallet } from '@/ui/utils';
|
||||
import { CHAINS, CHAINS_ENUM } from '@debank/common';
|
||||
import {
|
||||
ExplainTxResponse,
|
||||
TokenItem,
|
||||
Tx,
|
||||
} from '@rabby-wallet/rabby-api/dist/types';
|
||||
import { CHAINS_ENUM } from '@debank/common';
|
||||
import { TokenItem, Tx } from '@rabby-wallet/rabby-api/dist/types';
|
||||
import {
|
||||
DEX_ENUM,
|
||||
DEX_ROUTER_WHITELIST,
|
||||
@@ -14,7 +10,7 @@ import {
|
||||
} from '@rabby-wallet/rabby-swap';
|
||||
import { QuoteResult, getQuote } from '@rabby-wallet/rabby-swap/dist/quote';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import pRetry from 'p-retry';
|
||||
import { useRabbySelector } from '@/ui/store';
|
||||
import stats from '@/stats';
|
||||
@@ -33,6 +29,8 @@ export const useQuoteMethods = () => {
|
||||
const walletController = useWallet();
|
||||
const walletOpenapi = walletController.openapi;
|
||||
|
||||
const nativeTokenPriceRef = useRef<Promise<TokenItem>>();
|
||||
|
||||
const validSlippage = React.useCallback(
|
||||
async ({
|
||||
chain,
|
||||
@@ -149,7 +147,7 @@ export const useQuoteMethods = () => {
|
||||
[walletController.getERC20Allowance]
|
||||
);
|
||||
|
||||
const getPreExecResult = React.useCallback(
|
||||
const getPreEstimateGasUsed = React.useCallback(
|
||||
async ({
|
||||
userAddress,
|
||||
chain,
|
||||
@@ -167,38 +165,45 @@ export const useQuoteMethods = () => {
|
||||
const lastTimeGas: ChainGas | null = await walletController.getLastTimeGasSelection(
|
||||
chainInfo.id
|
||||
);
|
||||
const gasMarket = await walletController.gasMarketV2({
|
||||
chain: chainInfo,
|
||||
tx: {
|
||||
...quote.tx,
|
||||
nonce,
|
||||
chainId: chainInfo.id,
|
||||
gas: '0x0',
|
||||
},
|
||||
});
|
||||
|
||||
let gasPrice = 0;
|
||||
if (lastTimeGas?.lastTimeSelect === 'gasPrice' && lastTimeGas.gasPrice) {
|
||||
// use cached gasPrice if exist
|
||||
gasPrice = lastTimeGas.gasPrice;
|
||||
} else if (
|
||||
lastTimeGas?.lastTimeSelect &&
|
||||
lastTimeGas?.lastTimeSelect === 'gasLevel'
|
||||
) {
|
||||
const target = gasMarket.find(
|
||||
(item) => item.level === lastTimeGas?.gasLevel
|
||||
)!;
|
||||
if (target) {
|
||||
gasPrice = target.price;
|
||||
const getGasPrice = async () => {
|
||||
const gasMarket = await walletController.gasMarketV2({
|
||||
chain: chainInfo,
|
||||
tx: {
|
||||
...quote.tx,
|
||||
nonce,
|
||||
chainId: chainInfo.id,
|
||||
gas: '0x0',
|
||||
},
|
||||
});
|
||||
|
||||
let gasPrice = 0;
|
||||
if (
|
||||
lastTimeGas?.lastTimeSelect === 'gasPrice' &&
|
||||
lastTimeGas.gasPrice
|
||||
) {
|
||||
// use cached gasPrice if exist
|
||||
gasPrice = lastTimeGas.gasPrice;
|
||||
} else if (
|
||||
lastTimeGas?.lastTimeSelect &&
|
||||
lastTimeGas?.lastTimeSelect === 'gasLevel'
|
||||
) {
|
||||
const target = gasMarket.find(
|
||||
(item) => item.level === lastTimeGas?.gasLevel
|
||||
)!;
|
||||
if (target) {
|
||||
gasPrice = target.price;
|
||||
} else {
|
||||
gasPrice =
|
||||
gasMarket.find((item) => item.level === 'normal')?.price || 0;
|
||||
}
|
||||
} else {
|
||||
// no cache, use the fast level in gasMarket
|
||||
gasPrice =
|
||||
gasMarket.find((item) => item.level === 'normal')?.price || 0;
|
||||
}
|
||||
} else {
|
||||
// no cache, use the fast level in gasMarket
|
||||
gasPrice =
|
||||
gasMarket.find((item) => item.level === 'normal')?.price || 0;
|
||||
}
|
||||
return gasPrice;
|
||||
};
|
||||
|
||||
let nextNonce = nonce;
|
||||
const pendingTx: Tx[] = [];
|
||||
@@ -218,11 +223,9 @@ export const useQuoteMethods = () => {
|
||||
...tokenApproveParams,
|
||||
nonce: nextNonce,
|
||||
value: '0x',
|
||||
gasPrice: `0x${new BigNumber(gasPrice).toString(16)}`,
|
||||
gas: '0x0',
|
||||
};
|
||||
|
||||
const tokenApprovePreExecTx = await walletOpenapi.preExecTx({
|
||||
const tokenApproveGas = await walletOpenapi.estimateGasUsd({
|
||||
tx: tokenApproveTx,
|
||||
origin: INTERNAL_REQUEST_ORIGIN,
|
||||
address: userAddress,
|
||||
@@ -230,22 +233,14 @@ export const useQuoteMethods = () => {
|
||||
pending_tx_list: pendingTx,
|
||||
});
|
||||
|
||||
if (!tokenApprovePreExecTx?.pre_exec?.success) {
|
||||
throw new Error('pre_exec_tx error');
|
||||
}
|
||||
const txGasUsed =
|
||||
tokenApproveGas.gas_used || tokenApproveGas.safe_gas_used || 0;
|
||||
|
||||
gasUsed +=
|
||||
tokenApprovePreExecTx.gas.gas_limit ||
|
||||
tokenApprovePreExecTx.gas.gas_used;
|
||||
gasUsed += txGasUsed;
|
||||
|
||||
pendingTx.push({
|
||||
...tokenApproveTx,
|
||||
gas: `0x${new BigNumber(
|
||||
tokenApprovePreExecTx.gas.gas_limit ||
|
||||
tokenApprovePreExecTx.gas.gas_used
|
||||
)
|
||||
.times(4)
|
||||
.toString(16)}`,
|
||||
gas: `0x${new BigNumber(txGasUsed).times(4).toString(16)}`,
|
||||
});
|
||||
nextNonce = `0x${new BigNumber(nextNonce).plus(1).toString(16)}`;
|
||||
};
|
||||
@@ -270,38 +265,40 @@ export const useQuoteMethods = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const swapPreExecTx = await walletOpenapi.preExecTx({
|
||||
tx: {
|
||||
...quote.tx,
|
||||
nonce: nextNonce,
|
||||
chainId: chainInfo.id,
|
||||
value: `0x${new BigNumber(quote.tx.value).toString(16)}`,
|
||||
gasPrice: `0x${new BigNumber(gasPrice).toString(16)}`,
|
||||
gas: '0x0',
|
||||
} as Tx,
|
||||
origin: INTERNAL_REQUEST_ORIGIN,
|
||||
address: userAddress,
|
||||
updateNonce: true,
|
||||
pending_tx_list: pendingTx,
|
||||
});
|
||||
const [swapTxGas, gasPrice = 0] = await Promise.all([
|
||||
walletOpenapi.estimateGasUsd({
|
||||
tx: {
|
||||
...quote.tx,
|
||||
nonce: nextNonce,
|
||||
chainId: chainInfo.id,
|
||||
value: `0x${new BigNumber(quote.tx.value).toString(16)}`,
|
||||
} as Tx,
|
||||
origin: INTERNAL_REQUEST_ORIGIN,
|
||||
address: userAddress,
|
||||
updateNonce: true,
|
||||
pending_tx_list: pendingTx,
|
||||
}),
|
||||
getGasPrice(),
|
||||
]);
|
||||
|
||||
if (!swapPreExecTx?.pre_exec?.success) {
|
||||
throw new Error('pre_exec_tx error');
|
||||
}
|
||||
const txGasUsed = swapTxGas.gas_used || swapTxGas.safe_gas_used || 0;
|
||||
|
||||
gasUsed += swapPreExecTx.gas.gas_limit || swapPreExecTx.gas.gas_used;
|
||||
gasUsed += txGasUsed;
|
||||
|
||||
const nativeToken = await nativeTokenPriceRef.current!;
|
||||
|
||||
const gasUsdValue = new BigNumber(gasUsed)
|
||||
.times(gasPrice)
|
||||
.div(10 ** swapPreExecTx.native_token.decimals)
|
||||
.times(swapPreExecTx.native_token.price)
|
||||
.div(10 ** nativeToken.decimals)
|
||||
.times(nativeToken.price)
|
||||
.toString(10);
|
||||
|
||||
return {
|
||||
shouldApproveToken: !tokenApproved,
|
||||
shouldTwoStepApprove,
|
||||
swapPreExecTx,
|
||||
swapPreExecTx: swapTxGas,
|
||||
gasPrice,
|
||||
gasUsed,
|
||||
gasUsdValue,
|
||||
gasUsd: formatUsdValue(gasUsdValue),
|
||||
};
|
||||
@@ -387,7 +384,7 @@ export const useQuoteMethods = () => {
|
||||
try {
|
||||
preExecResult = await pRetry(
|
||||
() =>
|
||||
getPreExecResult({
|
||||
getPreEstimateGasUsed({
|
||||
userAddress,
|
||||
chain,
|
||||
payToken,
|
||||
@@ -456,7 +453,7 @@ export const useQuoteMethods = () => {
|
||||
return quote;
|
||||
}
|
||||
},
|
||||
[walletOpenapi, pRetry, getPreExecResult]
|
||||
[walletOpenapi, pRetry, getPreEstimateGasUsed]
|
||||
);
|
||||
|
||||
const supportedDEXList = useRabbySelector((s) => s.swap.supportedDEXList);
|
||||
@@ -467,6 +464,18 @@ export const useQuoteMethods = () => {
|
||||
setQuote: (quote: TDexQuoteData) => void;
|
||||
}
|
||||
) => {
|
||||
const chainObj = findChainByEnum(params.chain)!;
|
||||
|
||||
nativeTokenPriceRef.current = pRetry(
|
||||
() =>
|
||||
walletOpenapi.getToken(
|
||||
params.userAddress,
|
||||
chainObj.serverId,
|
||||
chainObj.nativeTokenAddress
|
||||
),
|
||||
{ retries: 1 }
|
||||
);
|
||||
|
||||
if (
|
||||
isSwapWrapToken(
|
||||
params.payToken.id,
|
||||
@@ -482,9 +491,7 @@ export const useQuoteMethods = () => {
|
||||
|
||||
return Promise.all([
|
||||
...(supportedDEXList.filter((e) => DEX[e]) as DEX_ENUM[]).map((dexId) =>
|
||||
getDexQuote({ ...params, dexId }).finally(() => {
|
||||
console.log('dexid end', dexId);
|
||||
})
|
||||
getDexQuote({ ...params, dexId })
|
||||
),
|
||||
]);
|
||||
},
|
||||
@@ -497,7 +504,7 @@ export const useQuoteMethods = () => {
|
||||
postSwap,
|
||||
getToken,
|
||||
getTokenApproveStatus,
|
||||
getPreExecResult,
|
||||
getPreExecResult: getPreEstimateGasUsed,
|
||||
getDexQuote,
|
||||
getAllQuotes,
|
||||
supportedDEXList,
|
||||
@@ -546,8 +553,9 @@ interface getPreExecResultParams
|
||||
export type QuotePreExecResultInfo = {
|
||||
shouldApproveToken: boolean;
|
||||
shouldTwoStepApprove: boolean;
|
||||
swapPreExecTx: ExplainTxResponse;
|
||||
// swapPreExecTx: ExplainTxResponse;
|
||||
gasPrice: number;
|
||||
gasUsed: number;
|
||||
gasUsd: string;
|
||||
gasUsdValue: string;
|
||||
isSdkPass?: boolean;
|
||||
|
||||
@@ -564,9 +564,11 @@ export const useTokenPair = (userAddress: string) => {
|
||||
return new BigNumber(Number.MIN_SAFE_INTEGER);
|
||||
}
|
||||
const balanceChangeReceiveTokenAmount =
|
||||
quote?.preExecResult.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(token) => isSameAddress(token.id, receiveToken.id)
|
||||
)?.amount || 0;
|
||||
new BigNumber(quote.data?.toTokenAmount || 0)
|
||||
.div(
|
||||
10 ** (quote?.data?.toTokenDecimals || receiveToken.decimals)
|
||||
)
|
||||
.toString() || 0;
|
||||
|
||||
if (sortIncludeGasFee) {
|
||||
return new BigNumber(balanceChangeReceiveTokenAmount)
|
||||
@@ -602,9 +604,13 @@ export const useTokenPair = (userAddress: string) => {
|
||||
halfBetterRate: '',
|
||||
quoteWarning: undefined,
|
||||
actualReceiveAmount:
|
||||
preExecResult?.swapPreExecTx.balance_change.receive_token_list.find(
|
||||
(token) => isSameAddress(token.id, receiveToken.id)
|
||||
)?.amount || '',
|
||||
new BigNumber(bestQuote.data?.toTokenAmount || 0)
|
||||
.div(
|
||||
10 **
|
||||
(bestQuote?.data?.toTokenDecimals ||
|
||||
receiveToken.decimals)
|
||||
)
|
||||
.toString() || '',
|
||||
gasUsd: preExecResult?.gasUsd,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4792,10 +4792,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-action/-/rabby-action-0.1.8.tgz#05b258b628a224d51dc471e93117d4106024a916"
|
||||
integrity sha512-K0euVX55tW2mbnudm3bHPAbbLYlnDQD5PgAvM1mwPPFHhF/Ve/U5MYdg1XERJPxkoXKqiVti46idFFB+s8sv7A==
|
||||
|
||||
"@rabby-wallet/rabby-api@0.9.16":
|
||||
version "0.9.16"
|
||||
resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-api/-/rabby-api-0.9.16.tgz#3be48d9a33acc9347a25f39e3a4ff0d3e1c35d48"
|
||||
integrity sha512-10j6yPXBWPFm/JG/HYBZEmBA9l0p4kJ0/5cS48s3TLCH/W9EAvXr6GH65CmW3J7vvYFJiYxcxhT1dEXYT1e9ww==
|
||||
"@rabby-wallet/rabby-api@0.9.17":
|
||||
version "0.9.17"
|
||||
resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-api/-/rabby-api-0.9.17.tgz#9df9611243c79089c950e99f2d56c78db7625790"
|
||||
integrity sha512-sQbNssTCeR8mqD76dl25/5ZZcbL3BoEvKwlS/Jd+Z5MG7xacpuzv92+Tu/xHj4pyeKEoFyGlBA3qQE7pYcIELA==
|
||||
dependencies:
|
||||
"@rabby-wallet/rabby-sign" "0.4.0"
|
||||
axios "^0.27.2"
|
||||
|
||||
Reference in New Issue
Block a user