# Submit a transfer or withdrawal via relayer

> Hand a prepared transfer or withdrawal and signed quote to a relayer, who pays gas, submits on-chain, and splits the fee.

## What this lets you do

Hand a prepared transfer or withdrawal (proof and signed quote) to a [relayer](/operations/relaying). The relayer submits it on-chain and pays the gas, taking its fee from the amount you send, so your wallet never appears as the submitter. That is central to staying anonymous: the proof already hides the sender, recipient, and amount (for transfers), and submitting from your own wallet would link the transaction back to you. Relayers also accept fees in whatever asset is being transferred or withdrawn, so the end user never needs ETH for gas.

## Constraints & limits

-   **Quote must be unexpired.** A quote that has expired by the time you submit is rejected by the relayer. Depending on timing this surfaces as a local `FeeCommitmentExpired` error or as a relayer-side rejection. Either way, recover by re-quoting.
-   **Processor must match.** Two separate on-chain checks enforce this: `require(_transactParams.processor == msg.sender)` reverts with `PoolVault_InvalidProcessor` if the relayer submits via a contract whose address differs from the quoted processor; and the proof's `context` binds to the full `TransactParams + NoteData`, so any payload tampering reverts with `PoolVault_ProofContextMismatch`.
-   **Relayer is trusted to submit timely.** A malicious relayer could sit on your quote and not submit, costing you time. The protocol doesn't punish them. Pick reliable relayers.
-   **Relayer is not trusted with privacy.** The relayer sees routing metadata, the proof shape (input/output counts), the encrypted note payloads, the asset and fee, and request metadata such as your IP. It does not see the sender or the recipient. A transfer's amount stays shielded, while a withdrawal's amount is public by design, since the value is leaving the pool. On the deployed relayer the fee shows up on-chain as the transact's public output (`amountOut`), so a relayed transfer publicly reveals its fee even when nothing else about it is visible.

## What it unlocks next

-   Anonymous submission: your wallet never touches the chain, so nothing on-chain links you to the transfer or withdrawal you submitted.
-   Gasless onboarding: users who hold no ETH can still transfer or withdraw, because the relayer takes its fee from the asset being sent (i.e. a USDC fee for a USDC transfer).

## How to use it

```ts
const prepared = await session.prepareTransfer({
    inputCommitments: [noteToSpend.commitment],
    amount,
    tokenId,
    recipientDiscoveryData: { evmAddress: recipient },
});

const result = await session.relayTransfer(prepared.relayOptions[0]);

console.log("Transfer landed:", result.txReceipt.txHash);
console.log("Recipient notes:", result.recipientNotes);
```

```ts
const prepared = await session.prepareWithdraw({
    inputCommitments: [noteToSpend.commitment],
    amount,
    tokenId,
    recipientAddress: publicDestination,
});

const result = await session.relayWithdraw(prepared.relayerOptions[0]);

console.log("Withdrawal landed:", result.txHash);
```

**Quote-expiry retry pattern:** if proof generation is slow and the quote expires, request a fresh quote by re-running `prepareTransfer` or `prepareWithdraw`, then submit again. Cap retries so a stuck relayer doesn't trap the user in an endless loop.

## Behind the scenes

SDK call `session.relayTransfer(prepared.relayOptions[0])`, or `session.relayWithdraw(prepared.relayerOptions[0])` for a withdrawal
Relayer endpoint `POST {relayer.url}/v1/relay/{chainType}/{chainId}/transfer`, or `.../withdrawal` for a withdrawal
Contract method The deployed Sepolia relayer submits through [`PrivacyPoolRelay.relay(...)`](/protocol/contracts/privacy-pool-relay), which calls `PoolVault.transact(...)`, so the proof binds `processor` to the PrivacyPoolRelay contract for transfers, payment-request fulfillment, and withdrawals alike. Bind whatever processor your relayer designates in `RelayerInfo.processorAddress`.
