Skip to main content
This guide is for anyone who maintains code that turns Stellar XDR into objects: an SDK, a language binding, or an in-house decoder sitting under an indexer or wallet. By the end you’ll have a test that runs on every commit and fails the moment your decoder drifts from what the network actually produced. It checks three things, in order of how much they’ll save you:
  1. It decodes. Every fixture is real network data, including shapes that synthetic tests rarely build: fee-bumps wrapping muxed destinations, signed-payload signers, Soroban metadata.
  2. It re-encodes byte-for-byte. Decode, encode, compare. If the bytes differ, your SDK will compute the wrong transaction hash and sign something different from what it showed the user.
  3. The values are right. A decoder can round-trip perfectly and still hand your users a corrupted muxed ID. These checks compare what your SDK exposes against values taken from an independent source.

Get the corpus

You have two options, and for CI you want the first.

The round-trip test

Each manifest lists its files and their XDR types. Decode each one with the matching type, encode it back, and compare it with the original base64.
When a new fixture introduces an XDR type you don’t map yet, these tests fail loudly with unknown xdr_type rather than skipping it. That’s deliberate: a silently skipped fixture is a test that passes while proving nothing.

Check the values that matter

Round-tripping proves your codec is symmetric. It doesn’t prove the values your SDK hands to application code are right. Each manifest’s expected_decoded lists what a correct decoder must extract, with values taken from Horizon or from the transaction as it was built, never from an SDK under test. The muxed-payment fixture is the one most worth automating, because the failure mode is silent: a 64-bit muxed ID passed through a float comes out close, and nobody notices until a deposit lands in the wrong customer’s account.
Never read expected_decoded or expected_indexed through a parser that turns large integers into floats and then compare numbers. Every 64-bit value in a manifest is a JSON string for exactly this reason. Compare strings, or parse into BigInt / uint64 / i64.
The other fixtures have their own expectations: fee-bump hashes and fee sources (CAP-0015), the signed-payload signer’s key and payload (CAP-0040), and the trustline a SAC trust() call created (CAP-0073). The kit’s own runners check all of them and are the best reference for how: JavaScript, Go and Rust. Current results are on the SDK runners page.

Run it in CI

The vendored corpus makes this an ordinary unit test with no network access. If you went with the API instead, run it on a schedule rather than on every PR, so a newly published fixture opens an issue instead of blocking someone’s unrelated change.
.github/workflows/istravex.yml

When a check fails

  • Decode fails. Your XDR definitions are probably behind the protocol version in the manifest’s protocol_version. Regenerate from the matching stellar-xdr release.
  • Re-encode differs. Usually a union arm or optional field that’s normalized on encode. This one matters: it changes hashes and signatures.
  • A value is wrong. Find the pattern in the hardening guide; each rule explains the mechanism and the fix.
  • You think the fixture is wrong. Open an issue. Each manifest’s source says exactly where the bytes came from, and provenance explains how they were verified against the ledger that closed them.