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

Parse the recovery material in memory controlled by your application. Reject the wrong chain, pool, runtime, template, commitment, checksum, or encrypted payload before making RPC calls. A restored record is only a candidate note until membership and spent-state checks pass. Never log it or send it through analytics and error-reporting hooks.

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 MegaETH.
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.