Reconciliation
Balanced books are not the same as correct books. A ledger can balance perfectly while missing a movement the chain recorded. Corridor reconciles in both directions: is everything the chain saw booked? and does each vault hold what the books say?
Observers
Section titled “Observers”| Venue | Observer | Observation externalRef |
|---|---|---|
| Stellar | StellarObserver: Horizon payments in and out of the watched accounts, with the memo joined in. With unreferenced it also reports account funding, sponsored reserves, plain inbound payments, and the XLM fee each outbound transaction charged |
${txHash}:${operationId}, and ${txHash}:fee for the fee |
| Anchors | SEP-31 status, polled; signed webhooks where a provider has them, stored once per order status | ${partner}:${orderId} |
| Solana | Stablecoin balance changes of any address, with the memo reference when present | signature, or ${signature}:${asset} when one transaction moved several stablecoins |
| Base | USDC and EURC Transfer logs in and out of any address |
${txHash}:${logIndex} |
The Stellar observer resumes from a stored Horizon paging token, so restarts neither miss nor double-count payments.
In a read-only pilot the same observers watch the customer’s own Stellar accounts and reconcileReadOnly matches them against the customer’s own payout records instead of Corridor’s ledger.
Matching
Section titled “Matching”const results = await reconcile(ledger, observations);summarize(results); // { matched, amount_mismatch, unbooked, unreferenced }| Status | Meaning | Action |
|---|---|---|
matched |
Booked, same amount | None |
amount_mismatch |
Booked, but the ledger amount differs from the chain | Investigate: wrong amount booked, or a partial delivery |
unbooked |
Carries a Corridor Reference but nothing was booked | Our own movement went unrecorded |
unreferenced |
No reference and not booked | An unidentified deposit: never credited automatically |
The comparison uses entryTotal(..., { excludeLiabilities: true }). Now that the hub and its spokes settle in the same asset, a payout debits the customer’s hold in USDC and moves USDC on the chain, and only the second is what an observer saw.
Vault accounting from the chain
Section titled “Vault accounting from the chain”Memo-tagged movements are booked by the legs that make them. Two kinds of movement are not, and Corridor books them from the chain:
Stellar charges each transaction’s fee in XLM to its source account. Every fee a vault paid is observed with the transaction that paid it and booked to expense:gas, keyed by that transaction.
Deposits into a vault from an approved funding source — owner wallets on mainnet, Friendbot on testnet — are booked to equity:owner.
Movements are processed in chain order, so a top-up is booked before the fees it pays for. Anything else without a reference stays unreferenced: a payment sent without a memo may be a customer’s money, so it is never credited automatically. See ADR 0008.
The vault check
Section titled “The vault check”const checks = await checkVaults(accounts, ledger, [ { name: "treasury", address, asset: "USDC", venue: "stellar", onchain },]);// { vault: "treasury", onchain, ledger, diff, status: "balanced" | "drift" }The difference is exact, not approximate, because the ledger keeps Stellar’s own 7 decimal places (ADR 0012). A tolerance would be a hole in exactly the check that is meant to catch drift.
Alongside it, checkInvariants() re-verifies globally that every entry balances per asset and that the balance projection equals a full recompute from the postings. pnpm stellar:payout prints it at the end of every run.
What this gives an auditor
Section titled “What this gives an auditor”Every booked movement names the transaction that caused it, the journal is append-only, and the reference ties a bank credit, an anchor order and a Stellar payment to the same payout. A reviewer can take any reference from the ledger, look it up on an explorer, and see the same amount.