Privacy Wallet · Technical Docs

Transaction flow

Withdrawal overview

Withdrawals use a two-phase commit system to separate heavy off-chain proof generation from lightweight on-chain execution.

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.