Skip to main content

Withdraw

A withdrawal spends notes and lands their value at a public address, which is how funds leave the pool. The destination and the amount are public, but there is no on-chain link to the original depositor or any addresses to which that note was transferred within the pool.

How a withdrawal works

A withdrawal moves through three stages:

  1. Prove. A withdrawal is a transact with a non-zero public output. You build the proof, binding it to the destination address (so the funds can't be redirected after proving) and to the PrivacyPoolRelay contract as its processor. The amount is the net the recipient receives, so the input notes must also cover the relayer's fee, and any remainder returns to you as a change note.
  2. Submit. You hand the proof and signed quote to the relayer, which submits it through PrivacyPoolRelay to the PoolVault.
  3. Release. The PoolVault releases the withdrawn value to the relay contract, which forwards it to the destination minus the relayer's fee.

A fresh destination with no on-chain history keeps the withdrawal hard to link, while withdrawing to an address with established activity re-attaches your identity to the funds.

How to withdraw

const withdrawAmount = "0x2386f26fc10000"; // 0.01 ETH in wei
const prepared = await session.prepareWithdraw({
inputCommitments: [noteToSpend.commitment],
amount: withdrawAmount,
tokenId: NATIVE_ETH,
recipientAddress: publicDestination,
});

const result = await session.relayWithdraw(prepared.relayerOptions[0]);
console.log("withdrawal tx:", result.txHash);

The input notes you pass as inputCommitments must be ACTIVE, the only spendable state in the note lifecycle (INACTIVE -> PENDING -> ACTIVE -> SPENT | EXITED). A PENDING note (deposited but not yet attested) cannot be spent.

relayWithdraw(...) returns a TxReceipt directly, so the hash is result.txHash.

Constraints

  • Fees and change. The relayer's fee is taken from the withdrawn asset, while on-chain gas is paid in ETH by the relayer. A partial withdrawal is not a separate operation, just a withdrawal that leaves a change note.
  • Same quote-expiry and partial-failure considerations as a private transfer (relayer path only); see Relaying.

Behind the scenes

SDK call session.prepareWithdraw(...), then session.relayWithdraw(...)
Contract method PrivacyPoolRelay.relay(...), which calls PoolVault.transact(...). The transact carries a public output leg
Circuit transact_NxM (the Transact template, e.g. transact_1x2). Withdrawal is a transact proof whose public amountOut / tokenIdOut signals are non-zero (they are 0 for a pure transfer)

Variants

What's next

  • The withdrawn value is public; nothing further happens in-protocol.
  • Any un-withdrawn remainder lands as a change note, ready to spend.