Summary
In Phase 1 (Prepare), the user proves ownership of notes off-chain. In Phase 2 (Execute), a relayer finalizes the payout from the pool to a public recipient, preserving the user's anonymity.
Architecture
Phase 1: Prepare (user)
-----------------------
[ Client ]
|
| 1. select input notes
| 2. fetch Merkle paths
| 3. compute nullifiers
| 4. choose outputs + recipient
| 5. generate Groth16 proof
v
[ prepare_withdraw() transaction ]
|
v
[ Void Program ]
|
| verify proof + public inputs
| check nullifiers unused
| insert output commitments
| create PreparedTx PDA
| create NullifierFlag PDAs
v
Phase 2: Execute (relayer)
--------------------------
[ Relayer ]
|
| execute_prepared_tx() transaction
v
[ Void Program ]
|
| verify PreparedTx exists + not executed
| check nonce not expired
| transfer publicAmount - fee -> recipient
| pay fee + gas buffer -> relayer
| create ExecutedFlag PDA
| close PreparedTx PDA
v
[ Recipient Wallet + Relayer ]Phase 1: Prepare
The user performs all heavy computation locally: generating the ZK proof, encrypting new notes, and signing the prepare transaction. This step validates the spend without moving funds.
Phase 2: Execute
A relayer (or the user themselves) submits the execution transaction. This step is computationally cheap and ensures the recipient gets paid while the relayer takes a fee.