WDK logoWDK documentation
BitcoinGuides

Handle Errors

Handle errors, manage fees, and dispose of sensitive data.

This guide explains how to handle transaction errors, handle connection 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:

Handle Transaction Errors
import {
  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 ValueError) {
    console.error('Transaction amount or input selection is invalid')
  } else {
    throw error
  }
}

Connection Errors

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.

Handle Connection Errors
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('Provider unavailable or timed out; retry according to your policy')
  } 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:

Handle a Transaction 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:

Handle a Finality Timeout
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():

Get Fee Rates
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:

Dispose Resources
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.

On this page