Handle Errors
Handle paymaster, fee, transaction, and cleanup errors in Solana gasless wallets.
This guide covers configuration errors, fee caps, invalid payment instructions, paymaster failures, transaction status, signed broadcasts, and 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 Invalid Payment Instructions
For unsigned flows, the module requires an SPL Token Transfer or TransferChecked payment to the associated token account derived from the configured paymaster address and effective fee token. It decodes the fee from that instruction rather than trusting separate payment_amount metadata. Invalid payment instructions raise ValueError.
A prebuilt TransactionMessage with a fee payer that differs from paymasterAddress also raises ValueError. Check the configuration and intended instructions; do not accept an unexpected payment destination or disable the cap to force a payment through.
You can stop signing when signTransaction() rejects the input or paymaster response:
try {
await account.signTransaction(transactionMessage)
} catch (error) {
if (!(error instanceof ValueError)) throw error
console.error('Check the transaction and paymaster configuration:', error.message)
}Handle Paymaster Failures
Paymaster calls can fail when the endpoint is unavailable, the transaction cannot be quoted, or the fee token cannot fund the requested flow. Provider libraries can throw errors outside the exported WDK classes.
You can surface a quote failure from quoteSendTransaction() without treating it as permission to send:
try {
const quote = await account.quoteSendTransaction(transaction)
console.log('Paymaster fee estimate:', quote.fee)
} catch (error) {
console.error('Unable to quote the paymaster fee:', error.message)
throw error
}Use ordered paymasterUrl arrays and retries when you need endpoint failover. A timeout after a send does not prove that the transaction failed to reach the network.
Handle Transaction Status Errors
getTransaction() throws ValueError for an invalid base58 signature and NoSuchElementError for a well-formed signature absent from history. waitForTransaction() throws TimeoutError when the target is not reached. A confirmed or final receipt can still have success: false; 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.
Best Practices
Manage Fee Caps
sendTransaction() and signTransaction() enforce transactionMaxFee; transfer() enforces transferMaxFee. A fee greater than the applicable cap raises MaximumFeeExceededError; a fee equal to the cap is allowed. Caps use the effective paymaster token's base units.
You can handle a rejected cap for a prepared transaction:
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')
}For unsigned flows, keep trusted paymaster payment amounts at or below Number.MAX_SAFE_INTEGER. Beta.5 still converts the decoded u64 through Number before returning a bigint, so larger amounts can round in quotes and cap comparisons. Signed-fee decoding uses bigint directly.
Dispose of sensitive data
Call dispose() when owned accounts and managers are no longer needed:
account.dispose()
wallet.dispose()Read-only accounts do not hold private keys. Disposing an owned account clears the keys held by its standard Solana account.
Next Steps
Return to Send Transactions for quote, sign, and broadcast flows.