A withdrawal touches recovery, tree state, proving files, fee state, calldata, transport, and settlement. Build it in stages, and do not change the public withdrawal intent after generating its proof.

Recover

For wallet recovery, scan CommitmentInserted only from the configured pool and deployment block, verify the EIP-712 signer, derive keys locally, and decrypt each bound payload. For bearer recovery, parse the imported nullark1_... note in memory controlled by your application. Both paths reject the wrong chain, pool, runtime, template, commitment, checksum, payload hash, authenticated context, or ciphertext shape. A restored record is only a candidate note until membership and spent-state checks pass. Never log wallet signatures, derived keys, bearer notes, or decrypted records.

Synchronize

Read from the runtime RPC starting at the configured deployment block:
  • Commitment insertion and leaf index
  • Merkle path to a currently accepted root
  • Nullifier spent state
  • Active fee and bounded fee policy
  • Pool and verifier bytecode identity where required
RPC results need to match the bundled runtime. Stop when the chain or contract differs.

Prove

Build one fixed withdrawal intent containing the accepted root, child nullifier, destination, gross amount, fee, chain, pool, proof context, and encrypted cover-payload hash. Load only proving files authenticated by the independent manifest pin.
Verify the generated proof locally and compare every public input by name and position. A response with the right array length can still contain the wrong statement.

Preflight

Immediately before submission:
  1. Recheck RPC chain ID.
  2. Recheck nullifier remains unspent.
  3. Recheck active fee and user bounds.
  4. Simulate exact zero-value transaction with eth_call.
  5. Run remote eth_estimateGas against the configured Robinhood Chain RPC.
If the root, fee, destination, amount, chain, pool, proof context, or encrypted payload changes, build a fresh proof package.

Submit

Send the exact prepared transaction through the user’s connected wallet after preflight. Relayer requests and deadlines are still pending in the SDK, so keep them out of calldata wrappers for now. An unknown wallet result needs investigation, not an automatic retry. See Submission transport for the boundary any future transport must preserve.

Reconcile

Resolve success from the configured pool:
  • Transaction succeeded at expected pool
  • Pool nullifiers marks the intended child nullifier as spent
  • Withdrawal matches nullifier, destination, gross amount, net amount, fee, and encrypted payload
  • Any unspent sibling child remains in the restored bundle inventory

Negative test matrix

The TypeScript SDK performs these checks around the local prover. See Withdrawal public inputs and Where a Nullark flow can fail for exact stop conditions.