diff --git a/src/entries/popup/components/TransactionFee/TransactionFee.tsx b/src/entries/popup/components/TransactionFee/TransactionFee.tsx index 0ce4707751..4b69f519c5 100644 --- a/src/entries/popup/components/TransactionFee/TransactionFee.tsx +++ b/src/entries/popup/components/TransactionFee/TransactionFee.tsx @@ -286,7 +286,7 @@ type TransactionFeeProps = { disableShortcuts?: boolean; address?: Address; defaultSpeed?: GasSpeed; - transactionRequest: TransactionRequest; + transactionRequests: TransactionRequest[]; accentColor?: string; plainTriggerBorder?: boolean; analyticsEvents?: { @@ -294,6 +294,8 @@ type TransactionFeeProps = { transactionSpeedSwitched: keyof EventProperties; transactionSpeedClicked: keyof EventProperties; }; + feeLabel?: string; + feeInfoButton?: { onClick: () => void }; }; export function TransactionFee({ @@ -301,10 +303,12 @@ export function TransactionFee({ disableShortcuts, address, defaultSpeed, - transactionRequest, + transactionRequests, accentColor, plainTriggerBorder, analyticsEvents, + feeLabel, + feeInfoButton, }: TransactionFeeProps) { const { defaultTxSpeed } = useDefaultTxSpeed({ chainId }); const { @@ -322,7 +326,7 @@ export function TransactionFee({ chainId, address, defaultSpeed: defaultSpeed || defaultTxSpeed, - transactionRequest, + transactionRequests, }); return ( @@ -342,6 +346,8 @@ export function TransactionFee({ currentBaseFee={currentBaseFee} baseFeeTrend={baseFeeTrend} feeType={feeType} + feeLabel={feeLabel} + feeInfoButton={feeInfoButton} /> ); } diff --git a/src/entries/popup/hooks/useGas.ts b/src/entries/popup/hooks/useGas.ts index dd07b1b807..3da4572fd9 100644 --- a/src/entries/popup/hooks/useGas.ts +++ b/src/entries/popup/hooks/useGas.ts @@ -10,6 +10,7 @@ import { isLegacyMeteorologyFeeData } from '~/core/resources/gas/classification' import { useEstimateApprovalGasLimit } from '~/core/resources/gas/estimateApprovalGasLimit'; import { useEstimateSwapGasLimit } from '~/core/resources/gas/estimateSwapGasLimit'; import { useOptimismL1SecurityFee } from '~/core/resources/gas/optimismL1SecurityFee'; +import { estimateTransactionsGasLimit } from '~/core/resources/transactions/simulation'; import { useCurrentCurrencyStore, useGasStore } from '~/core/state'; import { useNetworkStore } from '~/core/state/networks/networks'; import { ParsedAsset, ParsedSearchAsset } from '~/core/types/assets'; @@ -365,27 +366,72 @@ const useGas = ({ }; }; +const toSimulationTransaction = ( + tx: TransactionRequest, +): { from: string; to: string; value: string; data: string } => ({ + from: tx.from?.toString() ?? '', + to: tx.to?.toString() ?? '', + value: tx.value?.toString() ?? '0', + data: tx.data?.toString() ?? '0x', +}); + +/** + * Gas estimation for one or more transactions. + * Single tx: uses provider estimateGas. Batch: sums gas from simulation API. + */ export const useTransactionGas = ({ chainId, address, defaultSpeed, - transactionRequest, + transactionRequests, }: { chainId: ChainId; address?: Address; defaultSpeed?: GasSpeed; - transactionRequest: TransactionRequest; + transactionRequests: TransactionRequest[]; }) => { - const { data: estimatedGasLimit } = useEstimateGasLimit({ - chainId, - transactionRequest: useDebounce(transactionRequest, 500), + const debouncedRequests = useDebounce(transactionRequests, 500); + const isSingle = (debouncedRequests?.length ?? 0) <= 1; + + const { data: singleGasLimit } = useEstimateGasLimit( + { + chainId, + transactionRequest: debouncedRequests?.[0] ?? {}, + }, + { enabled: !!chainId && isSingle && !!debouncedRequests?.length }, + ); + + const { data: batchGasLimit } = useQuery({ + queryKey: [ + 'estimateBatchGasLimit', + chainId, + address, + debouncedRequests + ?.map( + (r) => + `${r.from ?? ''}-${r.to}-${r.value?.toString() ?? '0'}-${r.data}`, + ) + .join(','), + ], + queryFn: async () => { + if (!debouncedRequests?.length || isSingle) return undefined; + const steps = debouncedRequests.map((tx, i) => ({ + transaction: toSimulationTransaction(tx), + label: `Call ${i + 1}`, + })); + return estimateTransactionsGasLimit({ chainId, steps }); + }, + enabled: !!chainId && !isSingle && !!debouncedRequests?.length, }); + + const estimatedGasLimit = isSingle ? singleGasLimit : batchGasLimit; + return useGas({ chainId, address, defaultSpeed, - estimatedGasLimit, - transactionRequest, + estimatedGasLimit: estimatedGasLimit ?? undefined, + transactionRequest: debouncedRequests?.[0] ?? null, enabled: true, }); }; diff --git a/src/entries/popup/pages/home/Delegations/RevokeDelegationPage.tsx b/src/entries/popup/pages/home/Delegations/RevokeDelegationPage.tsx index dd585f42c9..1c8ac78a8f 100644 --- a/src/entries/popup/pages/home/Delegations/RevokeDelegationPage.tsx +++ b/src/entries/popup/pages/home/Delegations/RevokeDelegationPage.tsx @@ -634,13 +634,15 @@ export const RevokeDelegationPage = () => { diff --git a/src/entries/popup/pages/messages/SendTransaction/index.tsx b/src/entries/popup/pages/messages/SendTransaction/index.tsx index 4700a202fa..8f6af93cb5 100644 --- a/src/entries/popup/pages/messages/SendTransaction/index.tsx +++ b/src/entries/popup/pages/messages/SendTransaction/index.tsx @@ -315,7 +315,11 @@ export function SendTransaction({ }} chainId={activeSession?.chainId || ChainId.mainnet} address={activeSession?.address} - transactionRequest={request?.params?.[0] as TransactionRequest} + transactionRequests={ + request?.params?.[0] + ? [request.params[0] as TransactionRequest] + : [] + } plainTriggerBorder /> diff --git a/src/entries/popup/pages/speedUpAndCancelSheet/index.tsx b/src/entries/popup/pages/speedUpAndCancelSheet/index.tsx index 288c39ac94..9a9ebd5b08 100644 --- a/src/entries/popup/pages/speedUpAndCancelSheet/index.tsx +++ b/src/entries/popup/pages/speedUpAndCancelSheet/index.tsx @@ -393,7 +393,9 @@ export function SpeedUpAndCancelSheet({ )}