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():
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:
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:
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:
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.
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.
- Keep the signed output and the fee-token configuration together.
- Quote that signed payload with the same configuration.
- Broadcast it with the same token and approved cap.
You can distinguish a missing embedded payment from an uncertain broadcast outcome:
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.