Blog

Developer Guide

How to Query Stellar Accounts, Ledgers, and Transactions with JavaScript

A hands-on tutorial for JavaScript developers who want to query the Stellar blockchain. We will fetch account balances, read ledger data, page through transaction history, and handle errors — all with working code.

Setup

You need Node.js 18+ (for built-in fetch). Optionally install the Stellar SDK:

npm install @stellar/stellar-sdk

Querying Accounts

Fetch Account Balances

const HORIZON = 'https://horizon.stellar.org';

async function getBalances(accountId) {
  const res = await fetch(`${HORIZON}/accounts/${accountId}`);
  if (!res.ok) throw new Error(`Account not found: ${res.status}`);
  const account = await res.json();

  return account.balances.map(b => ({
    asset: b.asset_type === 'native' ? 'XLM' : b.asset_code,
    balance: parseFloat(b.balance).toLocaleString(),
    issuer: b.asset_issuer || 'native',
  }));
}

const balances = await getBalances('GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7');
console.table(balances);

Querying Ledgers

Get Latest Ledger

async function getLatestLedger() {
  const res = await fetch(`${HORIZON}/ledgers?limit=1&order=desc`);
  const data = await res.json();
  const ledger = data._embedded.records[0];

  return {
    sequence: ledger.sequence,
    closedAt: ledger.closed_at,
    txCount: ledger.successful_transaction_count,
    operationCount: ledger.operation_count,
    baseFee: ledger.base_fee_in_stroops,
    protocolVersion: ledger.protocol_version,
  };
}

Get Fee Statistics

async function getFeeStats() {
  const res = await fetch(`${HORIZON}/fee_stats`);
  const fees = await res.json();
  return {
    baseFee: fees.last_ledger_base_fee,
    p50: fees.fee_charged.p50,
    p95: fees.fee_charged.p95,
    maxFee: fees.fee_charged.max,
  };
}

Querying Transactions

Recent Transactions

async function getRecentTransactions(limit = 10) {
  const res = await fetch(`${HORIZON}/transactions?limit=${limit}&order=desc`);
  const data = await res.json();

  return data._embedded.records.map(tx => ({
    hash: tx.hash.slice(0, 12) + '...',
    ledger: tx.ledger,
    sourceAccount: tx.source_account.slice(0, 8) + '...',
    operationCount: tx.operation_count,
    feeCharged: tx.fee_charged,
    successful: tx.successful,
  }));
}

Transaction Operations

async function getTransactionOperations(txHash) {
  const res = await fetch(`${HORIZON}/transactions/${txHash}/operations`);
  const data = await res.json();

  return data._embedded.records.map(op => ({
    type: op.type,
    sourceAccount: op.source_account.slice(0, 8) + '...',
    ...(op.type === 'payment' && {
      to: op.to.slice(0, 8) + '...',
      amount: op.amount,
      asset: op.asset_type === 'native' ? 'XLM' : op.asset_code,
    }),
  }));
}

Pagination

Horizon uses cursor-based pagination with HAL links:

async function getAllPayments(accountId, maxPages = 5) {
  let url = `${HORIZON}/accounts/${accountId}/payments?limit=200&order=desc`;
  const allPayments = [];
  let page = 0;

  while (url && page < maxPages) {
    const res = await fetch(url);
    const data = await res.json();
    const records = data._embedded.records;
    if (records.length === 0) break;

    allPayments.push(...records);
    page++;
    url = data._links?.next?.href || null;
  }

  return allPayments;
}

Error Handling

async function safeFetch(url) {
  const res = await fetch(url);
  if (res.status === 404) return { error: 'not_found' };
  if (res.status === 429) {
    const retryAfter = res.headers.get('Retry-After') || '5';
    return { error: 'rate_limited', retryAfter: parseInt(retryAfter) };
  }
  if (!res.ok) return { error: 'api_error', status: res.status };
  return { data: await res.json() };
}

Retry with Backoff

async function fetchWithRetry(url, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const res = await fetch(url);
      if (res.status === 429) {
        await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
        continue;
      }
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (err) {
      if (attempt === maxRetries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
    }
  }
}

Using the Stellar SDK

import { Horizon } from '@stellar/stellar-sdk';

const server = new Horizon.Server('https://horizon.stellar.org');

// Account
const account = await server.loadAccount('GABC...');
console.log('Balances:', account.balances);

// Transactions
const txs = await server.transactions()
  .forAccount('GABC...')
  .limit(10)
  .order('desc')
  .call();

// Streaming payments
server.payments()
  .forAccount('GABC...')
  .cursor('now')
  .stream({ onmessage: (payment) => {
    console.log('Payment:', payment.amount, payment.asset_type);
  }});

Complete Example: Account Dashboard

async function accountDashboard(accountId) {
  const [account, payments] = await Promise.all([
    fetch(`${HORIZON}/accounts/${accountId}`).then(r => r.json()),
    fetch(`${HORIZON}/accounts/${accountId}/payments?limit=5&order=desc`).then(r => r.json()),
  ]);

  console.log('=== Account Dashboard ===');
  console.log('ID:', accountId.slice(0, 12) + '...');
  account.balances.forEach(b => {
    const asset = b.asset_type === 'native' ? 'XLM' : b.asset_code;
    console.log(`  ${asset}: ${parseFloat(b.balance).toLocaleString()}`);
  });
  console.log('Recent Payments:');
  payments._embedded.records.forEach(p => {
    const asset = p.asset_type === 'native' ? 'XLM' : p.asset_code;
    console.log(`  ${p.amount} ${asset} (${p.created_at})`);
  });
}

*Query the Stellar blockchain with reliable infrastructure. LumenQuery provides managed Horizon API with sub-100ms response times. Start free.*