WDK logoWDK documentation
SolanaGasless SolanaGuides

Handle Errors

Handle paymaster, fee, transaction, and cleanup errors in Solana gasless wallets.

This guide covers configuration errors, paymaster failures, fee caps, transaction message fee payer checks, and memory cleanup.

Beta.5 exports public error classes from the package root. Use instanceof checks, rethrow unfamiliar errors, and avoid branching on message text. ConfigurationError was removed; missing required paymaster fields now raise ValueError.

Handle Configuration Errors

Required paymaster fields are paymasterUrl, paymasterAddress, and paymasterToken. Missing fields raise ValueError when the account is created. An empty paymaster endpoint list also raises ValueError, including during manager construction in beta.6.

You can handle invalid configuration when calling wallet.getAccount():

Handle invalid configuration
import { ValueError } from '@tetherto/wdk-wallet-solana-gasless'

try {
  const account = await wallet.getAccount(0)
} catch (error) {
  if (!(error instanceof ValueError)) throw error
  console.error('Check the paymaster configuration:', error.message)
}

Handle Transaction Fee Caps

sendTransaction() and signTransaction() raise MaximumFeeExceededError when the fee is greater than transactionMaxFee. A fee equal to the cap is allowed. Caps use the effective paymaster token's base units.

Handle the new error class for a transaction your application has prepared and reviewed:

Transaction fee cap handling
import { MaximumFeeExceededError } from '@tetherto/wdk-wallet-solana-gasless'

try {
  const result = await account.sendTransaction(transaction, {
    transactionMaxFee: 500000n
  })
  console.log('Transaction submitted:', result.hash)
} catch (error) {
  if (!(error instanceof MaximumFeeExceededError)) throw error
  console.error('The paymaster fee exceeded the approved cap')
}

Handle Transfer Fee Caps

transfer() raises MaximumFeeExceededError when the fee is greater than transferMaxFee. Pass your reviewed token, recipient and base-unit amount in transferOptions:

Transfer fee cap handling
try {
  const result = await account.transfer(transferOptions, {
    transferMaxFee: 500000n
  })
  console.log('Transfer submitted:', result.hash)
} catch (error) {
  if (!(error instanceof MaximumFeeExceededError)) throw error
  console.error('The paymaster fee exceeded the approved cap')
}

Handle Fee Payer Mismatches

When you pass a prebuilt TransactionMessage, the explicit fee payer must match paymasterAddress. Beta.5 raises ValueError for a mismatch:

Fee payer mismatch handling
try {
  await account.sendTransaction(transactionMessage)
} catch (error) {
  if (!(error instanceof ValueError)) throw error
  console.error('Check the TransactionMessage fee payer and paymaster configuration')
}

Handle Paymaster Failures

Paymaster calls can fail when the endpoint is unavailable, the paymaster cannot quote the transaction, or the paymaster token is not funded for the requested flow. Wrap quote and send calls in try/catch blocks.

Use ordered paymasterUrl arrays and retries when you need endpoint failover.

Handle Invalid Payment Instructions

In beta.5, invalid payment instructions raise ValueError. For unsigned flows, the module does not trust the paymaster's separate payment_amount metadata. It derives the configured paymaster's associated token account, requires the returned instruction to be an SPL Token Transfer or TransferChecked to that account, and decodes the fee from the instruction before applying fee caps or requesting signatures. Keep trusted paymaster fees at or below Number.MAX_SAFE_INTEGER; beta.5 still converts that decoded amount through Number before the owned account compares it.

Reject an invalid paymaster payment
try {
  await account.signTransaction(transaction)
} catch (error) {
  if (error instanceof ValueError) {
    console.error('Check the transaction and paymaster configuration:', error.message)
  } else {
    throw error
  }
}

Do not retry by accepting the returned destination or by disabling fee caps. Verify paymasterAddress, paymasterToken, and the endpoint before trying again.

Handle Transaction Status Errors

getTransaction() throws ValueError for an invalid base58 signature and NoSuchElementError for a well-formed signature absent from transaction history. waitForTransaction() throws TimeoutError when the target is not reached. A confirmed or final receipt can still have success: false, and the Solana implementation does not currently classify a transaction as dropped.

Handle Signed-Transaction Broadcasts

sendTransaction() broadcasts a fully signed transaction through Solana RPC without contacting the paymaster again. Retain the paymasterToken used at signing in the quote and send overrides. The module decodes a matching embedded payment and reapplies transactionMaxFee before broadcast. A missing payment for that token raises NoSuchElementError instead of returning zero.

  1. Keep the signed output and the fee-token configuration together.
  2. Quote that signed payload with the same configuration.
  3. Broadcast it with the same token and approved cap.

You can distinguish a missing embedded payment from an uncertain broadcast outcome:

Handle a signed payment mismatch
import { NoSuchElementError } from '@tetherto/wdk-wallet-solana-gasless'

const signedTransaction = await account.signTransaction(transactionMessage, feeConfig)

try {
  const quote = await account.quoteSendTransaction(signedTransaction, feeConfig)
  console.log('Embedded paymaster fee:', quote.fee)
  const result = await account.sendTransaction(signedTransaction, feeConfig)
  console.log('Transaction submitted:', result.hash)
} catch (error) {
  if (!(error instanceof NoSuchElementError)) throw error
  console.error('The signed payment does not match the effective fee token')
}

This payment check does not validate every instruction or signature. Accept this account's exact signed output, or independently validate externally supplied payloads. The module does not refresh a signed blockhash, durable nonce, payment, or signature. After an uncertain RPC result, inspect the original transaction's status and lifetime before creating a replacement.

Dispose of Sensitive Data

Call dispose() on owned accounts and wallet managers when private keys are no longer needed.

Read-only accounts do not hold private keys, but owned accounts wrap a standard Solana account and should be disposed after use.

On this page