Skip to content

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 {
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 a TransactionFailedError, and its getResultCodes() returns both:

import { TransactionFailedError } from "@stellar/stellar-sdk";
try {
await horizon.submitTransaction(tx);
} catch (error) {
if (error instanceof TransactionFailedError) {
const { transaction, operations } = error.getResultCodes();
console.error("transaction:", transaction); // e.g. "tx_failed"
console.error("operations:", operations); // e.g. ["op_no_destination"]
} else {
throw error;
}
}

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. When Horizon answers with an error status, the SDK rejects with a NetworkError, which carries Horizon’s problem-details object at response.data with type, title, status, detail, and extras:

import { NetworkError } from "@stellar/stellar-sdk";
try {
await horizon.submitTransaction(tx);
} catch (error) {
console.error(
error instanceof NetworkError ? (error.response.data ?? error) : 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 {
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, and TransactionFailedError extends BadResponseError.

Put it together

A runnable script that triggers both failures and handles them:

import {
Keypair,
Horizon,
TransactionBuilder,
Operation,
Asset,
Networks,
BASE_FEE,
} from "@stellar/stellar-sdk";
import { TransactionFailedError } from "@stellar/stellar-sdk";
import { NotFoundError } from "@stellar/stellar-sdk";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");
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) {
if (error instanceof TransactionFailedError) {
const { transaction, operations } = error.getResultCodes();
console.error("transaction:", transaction); // e.g. "tx_failed"
console.error("operations:", operations); // e.g. ["op_no_destination"]
} else {
throw error;
}
}
// Loading a non-existent account throws NotFoundError.
const accountId = Keypair.random().publicKey();
try {
await horizon.loadAccount(accountId);
} catch (error) {
if (error instanceof NotFoundError) {
console.error("That account does not exist yet (fund it first).");
} else {
throw 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.