Skip to main content

Adopt go-stellar-sdk/xdr for SCVal decoding in source connectors

ADR-0013Accepted2026-04-23

Context

Soroban connectors must decode SCVal and ContractEvent XDR in-process, since Horizon is ruled out (ADR-0001). The XDR definitions are large and regenerate with every protocol release, so a hand-rolled fork would need a rebase each time and has already produced bugs elsewhere.

Decision

Decode SCVal with github.com/stellar/go-stellar-sdk/xdr, pinned in VERSIONS.md and go.mod.

  • internal/scval is a thin wrapper that re-exports scval.ScVal and scval.ScMapEntry as aliases and holds the shared decode helpers; the lint allowlist in the Invariant bounds where else xdr may be imported.
  • The original limit of the SDK dependency to .../xdr and .../strkey is retired: the ingest path also uses the SDK's ledger, datastore and history-archive packages.
  • The SDK's own RPC client is not adopted. Production ingest reads Galexie MinIO LedgerCloseMeta, and internal/stellarrpc survives only for the rpc-probe diagnostic and fixture capture (AGENTS.md invariant 6).
  • Rejected: a hand-rolled or vendored SCVal parser, a separate decoder process, and decoding stellar-rpc getEvents JSON (it returns opaque base64 XDR).

Invariant

  • The SDK version is pinned, and an upgrade that changes wire encoding fails TestGolden_symbolBytes in internal/scval/scval_test.go before it ships.
  • The SDK xdr package is imported only under internal/scval/ and the allowlist in lint rule B/xdr-scoped-to-scval (scripts/ci/lint-imports.sh): internal/canonical/sac.go and asset_xdr.go, internal/ledgerstream/, archivecompleteness/, dispatcher/, pipeline/, storage/clickhouse/, xdrjson/, sdexclaim/, entrywalk/, ops/, stellarrpc/, the ledger-meta source packages (sdex, classicmovements, accounts, trustlines, claimable_balances, liquidity_pools, sac_balances, sep41_supply, sep41_transfers), cmd/stellarindex-indexer/, scripts/dev/ and _test.go files. Any other package imports internal/scval instead.
  • Decoders keep ADR-0003 amount safety across the decode path.

Consequences

Each protocol bump costs one pin change, not a fork rebase. The dependency graph grows by the SDK's indirect modules. Decoders depend on the internal/scval wrapper, so a different decoder could replace the SDK behind it.

Evidence

internal/scval/ and its golden test, scripts/ci/lint-imports.sh rule B/xdr-scoped-to-scval, per-venue decode_test.go files with captured fixtures under test/fixtures/, and VERSIONS.md.