Core API Methods - Account, Ledger, and Server Information | XRPL APIs & Integration | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
intermediate50 min

Core API Methods - Account, Ledger, and Server Information

Learning Objectives

Query account information including balances, settings, objects, and transaction history

Retrieve ledger data at specific versions (validated, current, closed, historical)

Check server status and synchronization state for monitoring and failover

Handle pagination correctly for large result sets using markers

Interpret response fields accurately and detect potential issues

Every XRPL integration, regardless of purpose, uses a small set of core methods repeatedly:

CORE METHODS BY FREQUENCY OF USE:

- account_info       "What's the balance and state of this account?"
- tx                 "What happened with this transaction?"

- account_tx         "Show me transactions for this account"
- server_info        "Is this server healthy?"

- account_lines      "What trust lines does this account have?"
- account_objects    "What objects does this account own?"
- ledger             "What's in this ledger?"

- ledger_entry       "Get this specific ledger object"
- ledger_data        "Dump ledger contents"

Master these methods, and you can build any XRPL integration. This lesson covers each in detail.

---

account_info returns everything about an account's current state: balance, sequence number, settings, and flags.

Request:

{
  "command": "account_info",
  "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
  "ledger_index": "validated",
  "api_version": 2
}
  • `account`: The address to query (required)
  • `ledger_index`: Which ledger version ("validated", "current", "closed", or number)
  • `strict`: If true, only accept the exact address format provided
  • `queue`: If true, include queued transactions (for "current" ledger)

Response:

{
  "result": {
    "account_data": {
      "Account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
      "Balance": "12345678901",
      "Flags": 0,
      "LedgerEntryType": "AccountRoot",
      "OwnerCount": 5,
      "PreviousTxnID": "ABC123...",
      "PreviousTxnLgrSeq": 87654321,
      "Sequence": 42,
      "index": "DEF456..."
    },
    "ledger_current_index": 87654500,
    "ledger_index": 87654321,
    "status": "success",
    "validated": true
  }
}

Critical Fields Explained:

FIELD                   MEANING                          USE CASE
────────────────────────────────────────────────────────────────────
Balance                 XRP balance in drops             Display balance
                        (1 XRP = 1,000,000 drops)        Calculate available

Sequence                Next valid sequence number       Transaction submission
                        for transactions                 Must match exactly

OwnerCount              Number of objects owned          Reserve calculation
                        (trust lines, offers, etc.)      Available balance

Flags                   Account settings (bitmap)        Feature detection
                        RequireDestTag, DisallowXRP      Validation rules

PreviousTxnID           Hash of last transaction         Change tracking
PreviousTxnLgrSeq       Ledger of last transaction       Audit trail

validated               Is this from validated ledger?   TRUST ONLY IF TRUE

The Balance field isn't "available" balance—reserves affect what can be spent:

RESERVE CALCULATION:

Base Reserve:     10 XRP (required to exist)
Owner Reserve:    2 XRP per object owned

Available = Balance - BaseReserve - (OwnerCount × OwnerReserve)

EXAMPLE:
Balance:      15 XRP (15,000,000 drops)
OwnerCount:   2 (e.g., one trust line, one offer)
Available:    15 - 10 - (2 × 2) = 1 XRP

NOTE: Reserve amounts can change via network amendment.
Current values: BaseReserve=10, OwnerReserve=2 (as of 2024)

Checking Reserves Programmatically:

async function getAvailableBalance(client, address) {
  const [accountInfo, serverInfo] = await Promise.all([
    client.request({ command: 'account_info', account: address }),
    client.request({ command: 'server_info' })
  ])

const balance = parseInt(accountInfo.result.account_data.Balance)
  const ownerCount = accountInfo.result.account_data.OwnerCount

const reserves = serverInfo.result.info.validated_ledger
  const baseReserve = reserves.reserve_base_xrp * 1_000_000
  const ownerReserve = reserves.reserve_inc_xrp * 1_000_000

const reserved = baseReserve + (ownerCount * ownerReserve)
  const available = Math.max(0, balance - reserved)

return {
    total: balance,
    reserved: reserved,
    available: available,
    totalXRP: balance / 1_000_000,
    availableXRP: available / 1_000_000
  }
}

The Flags field is a bitmap encoding account settings:

FLAG                    HEX VALUE      DECIMAL     MEANING
──────────────────────────────────────────────────────────────────
lsfDefaultRipple        0x00800000     8388608     Rippling enabled by default
lsfDepositAuth          0x01000000     16777216    Must authorize deposits
lsfDisableMaster        0x00100000     1048576     Master key disabled
lsfDisallowIncomingNFT  0x04000000     67108864    Block NFT offers
lsfDisallowXRP          0x00080000     524288      Cannot receive XRP
lsfGlobalFreeze         0x00400000     4194304     Freeze all issued assets
lsfNoFreeze             0x00200000     2097152     Cannot freeze assets
lsfPasswordSpent        0x00010000     65536       Password was used
lsfRequireAuth          0x00040000     262144      Trust lines need approval
lsfRequireDestTag       0x00020000     131072      Require destination tag

Checking Flags:

function checkAccountFlags(flags) {
  return {
    requireDestTag: (flags & 0x00020000) !== 0,
    disallowXRP: (flags & 0x00080000) !== 0,
    requireAuth: (flags & 0x00040000) !== 0,
    depositAuth: (flags & 0x01000000) !== 0,
    defaultRipple: (flags & 0x00800000) !== 0,
    disableMaster: (flags & 0x00100000) !== 0,
  }
}

// Usage
const info = await client.request({ command: 'account_info', account: address })
const settings = checkAccountFlags(info.result.account_data.Flags)

if (settings.requireDestTag) {
  console.log('This account requires a destination tag')
}

account_lines returns all trust lines (for issued currencies) associated with an account:

Request:

{
  "command": "account_lines",
  "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
  "ledger_index": "validated"
}

Response:

{
  "result": {
    "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
    "lines": [
      {
        "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
        "balance": "123.45",
        "currency": "USD",
        "limit": "1000",
        "limit_peer": "0",
        "no_ripple": true,
        "quality_in": 0,
        "quality_out": 0
      },
      {
        "account": "rOtherIssuer...",
        "balance": "-50.00",
        "currency": "EUR",
        "limit": "500",
        "limit_peer": "1000000"
      }
    ],
    "status": "success"
  }
}

Understanding Trust Line Fields:

FIELD          MEANING
────────────────────────────────────────────────────────
account        The counterparty (issuer) address
balance        Current balance (positive = you hold, negative = you owe)
currency       Currency code (3-char or 40-hex)
limit          Your trust limit to them
limit_peer     Their trust limit to you
no_ripple      Rippling disabled on your side
no_ripple_peer Rippling disabled on their side
freeze         Line is frozen by issuer
freeze_peer    You froze the line (if you're issuer)

account_objects returns everything an account owns on the ledger:

Request:

{
  "command": "account_objects",
  "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
  "type": "state",
  "ledger_index": "validated",
  "limit": 200
}
  • `state`: Trust lines (RippleState)
  • `offer`: DEX offers
  • `escrow`: Escrow entries
  • `payment_channel`: Payment channels
  • `check`: Check entries
  • `nft_page`: NFT pages
  • `deposit_preauth`: Deposit authorizations
  • (omit `type` to get all)

account_tx returns transactions affecting an account, with pagination:

Request:

{
  "command": "account_tx",
  "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
  "ledger_index_min": -1,
  "ledger_index_max": -1,
  "limit": 100,
  "forward": false
}
  • `ledger_index_min`: Start ledger (-1 for earliest available)
  • `ledger_index_max`: End ledger (-1 for latest)
  • `limit`: Max transactions to return (max 400)
  • `forward`: true = oldest first, false = newest first
  • `marker`: Pagination marker from previous response

Response with Pagination:

{
  "result": {
    "account": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
    "ledger_index_max": 87654321,
    "ledger_index_min": 32570,
    "limit": 100,
    "marker": {
      "ledger": 87654200,
      "seq": 5
    },
    "transactions": [
      {
        "meta": { ... },
        "tx": { ... },
        "validated": true
      }
    ],
    "status": "success"
  }
}

Pagination Pattern:

async function getAllAccountTransactions(client, account) {
  const transactions = []
  let marker = undefined

do {
    const response = await client.request({
      command: 'account_tx',
      account: account,
      ledger_index_min: -1,
      ledger_index_max: -1,
      limit: 400,
      marker: marker
    })

transactions.push(...response.result.transactions)
    marker = response.result.marker

// Rate limiting
    await new Promise(resolve => setTimeout(resolve, 100))

console.log(`Fetched ${transactions.length} transactions...`)

} while (marker)

return transactions
}

XRPL has different ways to reference ledgers:

LEDGER INDEX OPTIONS:

"validated"          Most recent ledger confirmed by consensus
                     SAFE FOR: Transaction confirmations, balance checks

"current"            In-progress ledger (open, not yet closed)
                     SAFE FOR: Checking pending state (carefully)
                     NOT FOR: Confirmations, critical decisions

"closed"             Most recently closed (but not yet validated)
                     RARELY USED: Slight lag vs validated

[number]             Specific ledger by sequence number
                     SAFE FOR: Historical queries

[hash]               Specific ledger by hash
                     SAFE FOR: Cryptographic verification

Critical Rule: For anything financial, use "validated". Never trust "current" for confirmations.

ledger returns information about a specific ledger:

Request:

{
  "command": "ledger",
  "ledger_index": "validated",
  "transactions": false,
  "expand": false
}
  • `transactions`: Include transaction hashes (or full transactions if `expand` is true)
  • `expand`: Expand transactions to full objects
  • `accounts`: Include account state hashes
  • `full`: Include complete ledger data (large!)
  • `owner_funds`: Include owner funds for offers

Response:

{
  "result": {
    "ledger": {
      "accepted": true,
      "account_hash": "ABC...",
      "close_flags": 0,
      "close_time": 750000000,
      "close_time_human": "2023-Oct-15 12:00:00.000000000 UTC",
      "close_time_resolution": 10,
      "closed": true,
      "hash": "DEF...",
      "ledger_hash": "DEF...",
      "ledger_index": "87654321",
      "parent_close_time": 749999990,
      "parent_hash": "GHI...",
      "seqNum": "87654321",
      "totalCoins": "99999999999999990",
      "total_coins": "99999999999999990",
      "transaction_hash": "JKL..."
    },
    "ledger_hash": "DEF...",
    "ledger_index": 87654321,
    "status": "success",
    "validated": true
  }
}

ledger_entry retrieves a specific object from the ledger:

Request (by index):

{
  "command": "ledger_entry",
  "index": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4",
  "ledger_index": "validated"
}

Request (by object type):

{
  "command": "ledger_entry",
  "account_root": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
  "ledger_index": "validated"
}
  • `account_root`: Account by address
  • `directory`: Directory node
  • `offer`: Offer by account + sequence
  • `ripple_state`: Trust line by accounts + currency
  • `escrow`: Escrow by owner + sequence
  • `check`: Check by ID
  • `nft_page`: NFT page by ID

server_info returns detailed information about the connected server:

Request:

{
  "command": "server_info"
}

Key Response Fields:

{
  "result": {
    "info": {
      "build_version": "1.12.0",
      "complete_ledgers": "32570-87654321",
      "hostid": "ROSE",
      "io_latency_ms": 1,
      "jq_trans_overflow": "0",
      "last_close": {
        "converge_time_s": 2.001,
        "proposers": 35
      },
      "load_factor": 1,
      "peers": 52,
      "pubkey_node": "n9...",
      "server_state": "full",
      "server_state_duration_us": "123456789",
      "uptime": 1234567,
      "validated_ledger": {
        "age": 4,
        "base_fee_xrp": 0.00001,
        "hash": "ABC...",
        "reserve_base_xrp": 10,
        "reserve_inc_xrp": 2,
        "seq": 87654321
      },
      "validation_quorum": 28
    },
    "status": "success"
  }
}
FIELD                   MEANING                      HEALTHY VALUE
────────────────────────────────────────────────────────────────────
server_state            Server synchronization       "full" or "proposing"

complete_ledgers Available ledger range No gaps for your needs

validated_ledger.age Seconds since last close < 20 seconds

load_factor Fee multiplier due to load 1 (higher = congested)

peers Connected peer count > 10

io_latency_ms Storage latency < 10ms

last_close.proposers Validators in last close > 25
```

STATE           MEANING                              USABLE?
────────────────────────────────────────────────────────────
disconnected    Not connected to network             ✗
connected       Connected but not synced             ✗
syncing         Downloading ledger data              ✗
tracking        Tracking network but not caught up   ⚠ (limited)
full            Fully synchronized                   ✓
validating      Acting as validator                  ✓
proposing       Actively proposing (validator)       ✓
async function checkServerHealth(client) {
  const response = await client.request({ command: 'server_info' })
  const info = response.result.info

const health = {
healthy: true,
issues: [],
details: {}
}

// Check server state
if (!['full', 'validating', 'proposing'].includes(info.server_state)) {
health.healthy = false
health.issues.push(Server state: ${info.server_state})
}

// Check ledger age
const ledgerAge = info.validated_ledger?.age || 999
if (ledgerAge > 30) {
health.healthy = false
health.issues.push(Ledger age: ${ledgerAge}s (stale))
}

// Check load factor
if (info.load_factor > 10) {
health.issues.push(High load factor: ${info.load_factor})
}

// Check peer count
if ((info.peers || 0) < 5) {
health.healthy = false
health.issues.push(Low peer count: ${info.peers})
}

// Check IO latency
if ((info.io_latency_ms || 0) > 50) {
health.issues.push(High IO latency: ${info.io_latency_ms}ms)
}

health.details = {
version: info.build_version,
state: info.server_state,
ledger: info.validated_ledger?.seq,
ledgerAge: ledgerAge,
peers: info.peers,
loadFactor: info.load_factor,
uptime: info.uptime
}

return health
}

// Usage
const health = await checkServerHealth(client)
if (!health.healthy) {
console.error('Server unhealthy:', health.issues)
// Switch to backup server
}
```

For quick checks, server_state returns minimal information:

{
  "command": "server_state"
}

Response is smaller than server_info, useful for frequent health checks.


Many XRPL methods return paginated results using a marker:

METHODS THAT PAGINATE:
- account_tx         (transaction history)
- account_lines      (trust lines)
- account_objects    (owned objects)
- ledger_data        (ledger contents)
- book_offers        (order book)

Pattern:

async function paginatedQuery(client, command, params) {
  const allResults = []
  let marker = undefined

do {
    const request = {
      command,
      ...params,
      marker  // undefined on first request
    }

const response = await client.request(request)

// Extract results (field name varies by command)
    const results = response.result.transactions || 
                    response.result.lines ||
                    response.result.account_objects ||
                    response.result.offers ||
                    []

allResults.push(...results)
    marker = response.result.marker

// Rate limit
    if (marker) {
      await sleep(100)
    }

} while (marker)

return allResults
}
GOTCHAS:

1. Marker is Opaque

1. Results Can Change

1. Limits Are Guidelines

1. Performance

---
async function getAccountSummary(client, address) {
  const [accountInfo, accountLines, serverInfo] = await Promise.all([
    client.request({ 
      command: 'account_info', 
      account: address,
      ledger_index: 'validated'
    }),
    client.request({
      command: 'account_lines',
      account: address,
      ledger_index: 'validated'
    }),
    client.request({ command: 'server_info' })
  ])

// XRP balance
const xrpDrops = parseInt(accountInfo.result.account_data.Balance)
const xrpBalance = xrpDrops / 1_000_000

// Reserves
const ownerCount = accountInfo.result.account_data.OwnerCount
const baseReserve = serverInfo.result.info.validated_ledger.reserve_base_xrp
const ownerReserve = serverInfo.result.info.validated_ledger.reserve_inc_xrp
const totalReserve = baseReserve + (ownerCount * ownerReserve)
const availableXRP = Math.max(0, xrpBalance - totalReserve)

// Issued currencies
const currencies = accountLines.result.lines.map(line => ({
currency: line.currency,
issuer: line.account,
balance: parseFloat(line.balance),
limit: parseFloat(line.limit)
})).filter(c => c.balance !== 0)

// Account settings
const flags = checkAccountFlags(accountInfo.result.account_data.Flags)

return {
address,
xrp: {
total: xrpBalance,
reserved: totalReserve,
available: availableXRP
},
currencies,
settings: flags,
sequence: accountInfo.result.account_data.Sequence,
ledger: accountInfo.result.ledger_index
}
}
```

async function verifyTransaction(client, txHash, expectedDestination, expectedAmount) {
  const response = await client.request({
    command: 'tx',
    transaction: txHash
  })

const result = {
found: false,
validated: false,
successful: false,
matchesExpected: false,
details: null
}

if (response.result.status === 'error') {
return result
}

result.found = true
result.validated = response.result.validated === true

if (!result.validated) {
return result // Wait for validation
}

// Check transaction result
const txResult = response.result.meta?.TransactionResult
result.successful = txResult === 'tesSUCCESS'

if (!result.successful) {
result.details = { error: txResult }
return result
}

// For payments, verify destination and amount
if (response.result.TransactionType === 'Payment') {
const destination = response.result.Destination

// Use delivered_amount for actual delivered value
const deliveredAmount = response.result.meta?.delivered_amount ||
response.result.Amount

result.matchesExpected = (
destination === expectedDestination &&
deliveredAmount === expectedAmount
)

result.details = {
destination,
deliveredAmount,
destinationTag: response.result.DestinationTag
}
}

return result
}
```

async function accountExists(client, address) {
  try {
    const response = await client.request({
      command: 'account_info',
      account: address,
      ledger_index: 'validated'
    })
    return response.result.account_data !== undefined
  } catch (error) {
    if (error.data?.error === 'actNotFound') {
      return false  // Account doesn't exist
    }
    throw error  // Other error
  }
}

account_info is the workhorse: Every integration needs it for balance, sequence, and settings

Validated ledger is safe: Data from validated ledger has consensus finality

Pagination works reliably: Marker-based pagination handles large datasets

server_info enables monitoring: Essential for production health checks

⚠️ Complete ledger availability: Not all servers store all history; check complete_ledgers

⚠️ Query performance varies: Historical queries may be slow; Clio servers are faster

⚠️ Reserve values can change: Current reserves (10/2 XRP) could be amended

🔴 Trusting non-validated data: "current" ledger can change; only trust "validated"

🔴 Ignoring delivered_amount: For payments, Amount field can lie (partial payments)

🔴 Assuming accounts exist: Always handle actNotFound gracefully

🔴 Pagination without limits: Unbounded pagination can overwhelm servers and clients

These methods are straightforward but have nuances that matter. The difference between "Balance" and "available balance" loses users money when misunderstood. The difference between "validated" and "current" ledger causes transaction double-spends. Master these details; they're the foundation of everything else.


Assignment: Build a command-line tool that displays comprehensive account information.

Requirements:

Part 1: Core Information (40%)

  • XRP balance (total, reserved, available)
  • Account sequence number
  • Account flags decoded to human-readable settings
  • Owner count and reserve breakdown

Part 2: Extended Information (30%)

  • All trust lines with balances
  • Recent transactions (last 10, paginated)
  • Server health status

Part 3: Output Formatting (20%)

  • Clean, readable output format
  • Error handling with helpful messages
  • Support for both mainnet and testnet

Part 4: Edge Cases (10%)

  • Non-existent accounts
  • Accounts with many trust lines
  • Server errors

Sample Output:

=== XRP Account Explorer ===

Address: rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9
Network: Mainnet (validated ledger: 87654321)

XRP Balance:
  Total:     125.50 XRP
  Reserved:   14.00 XRP (base: 10, objects: 2 × 2)
  Available: 111.50 XRP

Account Settings:
  Sequence: 42
  [✓] Require Destination Tag
  [ ] Disallow XRP
  [✓] Default Ripple

Trust Lines (2):
  USD (Bitstamp): 1,234.56 / 10,000.00 limit
  EUR (Gatehub):    567.89 / 5,000.00 limit

Recent Transactions:
  1. Payment    | 2024-01-15 | +100 XRP from rSender...
  2. OfferCreate| 2024-01-14 | Sell 50 USD for XRP
  3. Payment    | 2024-01-13 | -25 XRP to rRecip...

Server: s1.ripple.com (healthy, 52 peers, state: full)
  • Correctness of balance/reserve calculation: 30%
  • Completeness of information displayed: 25%
  • Error handling: 20%
  • Code quality and readability: 15%
  • Output formatting: 10%

Time Investment: 2-3 hours

Submission: Code file(s) with usage instructions

Value: This tool is genuinely useful—you'll use it for debugging integrations and checking accounts.


1. Balance Calculation (Tests Understanding):

An account has Balance: "45000000" (drops) and OwnerCount: 5. With reserves of 10 XRP base and 2 XRP per object, what is the available balance?

A) 45 XRP
B) 25 XRP
C) 35 XRP
D) 20 XRP

Correct Answer: B

Explanation: Balance is 45,000,000 drops = 45 XRP. Reserved = 10 (base) + 5×2 (objects) = 20 XRP. Available = 45 - 20 = 25 XRP. This is the maximum that can be sent without affecting account existence or owned objects.


2. Ledger Index Selection (Tests Application):

You're building a payment processor that credits users when payments are received. Which ledger_index should you use when verifying incoming payments?

A) "current" for fastest confirmation
B) "validated" for consensus-confirmed data
C) "closed" for most recent final state
D) The specific ledger number from the transaction

Correct Answer: B

Explanation: For financial operations, only "validated" ledgers should be trusted—they have consensus finality and won't change. "current" can change as new transactions arrive. "closed" is almost validated but not quite. Using a specific number works but "validated" is the standard pattern for confirming transactions.


3. Pagination Handling (Tests Knowledge):

When paginating through account_tx results, what indicates there are more results to fetch?

A) The transactions array is exactly the size of the limit
B) The response includes a marker field
C) The validated field is true
D) The ledger_index_max hasn't been reached

Correct Answer: B

Explanation: The presence of a marker field in the response indicates more results exist. The marker is an opaque token that, when included in the next request, returns the next page. Array size matching limit doesn't guarantee more results (could be exact count). The other options don't indicate pagination state.


4. Server Health (Tests Critical Thinking):

server_info returns server_state: "tracking" and validated_ledger.age: 45. What should your application do?

A) Proceed normally—tracking means the server is working
B) Wait a moment—the server is nearly caught up
C) Switch to a different server—this one isn't fully synchronized
D) Retry the request—this is a temporary state

Correct Answer: C

Explanation: "tracking" means the server is following the network but isn't fully caught up. Ledger age of 45 seconds (normally 3-5) confirms it's behind. For production use, switch to a fully synchronized server (state: "full"). Don't rely on a server that may be serving stale data.


5. Partial Payments (Tests Comprehension):

When verifying a payment transaction, why is it critical to use delivered_amount instead of Amount?

A) Amount is in drops, delivered_amount is in XRP
B) Amount shows the intended amount, delivered_amount shows what was actually received
C) Amount is deprecated and shouldn't be used
D) delivered_amount includes transaction fees

Correct Answer: B

Explanation: When partial payments are enabled (tfPartialPayment flag), a transaction can succeed while delivering less than the Amount field specifies. The delivered_amount field shows what was actually received. Multiple exchanges have lost funds by crediting users based on Amount instead of delivered_amount. This is a critical security consideration.


For Next Lesson:
Lesson 6 covers transaction submission—the complete flow from construction through confirmation. You'll learn sequence number management, fee calculation, and the patterns that prevent lost transactions.


End of Lesson 5

Total words: ~5,100
Estimated completion time: 50 minutes reading + 2-3 hours for deliverable


  1. Provides comprehensive reference for core methods
  2. Teaches nuances that prevent real-world bugs (reserves, partial payments)
  3. Builds practical skills with health checks and pagination
  4. Creates a useful tool (account explorer) as deliverable
  • "User tried to send 50 XRP but only had 15 available" → Reserve calculation
  • "Payment credited wrong amount" → delivered_amount vs Amount
  • "App showed old balance" → Using current instead of validated
  • "Server was slow, app failed" → Health checks and failover

The delivered_amount Warning:
This is one of the most important security lessons. Real exchanges have lost real money by trusting Amount field. Emphasize this repeatedly.

Lesson 6 Setup:
Students can now query anything. Lesson 6 teaches them to change state—submitting transactions. This is where the real complexity (and risk) begins.

Key Takeaways

1

account_info is your starting point:

Balance, sequence, flags, owner count—everything starts here. Remember to calculate reserves for available balance.

2

Always use "validated" for financial operations:

The current ledger can change. Only validated ledgers have consensus finality.

3

Pagination is essential for history:

Transaction history and account objects paginate. Always handle markers correctly.

4

server_info enables smart failover:

Monitor server health and switch when unhealthy. Check server_state, ledger age, and peer count.

5

Flags encode account settings:

Parse the Flags bitmap to understand account configuration (require dest tag, disallow XRP, etc.). ---