redeem_voucher’s only signer is whoever pays the fee, and the program pays exactly the merchant the voucher names. So the relayer submits the voucher and pays, and it cannot redirect the money.
It runs as one small Node process. For the week-3 demo it runs on a Mac behind a tunnel, because the phones are on mobile data and cannot reach a laptop on a home network.
What it will pay for
A refusal on budget or the per-vault cap tells the till to retry later; any other refusal is held for a person to look at.
Run it on the Mac
1
A fee-payer key, funded with devnet SOL
2
Start it
127.0.0.1:8787. It needs Node 22.18 or later, which runs TypeScript directly. Keep the ledger file: it is how a retried voucher gets the same answer, so it must survive restarts.3
Put a public address in front of it
https://….trycloudflare.com address. Check it from a phone’s browser: https://…trycloudflare.com/v1/health should show the fee payer’s address. The quick-tunnel address changes whenever cloudflared restarts; a named tunnel keeps one address, and is worth setting up once the demo date is fixed.4
Point the merchant app at it
In Then restart Metro with
apps/merchant/.env:--clear. No new native build is needed for this.Put the same two lines in apps/payer/.env too. A customer paid by another customer settles through the same relayer.Keeping it up
The Mac must be awake with both processes running whenever a till settles.caffeinate -i in a spare terminal stops it sleeping. If the relayer is down, nothing is lost: the till treats it as offline, keeps the vouchers queued, and tries again on the next round.
Why it never submits a voucher twice
The till records the signature only after the relayer answers. A till that crashes in between asks again, so the relayer answers a voucher it has already submitted with the same signature, for as long as that transaction can still land. It builds a new one only once the old one is not on chain and its blockhash has expired. Otherwise the till would see “already redeemed” on the second transaction and report its own payment as a double spend. The submission is written to the ledger before the transaction is sent, and requests are handled one at a time.services/relay/test/ pins both, with a control test that shows the duplicate send happening without them.
Double spends
Two different vouchers from one vault with the same sequence number are proof that the payer spent the same money twice. The relayer acts on that proof in two steps:- Report. It sends
report_conflict, which freezes the vault for good. The proof can come from two places:- a till that scanned both vouchers posts the pair to
POST /v1/conflictwith{ "a": "<base64>", "b": "<base64>" }; - the relayer sees it itself when a second till submits a different voucher at a sequence it already submitted. That till’s voucher is declined with
conflict: true, and the report goes out at once.
- a till that scanned both vouchers posts the pair to
- Slash. Every minute, a sweep looks at each reported vault. Once the vault is frozen and still holds stake, the sweep sends
slash, which moves the stake to the reserve. The log showsslashed <vault>: <signature>.