diff --git a/.changeset/chilly-planets-hide.md b/.changeset/chilly-planets-hide.md
new file mode 100644
index 0000000000..02dc32d29f
--- /dev/null
+++ b/.changeset/chilly-planets-hide.md
@@ -0,0 +1,5 @@
+---
+"fuels-wallet": minor
+---
+
+Improve how error messages are displayed/parsed during fee estimation.
diff --git a/.changeset/giant-horses-play.md b/.changeset/giant-horses-play.md
new file mode 100644
index 0000000000..954664ce29
--- /dev/null
+++ b/.changeset/giant-horses-play.md
@@ -0,0 +1,5 @@
+---
+"fuels-wallet": minor
+---
+
+Display fees options even when there are tx simulation errors.
diff --git a/.changeset/swift-bugs-film.md b/.changeset/swift-bugs-film.md
new file mode 100644
index 0000000000..308202d8d9
--- /dev/null
+++ b/.changeset/swift-bugs-film.md
@@ -0,0 +1,5 @@
+---
+"fuels-wallet": patch
+---
+
+Allow dApps to pass account owner with `0x` address.
diff --git a/examples/cra-dapp/src/Connected.tsx b/examples/cra-dapp/src/Connected.tsx
index bce4333b9a..2833ea738c 100644
--- a/examples/cra-dapp/src/Connected.tsx
+++ b/examples/cra-dapp/src/Connected.tsx
@@ -11,9 +11,12 @@ import {
} from '@fuels/react';
import { DEVNET_NETWORK_URL, TESTNET_NETWORK_URL, bn } from 'fuels';
+import { useState } from 'react';
import './App.css';
export function Connected() {
+ const [loading, setLoading] = useState(false);
+
const { fuel } = useFuel();
const { disconnect } = useDisconnect();
const { wallet } = useWallet();
@@ -60,6 +63,7 @@ export function Connected() {
diff --git a/packages/app/src/systems/CRX/background/services/BackgroundService.ts b/packages/app/src/systems/CRX/background/services/BackgroundService.ts
index 827ba56cb3..16018bb890 100644
--- a/packages/app/src/systems/CRX/background/services/BackgroundService.ts
+++ b/packages/app/src/systems/CRX/background/services/BackgroundService.ts
@@ -43,6 +43,7 @@ export class BackgroundService {
'accounts',
'connect',
'network',
+ 'networks',
'disconnect',
'signMessage',
'sendTransaction',
@@ -302,13 +303,22 @@ export class BackgroundService {
);
}
+ const { address, provider, transaction } = input;
+
const popupService = await PopUpService.open(
origin,
Pages.requestTransaction(),
this.communicationProtocol
);
+
+ // We need to forward bech32 addresses to the popup, regardless if we receive a b256 here
+ // our database is storing fuel addresses
+ const bech32Address = Address.fromDynamicInput(address).toString();
+
const signedMessage = await popupService.sendTransaction({
- ...input,
+ address: bech32Address,
+ provider,
+ transaction,
origin,
title,
favIconUrl,
diff --git a/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx b/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx
index e99a8d839e..c7c00f3504 100644
--- a/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx
+++ b/packages/app/src/systems/DApp/hooks/useTransactionRequest.tsx
@@ -29,9 +29,7 @@ const selectors = {
errors(state: TransactionRequestState) {
if (!state.context.errors) return {};
const simulateTxErrors = state.context.errors?.simulateTxErrors;
- const hasSimulateTxErrors = Boolean(
- Object.keys(simulateTxErrors || {}).length
- );
+ const hasSimulateTxErrors = Boolean(simulateTxErrors);
const txApproveError = state.context.errors?.txApproveError;
return { txApproveError, simulateTxErrors, hasSimulateTxErrors };
},
diff --git a/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx b/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx
index 46b9f563e8..61a54f6456 100644
--- a/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx
+++ b/packages/app/src/systems/Transaction/components/TxContent/TxContent.tsx
@@ -23,45 +23,15 @@ import {
import { TxFeeOptions } from '../TxFeeOptions/TxFeeOptions';
const ErrorHeader = ({ errors }: { errors?: GroupedErrors }) => {
- const errorMessages = useMemo(() => {
- const messages = [];
- if (errors) {
- if (errors.InsufficientInputAmount || errors.NotEnoughCoins) {
- messages.push('Not enough funds');
- }
-
- // biome-ignore lint: will not be a large array
- Object.keys(errors).forEach((key: string) => {
- if (key === 'InsufficientInputAmount' || key === 'NotEnoughCoins') {
- return;
- }
-
- let errorMessage = `${key}: `;
- try {
- errorMessage += JSON.stringify(errors[key]);
- } catch (_) {
- errorMessage += errors[key];
- }
- messages.push(errorMessage);
- });
- }
-
- return messages;
- }, [errors]);
-
return (
-
- {errorMessages.map((message) => (
-
- {message}
-
- ))}
+
+ {errors}
);
diff --git a/packages/app/src/systems/Transaction/components/TxOperations/TxOperations.tsx b/packages/app/src/systems/Transaction/components/TxOperations/TxOperations.tsx
index 4938b68120..aea0c9dd90 100644
--- a/packages/app/src/systems/Transaction/components/TxOperations/TxOperations.tsx
+++ b/packages/app/src/systems/Transaction/components/TxOperations/TxOperations.tsx
@@ -1,4 +1,4 @@
-import { Box } from '@fuel-ui/react';
+import { Alert, Box } from '@fuel-ui/react';
import type { AssetData } from '@fuel-wallet/types';
import type { Operation, TransactionStatus } from 'fuels';
import type { Maybe } from '~/systems/Core';
@@ -18,6 +18,16 @@ export function TxOperations({
assets,
isLoading,
}: TxOperationsProps) {
+ if (operations?.length === 0) {
+ return (
+
+
+ No operations found in this transaction
+
+
+ );
+ }
+
return (
{operations?.map((operation, index) => (
diff --git a/packages/app/src/systems/Transaction/pages/TxApprove/TxApprove.test.tsx b/packages/app/src/systems/Transaction/pages/TxApprove/TxApprove.test.tsx
index 26c2f2474e..2ed27e4427 100644
--- a/packages/app/src/systems/Transaction/pages/TxApprove/TxApprove.test.tsx
+++ b/packages/app/src/systems/Transaction/pages/TxApprove/TxApprove.test.tsx
@@ -75,7 +75,7 @@ describe('TxApprove', () => {
shouldShowTxSimulated: true,
shouldShowTxExecuted: false,
shouldShowActions: true,
- simulateTxErrors: mockTxResult,
+ simulateTxErrors: 'Unknown error',
txSummarySimulated: mockTxResult,
approveStatus: jest.fn().mockReturnValue(TransactionStatus.success),
handlers: {
@@ -168,15 +168,13 @@ describe('TxApprove', () => {
setup(
{
errors: {
- simulateTxErrors: {
- InsufficientInputAmount: true,
- },
+ simulateTxErrors: 'Insufficient Input Amount',
},
},
{},
{ status: TxRequestStatus.failed, result: true }
);
- expect(screen.getByText('Not enough funds')).toBeDefined();
+ expect(screen.getByText('Insufficient Input Amount')).toBeDefined();
});
it('does not show the approve button show actions is false', () => {
diff --git a/packages/app/src/systems/Transaction/services/transaction.tsx b/packages/app/src/systems/Transaction/services/transaction.tsx
index f6943dc605..95e0521fe1 100644
--- a/packages/app/src/systems/Transaction/services/transaction.tsx
+++ b/packages/app/src/systems/Transaction/services/transaction.tsx
@@ -4,6 +4,8 @@ import type { TransactionRequest, WalletLocked } from 'fuels';
import {
Address,
type BN,
+ ErrorCode,
+ FuelError,
TransactionResponse,
TransactionStatus,
assembleTransactionSummary,
@@ -19,7 +21,7 @@ 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 { getAbiMap, getGroupedErrors } from '../utils';
+import { type GroupedErrors, getAbiMap, getErrorMessage } from '../utils';
import { getCurrentTips } from '../utils/fee';
export type TxInputs = {
@@ -206,16 +208,11 @@ export class TxService {
minGasLimit: customFee?.gasUsed,
txSummary: {
...txSummary,
- // if customFee was chosen, we override the txSummary fee with the customFee
fee: feeAdaptedToSdkDiff,
gasUsed: txSummary.gasUsed,
- // fee: customFee?.txCost?.maxFee || feeAdaptedToSdkDiff,
- // gasUsed: customFee?.txCost?.gasUsed || txSummary.gasUsed,
},
};
-
- // biome-ignore lint/suspicious/noExplicitAny: allow any
- } catch (e: any) {
+ } catch (e) {
const { gasPerByte, gasPriceFactor, gasCosts, maxGasPerTx } =
provider.getGasConfig();
const consensusParameters = provider.getChain().consensusParameters;
@@ -228,9 +225,8 @@ export class TxService {
inputs: transaction.inputs,
});
- const errorsToParse =
- e.name === 'FuelError' ? [{ message: e.message }] : e.response?.errors;
- const simulateTxErrors = getGroupedErrors(errorsToParse);
+ const simulateTxErrors: GroupedErrors =
+ e instanceof FuelError ? getErrorMessage(e) : 'Unknown error';
const gasPrice = await provider.getLatestGasPrice();
const baseAssetId = provider.getBaseAssetId();
@@ -251,9 +247,14 @@ export class TxService {
txSummary.isStatusFailure = true;
txSummary.status = TransactionStatus.failure;
+ // Fallback to the values from the transactionRequest
+ if ('gasLimit' in transactionRequest) {
+ txSummary.gasUsed = transactionRequest.gasLimit;
+ }
+
return {
- baseFee: undefined,
- minGasLimit: undefined,
+ baseFee: txSummary.fee.add(1),
+ minGasLimit: txSummary.gasUsed,
txSummary,
simulateTxErrors,
};
@@ -361,16 +362,10 @@ export class TxService {
} catch (e) {
attempts += 1;
- // @TODO: Waiting to match with FuelError type and ErrorCode enum from "fuels"
- // These types are not exported from "fuels" package, but they exists in the "@fuels-ts/errors"
- if (
- e instanceof Error &&
- 'toObject' in e &&
- typeof e.toObject === 'function'
- ) {
- const error: { code: string } = e.toObject();
+ if (e instanceof FuelError) {
+ const error = e.toObject();
- if (error.code === 'gas-limit-too-low') {
+ if (error.code === ErrorCode.GAS_LIMIT_TOO_LOW) {
throw e;
}
}
@@ -386,7 +381,7 @@ export class TxService {
};
}
- static async computeCustomFee({
+ private static async computeCustomFee({
wallet,
transactionRequest,
}: TxInputs['computeCustomFee']) {
@@ -399,7 +394,10 @@ export class TxService {
// funding the transaction with the required quantities (the maxFee might have changed)
await wallet.fund(transactionRequest, {
- ...txCost,
+ estimatedPredicates: txCost.estimatedPredicates,
+ addedSignatures: txCost.addedSignatures,
+ gasPrice: txCost.gasPrice,
+ updateMaxFee: txCost.updateMaxFee,
requiredQuantities: [],
});
diff --git a/packages/app/src/systems/Transaction/utils/error.tsx b/packages/app/src/systems/Transaction/utils/error.tsx
index 7508f22d7f..8c053d78b3 100644
--- a/packages/app/src/systems/Transaction/utils/error.tsx
+++ b/packages/app/src/systems/Transaction/utils/error.tsx
@@ -1,3 +1,5 @@
+import { ErrorCode, type FuelError } from 'fuels';
+
export type VMApiError = {
// biome-ignore lint/suspicious/noExplicitAny:
request: any;
@@ -11,76 +13,39 @@ export type VMApiError = {
};
};
-export type VmErrorType = 'InsufficientInputAmount' | string;
-
export type InsufficientInputAmountError = {
asset: string;
expected: string;
provided: string;
};
-export type GroupedErrors = {
- InsufficientInputAmount: InsufficientInputAmountError;
- NotEnoughCoins: string;
- // biome-ignore lint/suspicious/noExplicitAny: allow any
- [key: VmErrorType]: Record | string | unknown;
-};
-
-export const getGroupedErrors = (rawErrors?: { message: string }[]) => {
- if (!rawErrors) return undefined;
-
- const groupedErrors = rawErrors.reduce(
- (prevGroupedError, rawError) => {
- const { message } = rawError;
- // in some case I had to add the Validity() to the regex. why?
- // const regex = /Validity\((\w+)\s+(\{.*\})\)/;
- const regex = /(\w+)\s+(\{.*\})/;
+export type GroupedErrors = string | undefined;
- const match = message.match(regex);
- if (match) {
- const errorType = match[1];
- const errorMessage = match[2];
-
- // biome-ignore lint/suspicious/noImplicitAnyLet: allow any
- let errorValue;
- try {
- const keyValuesMessage = errorMessage
- .replace('{ ', '')
- .replace(' }', '')
- .split(', ');
- const errorParsed = keyValuesMessage.reduce((prevError, keyValue) => {
- const [key, value] = keyValue.split(': ');
-
- return {
- // biome-ignore lint/performance/noAccumulatingSpread:
- ...prevError,
- [key]: key === 'asset' ? `0x${value}` : value,
- };
- }, {});
- errorValue = errorParsed;
- } catch (_) {
- errorValue = errorMessage;
- }
-
- return {
- // biome-ignore lint/performance/noAccumulatingSpread:
- ...prevGroupedError,
- [errorType]: errorValue,
- };
- }
- if (message.includes('not enough coins to fit the target')) {
- return {
- // biome-ignore lint/performance/noAccumulatingSpread:
- ...prevGroupedError,
- NotEnoughCoins: message,
- };
- }
-
- return prevGroupedError;
- },
- // biome-ignore lint/suspicious/noExplicitAny: allow any
- {} as any
- );
+const camelCaseToHuman = (message: string): string => {
+ return message.replace(/([a-z])([A-Z])/g, '$1 $2');
+};
- return groupedErrors;
+export const getErrorMessage = (
+ error: FuelError | undefined
+): GroupedErrors => {
+ if (!error) return undefined;
+
+ // FuelCore error with Validity()
+ // Example: Validity(TransactionMaxGasExceeded)
+ if (error.message.startsWith('Validity(')) {
+ const validity = error.message.match(/\((\w+)\)/);
+ if (validity) {
+ return camelCaseToHuman(validity[1]);
+ }
+ }
+
+ // FuelCore error with object
+ // Example: InsufficientMaxFee { max_fee_from_policies: 0, max_fee_from_gas_price: 571 }
+ const withObject = /^([a-zA-Z]+)(?:\s*\{(.+)\})?$/;
+ const match = error.message.match(withObject);
+ if (match) {
+ return `${camelCaseToHuman(match[1])} { ${match[2]} }`;
+ }
+
+ return error.message;
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e89afcea19..013b012bc0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3431,6 +3431,9 @@ packages:
'@noble/curves@1.4.0':
resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==}
+ '@noble/curves@1.5.0':
+ resolution: {integrity: sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==}
+
'@noble/curves@1.6.0':
resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==}
engines: {node: ^14.21.3 || >=16}
@@ -19004,7 +19007,7 @@ snapshots:
'@metamask/utils@8.4.0':
dependencies:
'@ethereumjs/tx': 4.2.0
- '@noble/hashes': 1.5.0
+ '@noble/hashes': 1.4.0
'@scure/base': 1.1.6
'@types/debug': 4.1.12
debug: 4.3.4
@@ -19131,6 +19134,10 @@ snapshots:
dependencies:
'@noble/hashes': 1.4.0
+ '@noble/curves@1.5.0':
+ dependencies:
+ '@noble/hashes': 1.4.0
+
'@noble/curves@1.6.0':
dependencies:
'@noble/hashes': 1.5.0
@@ -21820,8 +21827,8 @@ snapshots:
'@solana/web3.js@1.91.7(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies:
'@babel/runtime': 7.25.0
- '@noble/curves': 1.6.0
- '@noble/hashes': 1.5.0
+ '@noble/curves': 1.5.0
+ '@noble/hashes': 1.4.0
'@solana/buffer-layout': 4.0.1
agentkeepalive: 4.5.0
bigint-buffer: 1.1.5
@@ -21842,8 +21849,8 @@ snapshots:
'@solana/web3.js@1.93.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
dependencies:
'@babel/runtime': 7.25.0
- '@noble/curves': 1.6.0
- '@noble/hashes': 1.5.0
+ '@noble/curves': 1.5.0
+ '@noble/hashes': 1.4.0
'@solana/buffer-layout': 4.0.1
agentkeepalive: 4.5.0
bigint-buffer: 1.1.5
@@ -23124,7 +23131,7 @@ snapshots:
'@testing-library/jest-dom@6.1.4(@jest/globals@29.7.0)(@types/jest@28.1.3)(jest@29.7.0(@types/node@20.12.11)(ts-node@10.9.1(@swc/core@1.3.92)(@types/node@20.8.4)(typescript@5.2.2)))':
dependencies:
'@adobe/css-tools': 4.3.2
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.0
aria-query: 5.3.0
chalk: 3.0.0
css.escape: 1.5.1
@@ -33598,8 +33605,8 @@ snapshots:
webauthn-p256@0.0.5:
dependencies:
- '@noble/curves': 1.6.0
- '@noble/hashes': 1.5.0
+ '@noble/curves': 1.5.0
+ '@noble/hashes': 1.4.0
webextension-polyfill@0.10.0: {}