Handle Errors
Network calls fail: a transaction is rejected, an account does not exist, a request is malformed. This guide shows how to read those errors so you can react to them. It builds on Send a Payment.
Prerequisites
- A funded account to submit from. If you need one, see Connect and Fund an Account.
- The examples run on testnet, so they are free and safe to repeat.
Errors are rejected promises
Every SDK call that hits the network returns a promise that rejects on failure,
so wrap calls in try/catch (or use .catch):
try { const result = await horizon.submitTransaction(tx);} catch (error) { // inspect the error (below)}Read a transaction’s result codes
When a submission fails, Horizon explains why with result codes: one for the
transaction and one per operation. A failed submission rejects with the
underlying HTTP error, and the codes are under response.data.extras:
try { await horizon.submitTransaction(tx);} catch (error: any) { const codes = error.response?.data?.extras?.result_codes; console.error("transaction:", codes?.transaction); // e.g. "tx_failed" console.error("operations:", codes?.operations); // e.g. ["op_underfunded"]}Common codes you will see (full list in Result Codes):
- Transaction-level:
tx_failed(an operation failed),tx_bad_seq(wrong sequence number, often a stale loaded account),tx_insufficient_fee,tx_too_late(the time bound passed). - Operation-level:
op_underfunded(not enough balance),op_no_destination(the destination account does not exist),op_no_trust(the destination has no trustline for the asset),op_low_reserve(would drop below the minimum balance).
Inspect the full error
For debugging, log the whole error body. Horizon returns a
problem-details
object at response.data with type, title, status, detail, and extras:
try { await horizon.submitTransaction(tx);} catch (error: any) { console.error(error.response?.data ?? error);}Catch a missing account
Reads throw typed SDK errors. Loading an account that does not exist rejects with
NotFoundError, so you can branch on it:
import { NotFoundError } from "@stellar/stellar-sdk";
try { const account = await horizon.loadAccount(accountId);} catch (error) { if (error instanceof NotFoundError) { console.error("That account does not exist yet (fund it first)."); } else { throw error; }}NotFoundError is one of the SDK’s network errors; BadRequestError (malformed
request) and BadResponseError are others, all extending NetworkError.
Put it together
A runnable script that triggers both failures and handles them:
import { Keypair, Horizon, TransactionBuilder, Operation, Asset, Networks, BASE_FEE, NotFoundError,} from "@stellar/stellar-sdk";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");
async function main() { const sender = Keypair.random(); await horizon.friendbot(sender.publicKey()).call(); const account = await horizon.loadAccount(sender.publicKey());
// Pay an account that does not exist -> the transaction fails. const tx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: Networks.TESTNET, }) .addOperation( Operation.payment({ destination: Keypair.random().publicKey(), asset: Asset.native(), amount: "1", }), ) .setTimeout(30) .build(); tx.sign(sender);
try { await horizon.submitTransaction(tx); } catch (error: any) { const codes = error.response?.data?.extras?.result_codes; console.error("failed:", codes?.transaction, codes?.operations); }
// Loading a non-existent account throws NotFoundError. try { await horizon.loadAccount(Keypair.random().publicKey()); } catch (error) { if (error instanceof NotFoundError) { console.error("account not found"); } }}
main().catch(console.error);With result codes and typed errors, you can tell the difference between a bad request, a missing resource, and a transaction the network rejected, and respond to each.