Skip to main content

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.transact enforces msg.sender == processorAddress. For self-relay, set processorAddress = yourWalletAddress when 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) uses executeTransfer, and a withdrawal (amountOut>0) uses executeWithdraw (or the one-shot withdraw). Because PoolVault.transact sends the public output to msg.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.sender is 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

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);

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 withdrawal
Contract method PoolVault.transact(...) called directly from your wallet
Proof 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