Read-only pilot
The first step with Corridor moves no money. A payment company shares the account ids of the Stellar accounts it already uses, plus an export of its payout records. Corridor watches those accounts, matches every movement against the records, and produces the morning exceptions queue.
This is the whole product on day one, and it is the reason a pilot can start without a custody review. See ADR 0011.
Share account ids
Stellar G… account ids. No keys, no API access to the company’s systems. Solana and Base addresses can be included for wallets that still sit there.
Share payout records
A CSV from the company’s core system: id, venue, asset, amount, direction, counterparty, reference (if any), time.
Get matched payouts and exceptions
Every record is matched, short, over, pending or missing; every movement with no record is flagged. A CSV comes back for the finance team.
What gets observed
Section titled “What gets observed”| Venue | Observer | What it reads |
|---|---|---|
| Stellar | StellarObserver with a key-less reader |
Horizon payments in and out, with the memo joined in, resuming from a stored paging token |
| Solana | SolanaWatcher |
Amounts from each transaction’s balance changes, so any program’s transfers count; the memo where present |
| Base | Erc20Watcher |
USDC and EURC Transfer logs in and out of any address |
How matching works
Section titled “How matching works”const results = reconcileReadOnly(records, observations, { graceMs, windowMs });- By Corridor Reference. A movement carrying a reference — Stellar’s
MEMO_HASH, a Solana memo — matches the record with the same reference on the same venue, exactly. - By amount and counterparty. Otherwise: same venue, same asset, same direction, same counterparty when the record names one, nearest in time within six hours, exact amounts first.
| Status | Meaning | Action |
|---|---|---|
matched |
Seen, amount as expected | None |
short / over |
Seen, but the amount differs | Chase the provider or the fee |
pending |
Not seen yet, inside the 30-minute grace period | Wait |
missing |
Not seen after the grace period | Chase it before the customer complains |
unexpected |
Moved on-chain with no record | Identify it: it may be customer money |
Amounts stay in integer minor units throughout, so nothing is rounded. difference is reported in minor units, and matchedBy says whether the match was exact or heuristic.
Run it
Section titled “Run it”CORRIDOR_WATCH=GABC…,GDEF… pnpm watch:reportIt polls Horizon for each account’s payments, reconciles them, prints a summary table and then every movement that is not matched. Nothing moves and no key is loaded: the client is built by reader(), which has no signer at all.
22 movements on 2 account(s) · testnet┌──────────────────┬───────┐│ matched │ ... ││ amount_mismatch │ ... ││ unbooked │ ... ││ unreferenced │ ... │└──────────────────┴───────┘exceptions(results) and readOnlyCsv(results) give the queue and the export for a finance team.
What it can and cannot see
Section titled “What it can and cannot see”- It sees what the watched accounts did, and whether each payout record has a movement behind it.
- It cannot see a payout that never reached a chain at all — which is what the payout records are for, and why
missingis the most valuable status in the list. - Heuristic matching is weaker than reference matching, and Base has no memo field at all, so Base movements match by amount and counterparty only. The pilot makes that gap visible, which is itself the argument for routing through Corridor: once Corridor sends the payment, the reference rides
MEMO_HASHand matching becomes exact.
From pilot to moving money
Section titled “From pilot to moving money”The same observers, the same reference, the same matcher and the same statuses run unchanged when Corridor starts moving money. The only difference is who signs: the company connects its own signer (Keys and signers), and Corridor’s sagas send payouts on Stellar with the reference in every memo.