Guides
Exit certificates
For a swap rail, an exchange or a desk that receives shielded ZEC: ask for a certificate, check it under your own policy, and match the deposit when it lands. Vouch returns evidence; you decide.
On this page
#The flow
Two checks, at two moments. Before the deposit: is the certificate valid under your policy (amount, period, unmoved-since block, no reuse)? After the deposit lands: does the transaction spend exactly the certified notes? The first gives you a pre-cleared deposit to expect; the second ties that deposit to the evidence.
#Choose a policy
| Knob | What it buys you | Typical choice |
|---|---|---|
| Threshold | Covers the deposit you expect | The quote amount, rounded up to 0.125 ZEC |
| Unmoved since H | The coins predate an incident and have not moved since | A block before the incident you care about |
| Max anchor age | The “not moved” fact is recent | Small: a few hundred blocks |
| Period (epoch) | The same coins certified twice to you show as AlreadyUsed | This month |
| Intent | The certificate is for this deposit only | Your quote or deposit id |
#1 · Ask
Build a request at /request with Exit certificate selected, and hand the link to the holder with the deposit address.
{ "v": 1, "id": "c1ea4e7e5a1f0002", "claim": "HoldsAtLeast", "zatoshi": "100000000",
"audience": "rail:example", "expiryDays": 7, "kind": "exit", "epoch": 202610, "dormantSince": 4410000 }The holder runs pof-prove prove --request …. An exit certificate names every usable note (up to five), bound to the request id as the deposit intent.
#2 · Check the certificate
pof-verify check certificate.pof --audience rail:example --anchors anchors.json \
--epoch 202610 --dormant-since 4410000 --seen your-registry.jsonValid means: at least that much, in notes unmoved since the block, made for you in this period, and none of these notes already used with you this period. Record the result’s tags in your registry. Or run the same check in the browser at /rail, or through the API:
POST /v1/certificates { "certificate": "<base64url>", "audience": "rail:example", "epoch": 202610,
"dormantSince": 4410000, "intent": "c1ea4e7e5a1f0002" }
→ 200 { "status": "pre-cleared", "certificate": { "id": "…", "revealed": [ … ], "tags": [ … ] }, "result": { … } }
→ 422 { "status": "refused", "result": { … } } (with a reason: not a certificate, another intent, …)#3 · Match the deposit
pof-anchor tx --network testnet --txid <deposit txid> > deposit.json
pof-verify match --cert certificate.pof --deposit deposit.json # exit 0: matched
POST /v1/certificates/{id}/match { "txid": "<deposit txid>" }
→ { "status": "matched" | "mismatch", "certificate": { "deposit": { "result": { "kind": "Matched", "spent": 2 } } } }Matched means every note the deposit spends is one the certificate named. Value inside one transaction is pooled, so a single uncertified spend makes the whole deposit a mismatch.
#The real testnet exit
The first attempt gave Mismatch: the wallet split change into four outputs, so the bundle carried three dummy spends whose nullifiers looked like uncertified notes. It is kept in fixtures/testnet-exit-first-attempt.* as a documented finding.
The clean run consolidated the notes first, certified both at anchor 4,460,391 (fixtures/proofs/testnet-exit.pof), then sent deposit ff0a2488543a58b852b22c426313ea00c00de1e79a6fa1710dc3cbae69ab256a, spending exactly those notes into two outputs: Matched. Try both on /rail.
#Webhooks and the store
| Event | When |
|---|---|
certificate.precleared | A certificate passed your policy. |
deposit.matched | The deposit spends only certified notes. |
deposit.mismatch | The deposit spends a note the certificate did not name. |
Bodies are { event, certificate, at }, signed with X-Vouch-Signature: sha256=<hmac> under POF_WEBHOOK_SECRET. Set POF_RAIL_TOKEN to require a bearer token on every write, and POF_STORE to persist the registry. The store holds ids, tags, revealed nullifiers and verdicts, never proof bytes.
#What it does not say
Unmoved since block H says when the coins last moved, not where they were before H. Coins stolen long before H and left untouched would pass, which is why H is your policy’s choice. It is evidence for your risk process, never a “clean” stamp, and not a legal answer to any regulation.
You see the certified notes’ nullifiers before the deposit lands, so you could see if the holder spent them elsewhere instead. Holders are told to make certificates only when they are ready to send.