Self-relay directly to PoolVault
What this lets you do
Submit a transfer or withdrawal yourself by calling PoolVault.transact() from your own wallet, instead of going through a relayer. Because your wallet sends the transaction, your address is publicly tied to it, so self-relay is not private, even though the proof still hides a transfer's recipient and amount. Use it only when that exposure is fine, or when no relayer is available.
When to use it
- Relayers are unavailable. A fallback when relayer submission is rate-limited or down, so you can still complete a transfer or withdrawal yourself.
- This transaction doesn't need submitter anonymity. When it is fine for your wallet to be seen transacting in the pool, a relayer would only add cost.
Constraints & limits
- Processor must equal your wallet.
PoolVault.transactenforcesmsg.sender == processorAddress. For self-relay, setprocessorAddress = yourWalletAddresswhen building the proof. The proof binds to this value, so re-prepare if you change it. - You pay gas in native ETH. Your wallet needs an ETH balance, even when the transfer is in USDC.
- Transfers and withdrawals both self-submit. A transfer (
amountOut=0) usesexecuteTransfer, and a withdrawal (amountOut>0) usesexecuteWithdraw(or the one-shotwithdraw). BecausePoolVault.transactsends the public output tomsg.sender, a self-relayed withdrawal lands the funds in your submitting wallet, not a fresh address. - You reveal yourself as the submitter. The tx's
msg.senderis your address. The transfer's sender / recipient / amount stay private, but the fact that "your wallet submitted a Privacy Pools transaction" doesn't.
How to use it
- Transfer
- Withdrawal
const prepared = await session.prepareTransfer({
inputCommitments: [noteToSpend.commitment],
amount,
tokenId,
relayAddress: walletAddress,
processorAddress: walletAddress,
feeAmount: "0x0",
recipientDiscoveryData: { evmAddress: recipient },
});
const result = await session.executeTransfer(prepared.executeOptions);
console.log("Self-relayed transfer:", result.txReceipt.txHash);
const prepared = await session.prepareWithdraw({
inputCommitments: [noteToSpend.commitment],
amount,
tokenId,
recipientAddress: walletAddress, // self-relay sends the output to msg.sender
processorAddress: walletAddress,
});
const result = await session.executeWithdraw(prepared);
console.log("Self-relayed withdrawal:", result.txHash);
Self-relay skips the relayer at submission time. executeTransfer (or executeWithdraw) submits directly from your wallet, so there is no relayer fee or quote expiry on the final send. The standard prepare path can still ask configured relayers for quotes. Avoiding quote calls entirely requires custom relayer wiring that does not make network requests.
Behind the scenes
SDK call
session.executeTransfer(prepared.executeOptions), or session.executeWithdraw(prepared) for a withdrawalContract method
PoolVault.transact(...) called directly from your walletProof binding processorAddress is part of the proof's public context. The
msg.sender == processorAddress check in PoolVault means self-relay needs processorAddress set to your own wallet address