From c163db487df1c5befe4090a19ccc3eb2e9d18abb Mon Sep 17 00:00:00 2001 From: Luiz Gomes <8636507+LuizAsFight@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:18:27 -0300 Subject: [PATCH] refactor: transaction estimation flow (#1549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Closes #1546 - Simplified the estimation / approval part of the DAPP Approve Transaction screen - Send transaction part - Simulate transaction before sending, avoiding the user to spend gas in case of fail - Skip estimating dependencies at this point, on this approval step we don’t wanna change the tx anymore - Refact the transaction estimation part: - Save the `initialTxRequest` as the one the dev first informed. All further changes will be always done based on this first tx informed - Do the estimation, fee changes, funding if needed, and generate a `proposedTxRequest` which may have some changes - this `proposedTxRequest` is gonna be the one to go for approval - If the user choose to customize gas or tips, we apply again to the `initialTxRequest` and create a new `proposetTxRequest`, this way we don’t continuously manipulate a transactionRequest. Instead we always have a safe start point - If the user didn't customize the `gasLimit` we automatically increase 20% on it, avoiding OutOfGas errors - Remove all `minGasLimit`, letting the user just inform the gas he wants, if it’s too low the dryRun will fail - For the approve screen to start in “Advanced Mode” - It will be needed that the tip informed in the tx is different then the regularTip and fastTIp we calculated - Also the gas can be different from the one calculated on proposedTxRequest, this will also make the Advanced Mode initiated --- .changeset/gentle-insects-drum.md | 5 + .changeset/tasty-deers-remain.md | 5 + packages/app/src/systems/Core/utils/wallet.ts | 9 +- .../DApp/hooks/useTransactionRequest.tsx | 5 + .../machines/transactionRequestMachine.tsx | 45 ++---- .../TransactionRequest.FormProvider.tsx | 18 --- .../TransactionRequest/TransactionRequest.tsx | 14 +- .../Send/components/SendSelect/SendSelect.tsx | 4 +- .../app/src/systems/Send/hooks/useSend.tsx | 31 +--- .../src/systems/Send/machines/sendMachine.ts | 6 +- .../TxContent/TxContent.stories.tsx | 1 - .../components/TxContent/TxContent.tsx | 34 +++- .../components/TxFeeOptions/TxFeeOptions.tsx | 12 +- .../Transaction/services/transaction.tsx | 149 +++++++++--------- .../src/systems/Transaction/utils/gasLimit.ts | 16 +- 15 files changed, 176 insertions(+), 178 deletions(-) create mode 100644 .changeset/gentle-insects-drum.md create mode 100644 .changeset/tasty-deers-remain.md diff --git a/.changeset/gentle-insects-drum.md b/.changeset/gentle-insects-drum.md new file mode 100644 index 000000000..531e9bcc1 --- /dev/null +++ b/.changeset/gentle-insects-drum.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": minor +--- + +Increase gasLimit by 20% to avoid OutOfGas error diff --git a/.changeset/tasty-deers-remain.md b/.changeset/tasty-deers-remain.md new file mode 100644 index 000000000..a002919f3 --- /dev/null +++ b/.changeset/tasty-deers-remain.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": minor +--- + +Refactor transaction estimation / customization of fees flow diff --git a/packages/app/src/systems/Core/utils/wallet.ts b/packages/app/src/systems/Core/utils/wallet.ts index 843ce9109..1c212b861 100644 --- a/packages/app/src/systems/Core/utils/wallet.ts +++ b/packages/app/src/systems/Core/utils/wallet.ts @@ -42,9 +42,14 @@ export class WalletLockedCustom extends WalletLocked { transactionRequestLike: TransactionRequestLike ): Promise { const transactionRequest = transactionRequestify(transactionRequestLike); - await this.provider.estimateTxDependencies(transactionRequest); const txRequestToSend = await this.populateTransactionWitnessesSignature(transactionRequest); - return this.provider.sendTransaction(txRequestToSend); + + await this.simulateTransaction(txRequestToSend, { + estimateTxDependencies: false, + }); + return this.provider.sendTransaction(txRequestToSend, { + estimateTxDependencies: false, + }); } } diff --git a/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx b/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx index c7c00f350..67c2f3038 100644 --- a/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx +++ b/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx @@ -23,6 +23,9 @@ const selectors = { txSummaryExecuted(state: TransactionRequestState) { return state.context.response?.txSummaryExecuted; }, + proposedTxRequest(state: TransactionRequestState) { + return state.context.response?.proposedTxRequest; + }, isLoadingAccounts(state: TransactionRequestState) { return state.matches('fetchingAccount'); }, @@ -102,6 +105,7 @@ export function useTransactionRequest(opts: UseTransactionRequestOpts = {}) { const title = useSelector(service, selectors.title); const txSummarySimulated = useSelector(service, selectors.txSummarySimulated); const txSummaryExecuted = useSelector(service, selectors.txSummaryExecuted); + const proposedTxRequest = useSelector(service, selectors.proposedTxRequest); const origin = useSelector(service, selectors.origin); const originTitle = useSelector(service, selectors.originTitle); const favIconUrl = useSelector(service, selectors.favIconUrl); @@ -174,6 +178,7 @@ export function useTransactionRequest(opts: UseTransactionRequestOpts = {}) { shouldDisableApproveBtn, shouldShowTxSimulated, shouldShowTxExecuted, + proposedTxRequest, handlers: { request, reset, diff --git a/packages/app/src/systems/DApp/machines/transactionRequestMachine.tsx b/packages/app/src/systems/DApp/machines/transactionRequestMachine.tsx index bc99b3d61..38fdc015a 100644 --- a/packages/app/src/systems/DApp/machines/transactionRequestMachine.tsx +++ b/packages/app/src/systems/DApp/machines/transactionRequestMachine.tsx @@ -42,12 +42,12 @@ type MachineContext = { response?: { txSummarySimulated?: TransactionSummary; txSummaryExecuted?: TransactionSummary; + proposedTxRequest?: TransactionRequest; }; fees: { baseFee?: BN; regularTip?: BN; fastTip?: BN; - minGasLimit?: BN; maxGasLimit?: BN; }; errors?: { @@ -67,9 +67,9 @@ type EstimateGasLimitReturn = { type SimulateTransactionReturn = { baseFee?: BN; - minGasLimit?: BN; txSummary: TransactionSummary; simulateTxErrors?: GroupedErrors; + proposedTxRequest?: TransactionRequest; }; type MachineServices = { @@ -185,7 +185,7 @@ export const transactionRequestMachine = createMachine( onDone: [ { target: 'waitingApproval', - actions: ['assignTxSummarySimulated', 'assignSimulateTxErrors'], + actions: ['assignSimulateResult', 'assignSimulateTxErrors'], }, ], }, @@ -220,7 +220,10 @@ export const transactionRequestMachine = createMachine( invoke: { src: 'send', data: { - input: (ctx: MachineContext) => ctx.input, + input: (ctx: MachineContext) => ({ + ...ctx.input, + transactionRequest: ctx.response?.proposedTxRequest, + }), }, onDone: [ { @@ -318,15 +321,6 @@ export const transactionRequestMachine = createMachine( throw new Error('origin is required'); } - const tip = transactionRequest.tip?.gt(0) - ? transactionRequest.tip - : undefined; - const gasLimit = - 'gasLimit' in transactionRequest && - transactionRequest.gasLimit?.gt(0) - ? transactionRequest.gasLimit - : undefined; - return { transactionRequest, origin, @@ -334,8 +328,6 @@ export const transactionRequestMachine = createMachine( providerUrl, title, favIconUrl, - tip, - gasLimit, skipCustomFee, }; }, @@ -345,7 +337,6 @@ export const transactionRequestMachine = createMachine( baseFee: fees?.baseFee, regularTip: fees?.regularTip, fastTip: fees?.fastTip, - minGasLimit: fees?.minGasLimit, maxGasLimit: fees?.maxGasLimit, }; }, @@ -353,21 +344,11 @@ export const transactionRequestMachine = createMachine( assignCustomFees: assign({ input: (ctx, ev) => { const { tip, gasLimit } = ev.input || {}; - const { transactionRequest } = ctx.input; - - if (!transactionRequest) { - throw new Error('Missing transactionRequest'); - } - - transactionRequest.tip = tip?.gt(0) ? tip : undefined; - - if ('gasLimit' in transactionRequest && gasLimit?.gt(0)) { - transactionRequest.gasLimit = gasLimit; - } return { ...ctx.input, - transactionRequest, + tip, + gasLimit, }; }, }), @@ -377,15 +358,15 @@ export const transactionRequestMachine = createMachine( txSummaryExecuted: ev.data, }), }), - assignTxSummarySimulated: assign({ + assignSimulateResult: assign({ response: (ctx, ev) => ({ ...ctx.response, txSummarySimulated: ev.data.txSummary, + proposedTxRequest: ev.data.proposedTxRequest, }), fees: (ctx, ev) => ({ ...ctx.fees, baseFee: ev.data.baseFee ?? ctx.fees.baseFee, - minGasLimit: ev.data.minGasLimit ?? ctx.fees.minGasLimit, }), }), assignSimulateTxErrors: assign((ctx, ev) => { @@ -445,8 +426,8 @@ export const transactionRequestMachine = createMachine( // screen doesn't flash between states await delay(600); - const txSummary = await TxService.simulateTransaction(input); - return txSummary; + const simulatedInfo = await TxService.simulateTransaction(input); + return simulatedInfo; }, }), send: FetchMachine.create< diff --git a/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.FormProvider.tsx b/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.FormProvider.tsx index 82d9027e4..792099af8 100644 --- a/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.FormProvider.tsx +++ b/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.FormProvider.tsx @@ -20,7 +20,6 @@ export type TransactionRequestFormData = { type SchemaOptions = { baseFee: BN | undefined; - minGasLimit: BN | undefined; maxGasLimit: BN | undefined; }; @@ -48,23 +47,6 @@ const schema = yup gasLimit: yup.object({ amount: yup .mixed() - .test({ - name: 'min', - test: (value, ctx) => { - const { minGasLimit } = ctx.options.context as SchemaOptions; - - if (!minGasLimit || value?.gte(minGasLimit)) { - return true; - } - - return ctx.createError({ - path: 'fees.gasLimit', - message: `Gas limit must be greater than or equal to ${formatGasLimit( - minGasLimit - )}.`, - }); - }, - }) .test({ name: 'max', test: (value, ctx) => { diff --git a/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.tsx b/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.tsx index 77f413561..587632851 100644 --- a/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.tsx +++ b/packages/app/src/systems/DApp/pages/TransactionRequest/TransactionRequest.tsx @@ -1,10 +1,11 @@ import { cssObj } from '@fuel-ui/css'; import { Button } from '@fuel-ui/react'; +import { bn } from 'fuels'; import { useMemo } from 'react'; import { useAssets } from '~/systems/Asset'; import { Layout } from '~/systems/Core'; import { TopBarType } from '~/systems/Core/components/Layout/TopBar'; -import { TxContent } from '~/systems/Transaction'; +import { TxContent, getGasLimitFromTxRequest } from '~/systems/Transaction'; import { formatTip } from '~/systems/Transaction/components/TxFeeOptions/TxFeeOptions.utils'; import { useTransactionRequest } from '../../hooks/useTransactionRequest'; import { AutoSubmit } from './TransactionRequest.AutoSubmit'; @@ -29,14 +30,15 @@ export function TransactionRequest() { shouldDisableApproveBtn, errors, executedStatus, + proposedTxRequest, } = txRequest; const { isLoading: isLoadingAssets } = useAssets(); const defaultValues = useMemo(() => { - if (!txSummarySimulated) return undefined; + if (!txSummarySimulated || !proposedTxRequest) return undefined; - const tip = txSummarySimulated.tip; - const gasLimit = txSummarySimulated.gasUsed; + const tip = bn(proposedTxRequest.tip); + const gasLimit = getGasLimitFromTxRequest(proposedTxRequest); return { fees: { @@ -50,7 +52,7 @@ export function TransactionRequest() { }, }, }; - }, [txSummarySimulated]); + }, [txSummarySimulated, proposedTxRequest]); const isLoadingInfo = useMemo(() => { return status('loading') || status('sending') || isLoadingAssets; @@ -74,7 +76,6 @@ export function TransactionRequest() { testId={txRequest.txStatus} context={{ baseFee: fees.baseFee, - minGasLimit: fees.minGasLimit, maxGasLimit: fees.maxGasLimit, }} > @@ -87,6 +88,7 @@ export function TransactionRequest() { diff --git a/packages/app/src/systems/Send/hooks/useSend.tsx b/packages/app/src/systems/Send/hooks/useSend.tsx index abae527db..6d82b9417 100644 --- a/packages/app/src/systems/Send/hooks/useSend.tsx +++ b/packages/app/src/systems/Send/hooks/useSend.tsx @@ -24,8 +24,8 @@ export enum SendStatus { } const selectors = { - minGasLimit(state: SendMachineState) { - return state.context.minGasLimit; + gasLimit(state: SendMachineState) { + return state.context.gasLimit; }, maxGasLimit(state: SendMachineState) { return state.context.maxGasLimit; @@ -73,7 +73,7 @@ type BalanceAsset = { type SchemaOptions = { balances: BalanceAsset[]; baseFee: BN | undefined; - minGasLimit: BN | undefined; + gasLimit: BN | undefined; maxGasLimit: BN | undefined; }; @@ -160,23 +160,6 @@ const schema = yup gasLimit: yup.object({ amount: yup .mixed() - .test({ - name: 'min', - test: (value, ctx) => { - const { minGasLimit } = ctx.options.context as SchemaOptions; - - if (!minGasLimit || value?.gte(minGasLimit)) { - return true; - } - - return ctx.createError({ - path: 'fees.gasLimit', - message: `Gas limit must be greater than or equal to ${formatGasLimit( - minGasLimit - )}.`, - }); - }, - }) .test({ name: 'max', test: (value, ctx) => { @@ -254,7 +237,6 @@ export function useSend() { baseFee, regularTip, fastTip, - minGasLimit, maxGasLimit, } = ctx; if (!providerUrl || !transactionRequest || !address) { @@ -269,7 +251,6 @@ export function useSend() { baseFee, regularTip, fastTip, - minGasLimit, maxGasLimit, }, skipCustomFee: true, @@ -280,7 +261,7 @@ export function useSend() { ); const baseFee = useSelector(service, selectors.baseFee); - const minGasLimit = useSelector(service, selectors.minGasLimit); + const gasLimit = useSelector(service, selectors.gasLimit); const maxGasLimit = useSelector(service, selectors.maxGasLimit); const errorMessage = useSelector(service, selectors.error); @@ -291,7 +272,7 @@ export function useSend() { context: { balances: account?.balances, baseFee, - minGasLimit, + gasLimit, maxGasLimit, }, }); @@ -376,7 +357,7 @@ export function useSend() { return { form, baseFee, - minGasLimit, + gasLimit, tip, regularTip, fastTip, diff --git a/packages/app/src/systems/Send/machines/sendMachine.ts b/packages/app/src/systems/Send/machines/sendMachine.ts index d55d1892f..7da036b12 100644 --- a/packages/app/src/systems/Send/machines/sendMachine.ts +++ b/packages/app/src/systems/Send/machines/sendMachine.ts @@ -22,7 +22,7 @@ export type MachineContext = { baseFee?: BN; regularTip?: BN; fastTip?: BN; - minGasLimit?: BN; + gasLimit?: BN; maxGasLimit?: BN; input?: TxInputs['createTransfer']; error?: string; @@ -39,7 +39,7 @@ type EstimateGasLimitReturn = { type CreateTransactionReturn = { baseFee?: BN; - minGasLimit?: BN; + gasLimit?: BN; transactionRequest?: TransactionRequest; providerUrl: string; address: string; @@ -182,7 +182,7 @@ export const sendMachine = createMachine( providerUrl: ev.data.providerUrl, address: ev.data.address, baseFee: ev.data.baseFee ?? ctx.baseFee, - minGasLimit: ev.data.minGasLimit ?? ctx.minGasLimit, + gasLimit: ev.data.gasLimit ?? ctx.gasLimit, error: undefined, })), }, diff --git a/packages/app/src/systems/Transaction/components/TxContent/TxContent.stories.tsx b/packages/app/src/systems/Transaction/components/TxContent/TxContent.stories.tsx index d6c971528..1388cefdc 100644 --- a/packages/app/src/systems/Transaction/components/TxContent/TxContent.stories.tsx +++ b/packages/app/src/systems/Transaction/components/TxContent/TxContent.stories.tsx @@ -26,7 +26,6 @@ const defaultArgs = { txStatus: TransactionStatus.success, fees: { baseFee: bn(0.01), - minGasLimit: bn(0.01), regularTip: bn(0.01), fastTip: bn(0.01), }, diff --git a/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx b/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx index a957d8ff7..c4ecc7dfe 100644 --- a/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx +++ b/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx @@ -9,7 +9,12 @@ import { Text, VStack, } from '@fuel-ui/react'; -import type { BN, TransactionStatus, TransactionSummary } from 'fuels'; +import type { + BN, + TransactionRequest, + TransactionStatus, + TransactionSummary, +} from 'fuels'; import { type ReactNode, useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import type { Maybe } from '~/systems/Core'; @@ -20,6 +25,7 @@ import { TxFee, TxHeader, TxOperations, + getGasLimitFromTxRequest, } from '~/systems/Transaction'; import { TxFeeOptions } from '../TxFeeOptions/TxFeeOptions'; @@ -87,10 +93,10 @@ export type TxContentInfoProps = { errors?: GroupedErrors; fees?: { baseFee?: BN; - minGasLimit?: BN; regularTip?: BN; fastTip?: BN; }; + txRequest?: TransactionRequest; }; function TxContentInfo({ @@ -102,21 +108,33 @@ function TxContentInfo({ isConfirm, errors, fees, + txRequest, }: TxContentInfoProps) { const { getValues } = useFormContext(); const status = txStatus || tx?.status || txStatus; const hasErrors = Boolean(Object.keys(errors || {}).length); const isExecuted = !!tx?.id; + const txRequestGasLimit = getGasLimitFromTxRequest(txRequest); const initialAdvanced = useMemo(() => { - if (!fees?.regularTip || !fees?.minGasLimit) return false; + if (!fees?.regularTip || !fees?.fastTip) return false; + + // it will start as advanced if the transaction tip is not equal to the regular tip and fast tip + const isFeeAmountTheRegularTip = getValues('fees.tip.amount').eq( + fees.regularTip + ); + const isFeeAmountTheFastTip = getValues('fees.tip.amount').eq(fees.fastTip); + // it will start as advanced if the gasLimit if different from the tx gasLimit + const isGasLimitTheTxRequestGasLimit = getValues('fees.gasLimit.amount').eq( + txRequestGasLimit + ); return ( - !getValues('fees.tip.amount').eq(fees.regularTip) || - !getValues('fees.gasLimit.amount').eq(fees.minGasLimit) + (!isFeeAmountTheRegularTip && !isFeeAmountTheFastTip) || + !isGasLimitTheTxRequestGasLimit ); - }, [getValues, fees]); + }, [getValues, fees, txRequestGasLimit]); function getHeader() { if (hasErrors) return ; @@ -141,7 +159,7 @@ function TxContentInfo({ {showDetails && !fees && } {showDetails && fees?.baseFee && - fees?.minGasLimit && + txRequestGasLimit && fees?.regularTip && fees?.fastTip && ( @@ -149,7 +167,7 @@ function TxContentInfo({ diff --git a/packages/app/src/systems/Transaction/components/TxFeeOptions/TxFeeOptions.tsx b/packages/app/src/systems/Transaction/components/TxFeeOptions/TxFeeOptions.tsx index 48b8e9ddb..fda45e087 100644 --- a/packages/app/src/systems/Transaction/components/TxFeeOptions/TxFeeOptions.tsx +++ b/packages/app/src/systems/Transaction/components/TxFeeOptions/TxFeeOptions.tsx @@ -17,7 +17,7 @@ import { type TxFeeOptionsProps = { initialAdvanced: boolean; baseFee: BN; - minGasLimit: BN; + gasLimit: BN; regularTip: BN; fastTip: BN; }; @@ -25,13 +25,13 @@ type TxFeeOptionsProps = { export const TxFeeOptions = ({ initialAdvanced, baseFee, - minGasLimit, + gasLimit: gasLimitInput, regularTip, fastTip, }: TxFeeOptionsProps) => { const { control, setValue, getValues } = useFormContext(); const [isAdvanced, setIsAdvanced] = useState(initialAdvanced); - const previousMinGasLimit = useRef(minGasLimit); + const previousGasLimit = useRef(gasLimitInput); const previousDefaultTip = useRef(regularTip); const { field: tip, fieldState: tipState } = useController({ @@ -65,10 +65,10 @@ export const TxFeeOptions = ({ 'fees.gasLimit.amount', ]); - if (!currentGasLimit.eq(previousMinGasLimit.current)) { + if (!currentGasLimit.eq(previousGasLimit.current)) { setValue('fees.gasLimit', { - amount: previousMinGasLimit.current, - text: previousMinGasLimit.current.toString(), + amount: previousGasLimit.current, + text: previousGasLimit.current.toString(), }); } diff --git a/packages/app/src/systems/Transaction/services/transaction.tsx b/packages/app/src/systems/Transaction/services/transaction.tsx index 0e32b21e5..12088a9f9 100644 --- a/packages/app/src/systems/Transaction/services/transaction.tsx +++ b/packages/app/src/systems/Transaction/services/transaction.tsx @@ -13,12 +13,14 @@ import { FuelError, TransactionResponse, TransactionStatus, + TransactionType, assembleTransactionSummary, bn, getTransactionSummary, getTransactionSummaryFromRequest, getTransactionsSummaries, normalizeJSON, + transactionRequestify, } from 'fuels'; import { WalletLockedCustom, db, uniqueId } from '~/systems/Core'; @@ -26,7 +28,13 @@ import { createProvider } from '@fuel-wallet/connections'; import { AccountService } from '~/systems/Account/services/account'; import { NetworkService } from '~/systems/Network/services/network'; import type { Transaction } from '../types'; -import { type GroupedErrors, getAbiMap, getErrorMessage } from '../utils'; +import { + type GroupedErrors, + getAbiMap, + getErrorMessage, + getGasLimitFromTxRequest, + setGasLimitToTxRequest, +} from '../utils'; import { getCurrentTips } from '../utils/fee'; export type TxInputs = { @@ -49,7 +57,6 @@ export type TxInputs = { baseFee?: BN; regularTip?: BN; fastTip?: BN; - minGasLimit?: BN; maxGasLimit?: BN; }; }; @@ -62,6 +69,8 @@ export type TxInputs = { transactionRequest: TransactionRequest; providerUrl?: string; skipCustomFee?: boolean; + tip?: BN; + gasLimit?: BN; }; setCustomFees: { tip?: BN; @@ -169,15 +178,17 @@ export class TxService { static async simulateTransaction({ skipCustomFee, - transactionRequest, + transactionRequest: inputTransactionRequest, providerUrl, + tip: inputCustomTip, + gasLimit: inputCustomGasLimit, }: TxInputs['simulateTransaction']) { const [provider, account] = await Promise.all([ createProvider(providerUrl || ''), AccountService.getCurrentAccount(), ]); - if (!transactionRequest) { + if (!inputTransactionRequest) { throw new Error('Missing transaction request'); } if (!account) { @@ -185,49 +196,75 @@ export class TxService { } const wallet = new WalletLockedCustom(account.address, provider); + const initialMaxFee = inputTransactionRequest.maxFee; + const initialGasLimit = getGasLimitFromTxRequest(inputTransactionRequest); try { - const txRequestCustomFee = clone(transactionRequest); - const customFee = !skipCustomFee - ? await TxService.computeCustomFee({ - wallet, - transactionRequest: txRequestCustomFee, - }) - : undefined; - - const transaction = transactionRequest.toTransaction(); + /* + we'll work always based on the first inputted transactioRequest, then cloning it and manipulating + then outputting a proposedTxRequest, which will be the one to go for approval + */ + const proposedTxRequest = clone(inputTransactionRequest); + if (!skipCustomFee) { + // if the user has inputted a custom tip, we set it to the proposedTxRequest + if (inputCustomTip) { + proposedTxRequest.tip = inputCustomTip; + } + // if the user has inputted a custom gas Limit, we set it to the proposedTxRequest + if (inputCustomGasLimit) { + setGasLimitToTxRequest(proposedTxRequest, inputCustomGasLimit); + } else { + // if the user has not inputted a custom gas Limit, we increase the original one in 20% to avoid OutOfGas errors + setGasLimitToTxRequest( + proposedTxRequest, + initialGasLimit.mul(12).div(10) + ); + } + const { maxFee } = await provider.estimateTxGasAndFee({ + transactionRequest: proposedTxRequest, + }); + + // if the maxFee is greater than the initial maxFee, we set it to the new maxFee, and refund the transaction + if (maxFee.gt(initialMaxFee)) { + proposedTxRequest.maxFee = maxFee; + const txCost = await wallet.getTransactionCost(proposedTxRequest, { + estimateTxDependencies: true, + }); + await wallet.fund(proposedTxRequest, { + estimatedPredicates: txCost.estimatedPredicates, + addedSignatures: txCost.addedSignatures, + gasPrice: txCost.gasPrice, + updateMaxFee: txCost.updateMaxFee, + requiredQuantities: [], + }); + } + } + + const transaction = proposedTxRequest.toTransaction(); const abiMap = await getAbiMap({ inputs: transaction.inputs, }); - let txSummary: TransactionSummary; + const txSummary = await getTransactionSummaryFromRequest({ + provider, + transactionRequest: proposedTxRequest, + abiMap, + }); - try { - txSummary = await getTransactionSummaryFromRequest({ - provider, - transactionRequest: txRequestCustomFee, - abiMap, - }); - transactionRequest = txRequestCustomFee; - } catch (_) { - txSummary = await getTransactionSummaryFromRequest({ - provider, - transactionRequest, - abiMap, - }); - } + const baseFee = proposedTxRequest.maxFee.sub( + proposedTxRequest.tip ?? bn(0) + ); // Adding 1 magical unit to match the fake unit that is added on TS SDK (.add(1)) const feeAdaptedToSdkDiff = txSummary.fee.add(1); return { - baseFee: customFee?.baseFee, - minGasLimit: customFee?.gasUsed, + baseFee, txSummary: { ...txSummary, fee: feeAdaptedToSdkDiff, - gasUsed: txSummary.gasUsed, }, + proposedTxRequest, }; } catch (e) { const { gasPerByte, gasPriceFactor, gasCosts, maxGasPerTx } = @@ -235,8 +272,8 @@ export class TxService { const consensusParameters = provider.getChain().consensusParameters; const { maxInputs } = consensusParameters.txParameters; - const transaction = transactionRequest.toTransaction(); - const transactionBytes = transactionRequest.toTransactionBytes(); + const transaction = inputTransactionRequest.toTransaction(); + const transactionBytes = inputTransactionRequest.toTransactionBytes(); const abiMap = await getAbiMap({ inputs: transaction.inputs, @@ -265,15 +302,15 @@ export class TxService { txSummary.status = TransactionStatus.failure; // Fallback to the values from the transactionRequest - if ('gasLimit' in transactionRequest) { - txSummary.gasUsed = transactionRequest.gasLimit; + if ('gasLimit' in inputTransactionRequest) { + txSummary.gasUsed = inputTransactionRequest.gasLimit; } return { baseFee: txSummary.fee.add(1), - minGasLimit: txSummary.gasUsed, txSummary, simulateTxErrors, + proposedTxRequest: inputTransactionRequest, }; } } @@ -362,16 +399,13 @@ export class TxService { } ); - // Getting updated maxFee and costs - const txCost = await wallet.getTransactionCost(transactionRequest); - const baseFee = transactionRequest.maxFee.sub( transactionRequest.tip ?? bn(0) ); return { baseFee, - minGasLimit: txCost.gasUsed, + gasLimit: getGasLimitFromTxRequest(transactionRequest), transactionRequest, address: account.address, providerUrl: network.url, @@ -391,45 +425,12 @@ export class TxService { return { baseFee: undefined, - minGasLimit: undefined, + gasLimit: undefined, transactionRequest: undefined, address: account.address, providerUrl: network.url, }; } - - private static async computeCustomFee({ - wallet, - transactionRequest, - }: TxInputs['computeCustomFee']) { - try { - const txCost = await wallet.getTransactionCost(transactionRequest, { - estimateTxDependencies: true, - }); - - // add 10% to have some buffer as gasPrice may vary - const newTxCost = txCost.maxFee.add(txCost.maxFee.div(10)); - // only apply if it's bigger than the current maxFee - if (newTxCost.gt(txCost.maxFee)) { - transactionRequest.maxFee = newTxCost; - } - - // funding the transaction with the required quantities (the maxFee might have changed) - await wallet.fund(transactionRequest, { - estimatedPredicates: txCost.estimatedPredicates, - addedSignatures: txCost.addedSignatures, - gasPrice: txCost.gasPrice, - updateMaxFee: txCost.updateMaxFee, - requiredQuantities: [], - }); - - const baseFee = transactionRequest.maxFee.sub( - transactionRequest.tip ?? bn(0) - ); - - return { baseFee, gasUsed: txCost.gasUsed }; - } catch (_) {} - } } export function getAssetAccountBalance(account: Account, assetId: string) { diff --git a/packages/app/src/systems/Transaction/utils/gasLimit.ts b/packages/app/src/systems/Transaction/utils/gasLimit.ts index 55417e8c3..ac8908f5f 100644 --- a/packages/app/src/systems/Transaction/utils/gasLimit.ts +++ b/packages/app/src/systems/Transaction/utils/gasLimit.ts @@ -1,4 +1,4 @@ -import type { BN } from 'fuels'; +import { type BN, type TransactionRequest, bn } from 'fuels'; export const formatGasLimit = (value: BN) => { const hex = value.toHex(); @@ -6,3 +6,17 @@ export const formatGasLimit = (value: BN) => { return gasLimit.toLocaleString('en-US'); }; + +export const getGasLimitFromTxRequest = (txRequest?: TransactionRequest) => { + if (!txRequest || !('gasLimit' in txRequest)) return bn(0); + + return txRequest.gasLimit; +}; +export const setGasLimitToTxRequest = ( + txRequest?: TransactionRequest, + gasLimit?: BN +) => { + if (!txRequest || !('gasLimit' in txRequest) || !gasLimit) return undefined; + + txRequest.gasLimit = gasLimit; +};