What this covers, and what it cannot
The flow splits into two halves with very different reproducibility:
Be aware of what the scripted half does not prove:
e2e_local.mjs builds a
fabricated Bitcoin transaction paying the depositor’s Writz script and mines
easy-difficulty headers on top of a local checkpoint. The bitcoin-spv
contract genuinely verifies that header chain and Merkle inclusion, but no real
Bitcoin transaction, and therefore no real deposit, is involved. It also passes
an empty enc_note, so the sealed recovery-note round trip (#18) is untested
there.
Treat the scripted run as proof that the Soroban and ZK layers work.
The Bitcoin custody path needs the manual walkthrough at the end.
1. Prerequisites
Versions below are the ones CI pins; anything older is untested.circom 2.x is a Rust binary. Do not npm install -g circom - that
installs the legacy 1.x package, which cannot compile pragma circom 2.0.0:
snarkjs needs no global install - it is a dependency of privacy/circuits/.
2. Build the artifacts a clean checkout lacks
These paths the scripts depend on are gitignored, so a fresh clone does not have them:3. Trusted setup - read before running setup_dev.sh
privacy/circuits/scripts/setup_dev.sh generates the proving keys. It seeds its
entropy with $(date):
- The setup is not reproducible. Running it produces a different proving/verification key pair every time.
- It overwrites the committed
keys/*_vkey.json. Your working tree will show those four files as modified. Do not commit them unless you also intend to push the new keys on-chain.
zk-verifier holds the verification keys from the
original setup, whose .zkey files are not in git. Proofs from a
regenerated setup submitted to it fail with InvalidZkProof - that is a key
mismatch, not a bug in your proof. The scripted flow below avoids this by
deploying its own zk-verifier and registering whatever keys are in
privacy/circuits/keys/.
4. Run the scripted ZK flow
To rehearse the real deployment against the same local network, run
scripts/deploy/deploy_stack.mjs with STELLAR_NETWORK=local (inputs in
scripts/deploy/.env.example).
5. Testnet assumptions
The scripted flow is not a faithful mainnet rehearsal. What differs:6. The Bitcoin half (manual)
This part cannot be scripted - it needs Signet coins and two browser wallets. You need: Xverse on Signet with ≥ 0.0001 sBTC (from a Signet faucet), and Freighter on Stellar testnet, funded via Friendbot.-
Start the relayer, or point at the hosted one:
Hosted alternative:
https://api.testnet.writz.xyz. -
Start the frontend:
- In the browser: connect Xverse + Freighter, derive the deposit P2WSH address, send sBTC to it, and wait for confirmations. Budget ~10 minutes per Signet confirmation - this is the slow step, and the reason the demo script pre-stages deposits.
-
Click Deposit. The relayer assembles the SPV bundle,
bitcoin-spvverifies inclusion on-chain, and the browser generates the deposit proof locally. - Borrow, then repay in full. On full repayment the protocol co-signs the release PSBT; countersign in Xverse and broadcast. BTC returns to your wallet.
7. Troubleshooting
Errors seen while validating this runbook, and what they actually mean:Next: Contribution Guide → · Contract Reference →