Handle Errors
Handle errors, manage fees, and dispose of sensitive data.
This guide explains how to handle transaction errors, handle transfer errors, verify paymaster address mismatches, diagnose nonce-lane failures, and follow best practices for fee management and memory cleanup.
Transaction Errors
Transactions sent via account.sendTransaction() can fail when the paymaster token balance is insufficient. Wrap calls in a try/catch block:
try {
const result = await account.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
})
console.log('UserOperation hash:', result.hash)
} catch (error) {
if (error.message.includes('not enough funds')) {
console.error('Insufficient paymaster token balance')
} else {
console.error('Transaction failed:', error.message)
}
}Transfer Errors
Token transfers via account.transfer() can fail due to insufficient balance or a fee at or above the maximum limit:
try {
const result = await account.transfer({
token: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
amount: 1000000
})
console.log('Transfer UserOperation hash:', result.hash)
} catch (error) {
if (error.message.includes('Exceeded maximum fee')) {
console.error('Transfer cancelled: fee meets or exceeds the configured limit')
} else if (error.message.includes('not enough funds')) {
console.error('Insufficient paymaster token balance')
} else {
console.error('Transfer failed:', error.message)
}
}Paymaster Address Mismatches
Starting in v1.0.0-beta.15, the shared UserOperation builder checks the configured paymasterAddress against a non-empty paymaster address returned by the RPC in token-paymaster mode. A case-insensitive mismatch throws the exported ConfigurationError before that newly built operation is returned or submitted. The error message identifies the configured address, the paymasterUrl, and the returned address.
import { ConfigurationError } from '@tetherto/wdk-wallet-evm-erc-4337'
try {
await account.quoteSendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
})
} catch (error) {
if (error instanceof ConfigurationError && error.message.startsWith('paymasterAddress mismatch:')) {
console.error('The paymaster RPC returned an unexpected contract address')
} else {
throw error
}
}Confirm the configured contract with your paymaster provider before updating the address. Do not retry by automatically accepting the address returned by the RPC.
A matching quote can be cached for up to two minutes by transaction data. Its cache key does not include per-call fee-mode or paymaster settings, so a matching sign or send can reuse the cached UserOperation without calling the paymaster RPC or rerunning this address check. Pass the same fee-mode and paymaster configuration to the quote and the matching operation.
A caller-supplied signed UserOperation is not rebuilt either. Quoting it does not call the paymaster RPC, and sendTransaction(signedUserOperation) forwards it directly to the bundler without rerunning this address check or the current fee cap. Do not use a cached or signed operation to bypass a mismatch; submit only an operation built after you verified the intended paymaster URL and contract address.
Nonce-Lane Failures
Lane configuration and bundler behavior can fail in several ways:
- A raw
nonceKeybelow0or above2^192 - 1throwsnonceKey must be within the uint192 range (0 to 2^192 - 1). - Parallel sends from an undeployed Safe or a bundler that does not support nonzero keys can be rejected by the RPC or bundler. Deploy the account with one completed operation first and verify bundler support.
- Two operations submitted concurrently in the same named or default lane can select the same sequence. Both calls can return a UserOperation hash even though one never receives a receipt.
Treat a returned hash as submission evidence, not inclusion. Poll getUserOperationReceipt(hash) with an application timeout. If one same-lane hash never resolves, inspect the bundler response and on-chain lane sequence before retrying; use a fresh lane for independent work or batch dependent calls rather than blindly resubmitting the same operation.
Best Practices
Fee Management
You can retrieve current network fee rates using wallet.getFeeRates():
const feeRates = await wallet.getFeeRates()
console.log('Normal fee rate:', feeRates.normal)
console.log('Fast fee rate:', feeRates.fast)Dispose of Sensitive Data
For security, clear sensitive data from memory when a session is complete. Use account.dispose() and wallet.dispose() to securely wipe private keys:
try {
const result = await account.sendTransaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: 1000000000000000n
})
console.log('UserOperation hash:', result.hash)
} finally {
account.dispose()
wallet.dispose()
}Always call dispose() when finished with accounts. Private keys are securely wiped from memory. Disposal is irreversible.