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.
Handle Configuration Errors
The module validates required paymaster fields when you create an account:
try {
const account = await wallet.getAccount(0)
} catch (error) {
if (error.name === 'ConfigurationError') {
console.error('Invalid paymaster config:', error.message)
}
}Required paymaster fields are paymasterUrl, paymasterAddress, and paymasterToken.
In 1.0.0-beta.2, the published TypeScript declarations name ConfigurationError, but the JavaScript root entrypoint does not re-export the runtime class. Check error.name or error.message instead of importing the class from the package root.
Handle Transaction Fee Caps
sendTransaction() and signTransaction() throw when the quoted fee is greater than transactionMaxFee:
try {
const result = await account.sendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
}, {
transactionMaxFee: 500000n
})
console.log('Transaction submitted:', result.hash)
} catch (error) {
if (error.message.includes('Exceeded maximum fee cost for transaction operation')) {
console.error('Transaction cancelled because the paymaster fee exceeded the cap.')
}
}Handle Transfer Fee Caps
transfer() throws when its quoted fee is greater than transferMaxFee:
try {
const result = await account.transfer({
token: 'TokenMint111111111111111111111111111111111',
recipient: 'Recipient1111111111111111111111111111111',
amount: 1000000n
}, {
transferMaxFee: 500000n
})
console.log('Transfer submitted:', result.hash)
} catch (error) {
if (error.message.includes('Exceeded maximum fee cost')) {
console.error('Transfer cancelled because the paymaster fee exceeded the cap.')
}
}Handle Fee Payer Mismatches
When you pass a prebuilt TransactionMessage, the explicit fee payer must match paymasterAddress:
try {
await account.sendTransaction(transactionMessage)
} catch (error) {
if (error.message.includes('does not match paymaster address')) {
console.error('Set the TransactionMessage fee payer to the configured paymaster address.')
}
}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:
try {
const quote = await account.quoteSendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
})
console.log('Paymaster fee estimate:', quote.fee)
} catch (error) {
console.error('Unable to quote paymaster fee:', error.message)
}Use ordered paymasterUrl arrays and retries when you need endpoint failover.
Handle Signed-Transaction Broadcasts
sendTransaction(signedTransaction) bypasses the paymaster and sends the existing signed wire transaction directly through the configured Solana RPC. It still decodes the embedded payment fee and enforces transactionMaxFee before broadcast. Keep the original signed payload so that a timeout can be investigated without creating a second transaction:
const signedTransaction = await account.signTransaction(transactionMessage, {
transactionMaxFee: 500000n
})
try {
const result = await account.sendTransaction(signedTransaction, {
transactionMaxFee: 500000n
})
console.log('Transaction submitted:', result.hash)
} catch (error) {
// Do not create and sign a replacement transaction until you have checked
// the original transaction's status and lifetime.
console.error('Signed transaction was not broadcast:', error.message)
}The package does not refresh a signed transaction's blockhash, durable nonce, payment instruction, or signatures. Broadcast it while its existing transaction lifetime is valid, and avoid concurrent retries of the same signed payload. If the RPC outcome is uncertain, determine whether the existing transaction reached the network before deciding whether to rebuild and re-sign.
Dispose of Sensitive Data
Call dispose() on owned accounts and wallet managers when private keys are no longer needed:
try {
await account.sendTransaction({
to: 'Recipient1111111111111111111111111111111',
value: 1000000n
})
} finally {
account.dispose()
wallet.dispose()
}Read-only accounts do not hold private keys, but owned accounts wrap a standard Solana account and should be disposed after use.