WDK logoWDK documentation

Handle Errors

Handle EIP-7702 gasless wallet configuration, paymaster, fee-limit, and receipt states.

This guide explains the main error cases exposed by @tetherto/wdk-wallet-evm-7702-gasless.

Configuration Errors

Account creation and per-call config overrides can throw ConfigurationError when required fields are missing. The wallet manager stores the config, then account creation validates it before the account is used.

Handle configuration errors
import WalletManagerEvm7702Gasless, { ConfigurationError } from '@tetherto/wdk-wallet-evm-7702-gasless'

try {
  const wallet = new WalletManagerEvm7702Gasless(seedPhrase, {
    provider: 'https://rpc.mevblocker.io/fast',
    bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY',
    isSponsored: true
  })

  const account = await wallet.getAccount(0)
  console.log(await account.getAddress())
} catch (error) {
  if (error instanceof ConfigurationError) {
    console.error('Invalid wallet configuration:', error.message)
  }
}

Common configuration errors include missing provider, bundlerUrl, delegationAddress, or paymasterToken when the account is not sponsored.

Paymaster Token Errors

Token paymaster sends can fail when the account does not have enough paymaster-token balance to repay the paymaster.

Handle paymaster balance errors
try {
  const result = await account.sendTransaction({
    to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
    value: 0n,
    data: '0x'
  })
} catch (error) {
  if (error.message.includes('not enough funds')) {
    console.error('Insufficient paymaster token balance')
  } else {
    console.error('Transaction failed:', error.message)
  }
}

If a generic ERC-7677 paymaster does not support the configured token, the operation throws an error that includes Token <address> is not supported by the paymaster.

Paymaster Address Mismatch

When paymasterAddress is configured, the module checks it against the paymaster returned by the RPC. A mismatch throws ConfigurationError.

Paymaster address mismatch
try {
  await account.quoteSendTransaction({
    to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
    value: 0n,
    data: '0x'
  })
} catch (error) {
  if (error instanceof ConfigurationError && error.message.includes('paymasterAddress mismatch')) {
    console.error('Unexpected paymaster address returned by RPC')
  }
}

Transfer Fee Cap

transfer() throws when paymaster-token fee estimation meets or exceeds transferMaxFee.

Handle transfer fee cap
try {
  await account.transfer({
    token: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
    recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
    amount: 1000000n
  })
} catch (error) {
  if (error.message.includes('Exceeded maximum fee')) {
    console.error('Transfer cancelled because the estimated fee exceeded transferMaxFee')
  }
}

Transaction Fee Cap

In paymaster-token mode, transactionMaxFee applies when this wallet builds sendTransaction(), signTransaction(), or approve() operations. These paths reject only a fee greater than the cap, so an equal fee is allowed. Sponsored operations ignore the cap.

An already-signed UserOperationV8 passed to sendTransaction() is broadcast without a transactionMaxFee check. The module cannot reconstruct its token-denominated paymaster fee. Enforce the cap before signing and reject untrusted signed operations.

Transaction Status

Use the UserOperation hash with getTransaction() or waitForTransaction(). A one-shot getTransaction() call throws ValueError for a malformed hash and NoSuchElementError for a well-formed hash unknown to the bundler. waitForTransaction() treats an unknown hash as transient while it propagates and throws TimeoutError if the bounded wait does not reach its target.

Wait for UserOperation status
const receipt = await account.waitForTransaction(userOperationHash, {
  target: 'confirmed',
  timeout: 180000
})

if (receipt.finality === 'dropped') {
  console.log('The bundling transaction was replaced or otherwise dropped')
} else if (receipt.success === false) {
  console.log('The UserOperation was included but failed')
}

getTransactionReceipt() is deprecated and returns null before the UserOperation maps to an EVM transaction. getTransaction() instead returns normalized pending status during that phase.

Dispose of Sensitive State

Use dispose() when the account is no longer needed.

Dispose state
try {
  const result = await account.transfer({
    token: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
    recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F',
    amount: 1000000n
  })
} finally {
  account.dispose()
  wallet.dispose()
}

On this page