Handle Errors
Handle errors, manage fees, and dispose of sensitive data.
This guide explains how to handle transaction errors, handle connection errors, handle transaction status errors, and follow best practices for fee management and memory cleanup.
Transaction Errors
Transactions sent via account.sendTransaction() can fail for several reasons. Catch the exported WDK error classes and compare reason constants where an error exposes them; do not match message text:
import {
AssertionError,
MaximumFeeExceededError,
TransactionError,
TransactionErrorReason,
ValueError
} from '@tetherto/wdk-wallet'
try {
const result = await account.sendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
value: 100000n
})
console.log('Transaction hash:', result.hash)
} catch (error) {
if (error instanceof TransactionError &&
error.reason === TransactionErrorReason.INSUFFICIENT_BALANCE) {
console.error('Not enough funds in wallet')
} else if (error instanceof MaximumFeeExceededError) {
console.error('Transaction fee exceeds transactionMaxFee')
} else if (error instanceof AssertionError) {
console.error('Transaction input validation failed; verify the Bitcoin provider data')
} else if (error instanceof ValueError) {
console.error('Transaction amount or input selection is invalid')
} else {
throw error
}
}For legacy BIP-44 accounts, signTransaction() and unsigned sendTransaction() compare selected inputs with their fetched previous transactions. A transaction-ID, output-script, or value mismatch raises AssertionError before signing. Verify the client data and endpoint; do not bypass the failure by signing the same reported UTXOs elsewhere.
Connection Errors
With the built-in mempool Electrum adapter, recognized TLS/SSL certificate failures are wrapped in ProviderError with ProviderErrorReason.NETWORK_ERROR; its cause retains the underlying error. Other connection failures can retain their underlying error type. Check the configured hostname, certificate expiry, and runtime trust store. Retrying the same invalid certificate will not repair it, and disabling verification is not a fix. This beta.16 change applies to the mempool adapter's TLS/SSL transports; it does not add certificate checks to TCP or to a caller-supplied client.
Network and backend failures use ProviderError. Inspect error.reason instead of parsing a provider message. For BlockbookClient, HTTP 401 maps to UNAUTHORIZED, 403 to FORBIDDEN, 408 or 504 to REQUEST_TIMEOUT, other 5xx responses to INTERNAL_SERVER_ERROR, and other non-2xx responses to NETWORK_ERROR. WebSocket connection failures use NETWORK_ERROR; WebSocket server-response and fee-estimation failures use INTERNAL_SERVER_ERROR.
import { ProviderError, ProviderErrorReason } from '@tetherto/wdk-wallet'
try {
const balance = await account.getBalance()
console.log('Balance:', balance, 'satoshis')
} catch (error) {
if (error instanceof ProviderError &&
(error.reason === ProviderErrorReason.NETWORK_ERROR ||
error.reason === ProviderErrorReason.REQUEST_TIMEOUT)) {
console.error('Check connectivity and TLS certificate trust before retrying')
} else if (error instanceof ProviderError &&
(error.reason === ProviderErrorReason.UNAUTHORIZED ||
error.reason === ProviderErrorReason.FORBIDDEN)) {
console.error('Check provider credentials and permissions')
} else if (error instanceof ProviderError &&
error.reason === ProviderErrorReason.INTERNAL_SERVER_ERROR) {
console.error('Provider failed internally; retry or use another client')
} else {
throw error
}
}Invalid mnemonic and unsupported bip values throw ValueError while an account is created. Bitcoin accounts do not support token operations: getTokenBalance(tokenAddress), quoteTransfer(options), and transfer(options) throw UnsupportedOperationError.
Transaction Status Errors
getTransaction() validates the transaction ID and searches this account address's history. Handle malformed and unknown transactions on that one-shot lookup:
import { NoSuchElementError, ValueError } from '@tetherto/wdk-wallet'
try {
const receipt = await account.getTransaction(transactionHash)
console.log('Current finality:', receipt.finality)
} catch (error) {
if (error instanceof ValueError) {
console.error('Expected a 64-character hexadecimal transaction ID')
} else if (error instanceof NoSuchElementError) {
console.error('Transaction is not in this account address history')
} else {
throw error
}
}Use waitForTransaction() when the hash may still be propagating. It consumes NoSuchElementError as a transient polling state, so the caller normally handles a timeout instead:
import { TimeoutError } from '@tetherto/wdk-wallet'
try {
const receipt = await account.waitForTransaction(transactionHash, {
target: 'final'
})
console.log('Final transaction:', receipt.hash)
} catch (error) {
if (error instanceof TimeoutError) {
console.error('Transaction did not reach the requested finality in time')
} else {
throw error
}
}Polling also tolerates, by default, up to three consecutive provider errors. Bitcoin does not expose reliable dropped detection through this history flow, so a never-seen or evicted transaction ends in TimeoutError.
Best Practices
Fee Management
You can retrieve current network fee rates using wallet.getFeeRates():
const feeRates = await wallet.getFeeRates()
console.log('Normal fee rate:', feeRates.normal, 'sat/vB')
console.log('Fast fee rate:', feeRates.fast, 'sat/vB')Set transactionMaxFee to stop sendTransaction() and signTransaction() when the estimated BTC network fee exceeds your limit.
wallet.getFeeRates() fetches rates from the mempool.space API, while account.sendTransaction() estimates fees from the connected Electrum server. Use getFeeRates() for display purposes.
Dispose of Sensitive Data
For security, clear sensitive data from memory when a session is complete. Use account.dispose() and wallet.dispose() to securely wipe private keys:
try {
const result = await account.sendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
value: 100000n
})
console.log('Transaction hash:', result.hash)
} finally {
account.dispose()
wallet.dispose()
}Always call dispose() when finished with accounts. Private keys are securely wiped from memory using sodium_memzero. Electrum connections are automatically closed. Disposal is irreversible.