Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xrpl-state-diff

Inspect exactly how an XRP Ledger transaction changed ledger state.

xrpl-state-diff is a small, developer-focused Rust tool that converts an XRPL transaction and its metadata into a human-readable and machine-readable ledger state diff.

Instead of manually reading AffectedNodes, the tool answers a simpler question:

What changed because of this transaction?

Payment · tesSUCCESS
Ledger 106,187,291 · 043D9639…26894 · rLBSbJgw…V8kYj

STATE CHANGES
────────────────────────────────────────────────────────────────
AccountRoot · modified  (3386568A…0633B)
  r4hck56M…CXG4C
    Balance  71,712.473095 XRP → 71,712.473097 XRP

AccountRoot · modified  (7EFA8F6F…57635)
  rLBSbJgw…V8kYj
    Balance   29.00298 XRP → 29.002967 XRP
    Sequence  101187494 → 101187495

FEE
0.000011 XRP

SUMMARY
2 modified · 0 created · 0 deleted
Fee: 0.000011 XRP

Why?

The XRP Ledger exposes detailed transaction metadata and ledger state, but raw metadata is optimized for machines rather than humans. A transaction can modify several different ledger objects. Understanding those changes currently requires manually interpreting AffectedNodes, PreviousFields, FinalFields, CreatedNode, ModifiedNode, and DeletedNode.

xrpl-state-diff makes that state transition explicit — a deterministic, field-level before/after view of every ledger object a transaction touched.

Features

  • Inspect an XRPL transaction by hash
  • Fetch transaction metadata from an XRPL JSON-RPC server
  • Decode AffectedNodes (CreatedNode, ModifiedNode, DeletedNode)
  • Field-level before/after changes, including unchanged-field mode
  • Normalized XRP / IOU amounts with float-free decimal arithmetic
  • Decodes hex-encoded currency codes (e.g. 4F70756C656E6365…Opulence)
  • Compact summaries for array fields (e.g. 10 NFTs → 11 NFTs)
  • Concise terminal output (auto-colored, NO_COLOR aware)
  • Deterministic machine-readable JSON for pipelines and tests
  • Minimal HTMX web interface (axum, server-rendered, no SPA)
  • Configurable XRPL endpoints (flag, XRPL_ENDPOINT env, or default)
  • Offline fixture-driven test suite (cargo test needs no network)

Install & build

Requires a stable Rust toolchain (1.75+).

cargo build --release
./target/release/xrpl-state-diff --help

Or install directly:

cargo install --path .

Usage

CLI

# human-readable diff
xrpl-state-diff tx 043D9639920D08BCDBE212CABBA685C62310096F5AF7956C4A312EAEF5826894

# deterministic JSON
xrpl-state-diff tx 043D9639920D08BCDBE212CABBA685C62310096F5AF7956C4A312EAEF5826894 --json

# also list fields that did not change
xrpl-state-diff tx <hash> --show-unchanged

# point at a different node
xrpl-state-diff tx <hash> --endpoint https://s2.ripple.com:51234
# or: export XRPL_ENDPOINT=https://my-private-node.example:51234

# disable colors
xrpl-state-diff tx <hash> --no-color

Web UI

xrpl-state-diff web --bind 127.0.0.1:8080
# open http://127.0.0.1:8080

Paste a transaction hash into the form, or click one of the real example transactions. Results are fetched server-side and swapped in via HTMX; no frontend build step, no JavaScript framework.

Real examples to try

# simple XRP payment
xrpl-state-diff tx 043D9639920D08BCDBE212CABBA685C62310096F5AF7956C4A312EAEF5826894

# payment that funds a brand-new account (CreatedNode)
xrpl-state-diff tx 018A90722173F75E9E7F1A9AE4A6D716149F89B43E5E1C878ACB8A39768234DA

# cross-currency payment (RippleState changes)
xrpl-state-diff tx 164CA8806FD55BE41BE6976126B033171D6B2FC26A94A340B6697CA0C27C9CEF

# OfferCreate with a hex-encoded currency ("Opulence")
xrpl-state-diff tx 0BE21941603B16EF6A9BE477B1BB490A0CD054684DA50F17C4762C8761875002

# OfferCancel that deletes an offer
xrpl-state-diff tx 3511CDEDCE001D71108801B34DB6D43429E5C1083B119AE17FC42907A5C1BBE0

# NFTokenMint (NFTokenPage with 10 → 11 tokens)
xrpl-state-diff tx 8A463BD654D254DF5E1D511E5893C4D833DF294C75B2FD88F4F4FB14E681D635

# AccountDelete
xrpl-state-diff tx 8DB4A3EDBD5B42F49338E337CDA3564508F8995E5DEDEF5D7956967EA7F75AB3

JSON output

--json emits a deterministic document (same metadata in → same JSON out). Keys use camelCase to mirror XRPL conventions. Raw values are preserved alongside human-formatted strings so pipelines never lose fidelity.

{
  "format": "xrpl-state-diff",
  "version": 1,
  "transaction": {
    "hash": "043D9639…",
    "type": "Payment",
    "result": "tesSUCCESS",
    "account": "rLBSbJgwwVbbrf9eLcK23mWeVSDQwV8kYj",
    "fee": "0.000011 XRP",
    "feeDrops": "11",
    "ledgerIndex": 106187291,
    "date": 839633451,
    "validated": true
  },
  "changes": [
    {
      "ledgerIndex": "7EFA8F6F…",
      "entryType": "AccountRoot",
      "operation": "modified",
      "label": "rLBSbJgw…V8kYj",
      "fields": [
        {
          "field": "Balance",
          "kind": "changed",
          "before": "29002980",
          "after": "29002967",
          "amount": "xrp",
          "beforeFormatted": "29.00298 XRP",
          "afterFormatted": "29.002967 XRP"
        }
      ]
    }
  ],
  "summary": { "created": 0, "modified": 2, "deleted": 0 }
}

Field semantics:

  • kindadded (created objects), removed (deleted objects or PreviousFields-only fields), changed.
  • amountxrp (drops ÷ 10⁶), iou (decimal value), iouRaw (integer ÷ 10¹⁵, older RippleState balances), raw.
  • before / after — raw JSON values (null/absent for added/removed).
  • beforeFormatted / afterFormatted — human-readable renderings.
  • unchanged — present on each change only with --show-unchanged.

Everything is jq-friendly:

xrpl-state-diff tx <hash> --json | jq '.changes[] | select(.entryType == "AccountRoot")'

Configuration

Setting Flag Env var Default
XRPL JSON-RPC endpoint --endpoint URL XRPL_ENDPOINT https://s1.ripple.com:51234

The tool is stateless and local-first: no account, no API key, no hosted service.

Production notes

Nothing needs to be installed beyond a Rust build, and no keys, seeds or secrets are required. The tool only reads ledger state — it never signs or submits transactions.

  • Endpoint selection — the default public Clio node works for casual use. For production workloads, prefer:

    • your own rippled/clio node (e.g. http://internal-node:51234), or
    • a reputable public aggregator (e.g. https://xrplcluster.com). Set XRPL_ENDPOINT in your environment so no flag is needed.
  • Rate limits — public nodes throttle abusive traffic. If you expect high volume, run your own node.

  • Data availability — nodes only serve data they have. Full-history nodes (s2.ripple.com, or a node with --ledger_history tuned) can answer for older ledgers; Clio nodes cache recent history.

  • Web UI deployment — bind to a specific address, put it behind a TLS reverse proxy, and keep XRPL_ENDPOINT pointed at your node:

    export XRPL_ENDPOINT=http://internal-node:51234
    xrpl-state-diff web --bind 127.0.0.1:8080
  • Unvalidated transactionstx may return unvalidated data; the JSON output reports validated so pipelines can filter it.

How it works

XRPL JSON-RPC ("tx" method)
        │
        ▼
Transaction + Metadata
        │
        ▼
AffectedNode parser (CreatedNode / ModifiedNode / DeletedNode)
        │
        ▼
Normalized field-level state changes
        ├── Human-readable CLI
        ├── Deterministic JSON
        └── HTMX web UI

Key XRPL facts the parser handles:

  • AffectedNodes entries are { "CreatedNode" | "ModifiedNode" | "DeletedNode": { ... } }
  • CreatedNode carries NewFields (the object's initial state)
  • ModifiedNode carries PreviousFields (only the fields that changed) and FinalFields (the full after-state); fields only in FinalFields are unchanged
  • DeletedNode may carry both PreviousFields and FinalFields
  • Metadata appears under meta (Clio/modern rippled) or metaData (older rippled); both are accepted
  • LedgerIndex may be a hex string or a number
  • XRP amounts are integer drops; IOU amounts are decimal strings or {currency, issuer, value} objects; currencies may be hex-encoded

Testing

cargo test      # offline — fixtures are checked in
cargo clippy --all-targets -- -D warnings
cargo fmt --check

fixtures/ contains real mainnet transaction responses covering:

Fixture Transaction Covers
payment.json Payment 2 AccountRoots modified, fee
payment-create-account Payment CreatedNode (new AccountRoot)
payment-cross-currency Payment RippleState balance changes
offer-create OfferCreate Created Offer, hex currency
offer-cancel OfferCancel DeletedNode (Offer)
trust-set TrustSet AccountRoot modification
nft-mint NFTokenMint NFTokens array summary
nft-create-offer NFTokenCreateOffer Created NFTokenOffer
account-set AccountSet Simple modification
account-delete AccountDelete Deleted AccountRoot with diff
check-create CheckCreate Created Check
ticket-create TicketCreate Created Ticket

To add a fixture, fetch a transaction and save the full tx response:

curl -s https://s1.ripple.com:51234 \
  -H 'Content-Type: application/json' \
  -d '{"method":"tx","params":[{"transaction":"<hash>","binary":false}]}' \
  > fixtures/my-case.json

Then add an assertion in tests/diff_tests.rs. scripts/fetch_fixtures.sh automates gathering a varied set from a live node.

Architecture

src/
├── main.rs          # binary entry point
├── cli.rs           # clap CLI definitions
├── config.rs        # endpoint resolution, hash validation
├── error.rs         # error type
├── xrpl/
│   ├── client.rs    # minimal JSON-RPC client (tx method)
│   └── model.rs     # lenient tx/meta parsing
├── diff/
│   ├── parser.rs    # AffectedNodes → normalized LedgerChange
│   ├── normalize.rs # field-level diffs + summary
│   └── format.rs    # amount classification & float-free formatting
├── output/
│   ├── terminal.rs  # human-readable renderer
│   └── json.rs      # deterministic JSON renderer
└── web/
    ├── routes.rs    # axum routes
    └── view.rs      # template-ready view model
templates/           # askama + HTMX templates
fixtures/            # offline test fixtures (real mainnet data)
scripts/             # fixture-fetching helper

Design principles

Small

This is a diagnostic tool, not an explorer or blockchain analytics platform.

Deterministic

Given the same transaction metadata, the same state diff is produced.

Local-first

No account, API key, hosted service or proprietary backend.

Composable

JSON output is suitable for shell pipelines, data-processing jobs and other developer tools.

Protocol-aware

Output exposes XRPL concepts such as ledger entry types rather than hiding everything behind generic blockchain abstractions.

Non-goals

xrpl-state-diff is deliberately not: a blockchain explorer, an indexer, a wallet, a trading terminal, an analytics platform, an SDK replacement, a transaction simulator, an archival database, a hosted SaaS service. It has no database, no authentication, no accounts, no signing, no submission, and no "AI explanation" — the value is the correct deterministic state transition.

License

MIT

About

Inspect exactly how an XRP Ledger transaction changed ledger state

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages