V5 Proxy Withdrawals

Overview

V5 introduces proxy-based withdrawal patterns for enhanced privacy and gas efficiency.

Key Concepts

Proxy Pattern

  • Users deposit into a proxy contract first
  • Proxy handles Merkle tree updates and proof generation
  • Reduces on-chain footprint of withdrawal operations
  • Enables batching multiple withdrawals in single transaction

Contract Addresses (Paseo AssetHub)

ContractAddressPurpose
Shield0x327773DAF3E145623b9CF5E78c8Cbc09c657e14bMain shield contract
Verifier0x471Aea61fA91cb122f1A00Da5f476eB69dc114EdZK proof verification
LeanIMT1280xFF8Ead5c88CdEf89f9E5514F00e1D9db09ac5Ca5Merkle tree storage
PoseidonT30x1d165f6fE5A30422E0E2140e91C8A9B800380637Hash function

Withdrawal Flow

  1. Build Merkle Tree: Reconstruct from contract events starting from deploymentBlock: 9592061
  2. Generate Proof: Use withdraw_phase2_fixed circuit with 7 public signals
  3. Validate Root: Compare local tree.root with contract.currentRoot()
  4. Execute Withdrawal: Call withdrawNative() or withdraw() with proof

7 Public Signals

V5 contract requires uint[7] calldata pubSignals:

pubSignals = [root, nullifierHash, existingValue, newValue, 
              existingSecret, newSecret, assetId]

Use padTo7Signals: true when calling zkWithdraw() for native tokens (assetId = 0).

Event Signatures

Deposit(address,bytes32,uint256)    // token, commitment, amount
Withdrawal(address,uint256,address,uint256) // caller, amount, recipient, nullifierHash
NewCommitment(bytes32)              // commitment

Known Limitations

  • Proxy contract 0x94196fF8Fb407F84414dfd6749d88D3FBc1dd1F6 has 85 leaves without historical events
  • Use main contract for full tree reconstruction
  • Addresses must be lowercase to avoid EIP-55 checksum validation errors

Troubleshooting

  • Duplicate commitments: Track with Set<string> to prevent double insertion
  • Wrong tree depth: Local tree must match contract's expected treeDepth: 128
  • Event scanning slow: Use deploymentBlock to skip historical blocks