Skip to main content

Withdraw

Withdrawing tokens exits the shielded pool and releases ERC20 tokens to any EVM address.

Direct withdrawal

import { withdraw, waitForWithdraw } from '@shroud/sdk';

const { tx, changeNote } = await withdraw({
signer,
provider,
poolAddress: "0x91c912eac...",
inputNote: myNote,
withdrawAmount: 50n,
recipient: "0x742d35Cc6...", // any EVM address
senderPublicKey: myPk,
senderPrivateKey: myPrivKey,
wasmPath: "/withdraw.wasm",
zkeyPath: "/withdraw_final.zkey",
});

Partial vs full withdrawal

  • Full withdrawal: withdrawAmount === inputNote.amount — no change note created
  • Partial withdrawal: withdrawAmount < inputNote.amount — a change note is created for the remaining balance

Gasless withdrawal (relay)

import { relayMetaWithdraw } from '@shroud/sdk';

const fee = withdrawAmount / 1000n; // 0.1% fee

const result = await relayMetaWithdraw({
signer,
provider,
poolAddress: "0x91c912eac...",
inputNote: myNote,
withdrawAmount: 50n,
recipient: "0x742d35Cc6...",
senderPublicKey: myPk,
senderPrivateKey: myPrivKey,
wasmPath: "/withdraw.wasm",
zkeyPath: "/withdraw_final.zkey",
fee,
metaTxRelayerAddress: "0xF994781eC...",
});

Gasless withdrawal flow

  1. SDK generates ZK proof locally
  2. User signs EIP-712 typed data (includes proof hash)
  3. SDK POSTs to relay API
  4. MetaTxRelayer receives tokens (as the withdraw recipient)
  5. Redistributes: (amount - fee) to actual recipient, fee to relay wallet
tip

The withdrawal amount is visible on-chain (needed to release ERC20 tokens). For maximum privacy, use different withdrawal amounts than your deposit amounts, and transfer at least once before withdrawing.

Privacy considerations

RiskMitigation
Amount correlationUse different amounts for deposit/withdraw
Timing correlationWait between deposit and withdrawal
Address linkageWithdraw to a fresh address
Deposit fingerprintingUse round numbers for deposits