feat: estimate gas (#2871)

* feat: estimate gas

* fix

* fix: gas price

* chore: @rabby-wallet/rabby-api@0.9.17
This commit is contained in:
DMY
2025-03-28 12:04:37 +08:00
committed by GitHub
parent 3e20b55467
commit b9528d2e65
7 changed files with 136 additions and 126 deletions

View File

@@ -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",

View File

@@ -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',

View File

@@ -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>

View File

@@ -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}

View File

@@ -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;

View File

@@ -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,
}
);

View File

@@ -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"