Skip to content

Commit

Permalink
refactor: transaction estimation flow (#1549)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
LuizAsFight authored Oct 5, 2024
1 parent 6dd69ce commit c163db4
Showing 15 changed files with 176 additions and 178 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-insects-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": minor
---

Increase gasLimit by 20% to avoid OutOfGas error
5 changes: 5 additions & 0 deletions .changeset/tasty-deers-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": minor
---

Refactor transaction estimation / customization of fees flow
9 changes: 7 additions & 2 deletions packages/app/src/systems/Core/utils/wallet.ts
Original file line number Diff line number Diff line change
@@ -42,9 +42,14 @@ export class WalletLockedCustom extends WalletLocked {
transactionRequestLike: TransactionRequestLike
): Promise<TransactionResponse> {
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,
});
}
}
5 changes: 5 additions & 0 deletions packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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,24 +321,13 @@ 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,
address,
providerUrl,
title,
favIconUrl,
tip,
gasLimit,
skipCustomFee,
};
},
@@ -345,29 +337,18 @@ export const transactionRequestMachine = createMachine(
baseFee: fees?.baseFee,
regularTip: fees?.regularTip,
fastTip: fees?.fastTip,
minGasLimit: fees?.minGasLimit,
maxGasLimit: fees?.maxGasLimit,
};
},
}),
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<
Original file line number Diff line number Diff line change
@@ -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<BN>()
.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) => {
Original file line number Diff line number Diff line change
@@ -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<TransactionRequestFormData | undefined>(() => {
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<boolean>(() => {
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() {
<TxContent.Info
showDetails
tx={txSummarySimulated}
txRequest={proposedTxRequest}
isLoading={isLoadingInfo}
errors={errors.simulateTxErrors}
isConfirm
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ export function SendSelect({
balances,
balanceAssetSelected,
baseFee = bn(0),
minGasLimit = bn(0),
gasLimit = bn(0),
tip,
regularTip,
fastTip,
@@ -171,7 +171,7 @@ export function SendSelect({
<TxFeeOptions
initialAdvanced={false}
baseFee={baseFee}
minGasLimit={minGasLimit}
gasLimit={gasLimit}
regularTip={regularTip}
fastTip={fastTip}
/>
31 changes: 6 additions & 25 deletions packages/app/src/systems/Send/hooks/useSend.tsx
Original file line number Diff line number Diff line change
@@ -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<BN>()
.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,
Loading

0 comments on commit c163db4

Please sign in to comment.