diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 822952b..864b292 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' registry-url: 'https://registry.npmjs.org' - name: Update npm to latest run: npm install -g npm@latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b9e0f..363ac09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added - [MINOR] Added `useWeb3WritableContract` to get a signer-bound (write-capable) contract instance directly, without a separate `useWeb3Contract` call or a manual `.connect()` -- [MINOR] Added optional `abi` param to `useWeb3Transaction` and a new `errorMessage` field on `Web3TransactionDetails`, decoding a transaction's custom-error revert data via the ABI when available +- [MINOR] Added optional `abi` param to `useWeb3Transaction` and a new `errorMessage` field on `Web3TransactionDetails` - [MINOR] Added `web3Units` helpers: `Web3MaxUint256`, `formatWeb3Units` (null-safe `formatUnits`), and `parseWeb3Units` (safe-fallback or throwing `parseUnits`) ### Changed diff --git a/src/useWeb3Transaction.ts b/src/useWeb3Transaction.ts index 3968455..2bfd5b5 100644 --- a/src/useWeb3Transaction.ts +++ b/src/useWeb3Transaction.ts @@ -1,6 +1,6 @@ import React from 'react'; -import { Interface, isCallException } from 'ethers'; +import { ErrorCode, Interface, isCallException, isError } from 'ethers'; import { Web3ContractInterface, Web3TransactionReceipt, Web3TransactionResponse } from './model'; @@ -14,7 +14,27 @@ export interface Web3TransactionDetails { receipt: Web3TransactionReceipt | null; } +const ETHERS_ERROR_MESSAGES_BY_CODE: Partial> = { + ACTION_REJECTED: 'Transaction cancelled.', + INSUFFICIENT_FUNDS: 'Your wallet does not have enough funds to cover this transaction.', + NONCE_EXPIRED: 'This transaction has already been submitted. Refresh and try again.', + REPLACEMENT_UNDERPRICED: 'Could not replace the pending transaction — try again with a higher fee.', + TIMEOUT: 'The request timed out. Please try again.', + NETWORK_ERROR: 'A network error occurred. Please try again.', + SERVER_ERROR: 'A network error occurred. Please try again.', +}; + const decodeErrorMessage = (newError: unknown, abi?: Web3ContractInterface): string => { + if (isError(newError, 'TRANSACTION_REPLACED')) { + if (newError.reason === 'repriced') { + return 'Your transaction was sped up by your wallet — check your wallet for the latest status.'; + } + return newError.reason === 'cancelled' ? 'Transaction cancelled.' : 'Your transaction was replaced by another transaction.'; + } + const code = (newError as { code?: ErrorCode } | null)?.code; + if (code && ETHERS_ERROR_MESSAGES_BY_CODE[code]) { + return ETHERS_ERROR_MESSAGES_BY_CODE[code]; + } if (abi && isCallException(newError) && newError.data) { try { const parsedError = new Interface(abi).parseError(newError.data); @@ -22,7 +42,7 @@ const decodeErrorMessage = (newError: unknown, abi?: Web3ContractInterface): str return parsedError.name; } } catch { - // Revert data present but not decodable with this ABI (e.g. selector from a different contract) + // Revert data present but not decodable with this ABI } } return newError instanceof Error ? newError.message : String(newError);