# Yield withdraw

> Unshield a yield-share note and unwrap it to the underlying for a recipient in one router call, with a slippage floor on what they receive.

A yield withdrawal spends [yield-share](/concepts/yield-shares) notes (ppUSDC) and lands their value at a public address as the underlying (USDC). It is a standard [withdrawal](/operations/withdraw) with one substitution: the proof binds [PPRouter](/protocol/contracts/pp-router) as its processor instead of PrivacyPoolRelay, so the vault pays the shares to the router, which unwraps them and pays the recipient.

## How a yield withdrawal works

```mermaid
sequenceDiagram
    participant You
    participant Router as PPRouter
    participant PoolVault
    participant Zap as PPYieldTokenZap
    participant Recipient as Recipient address

    Note over You: transact proof, bound to the recipientand to PPRouter as processor
    You->>Router: withdrawToUnderlying(proof, params, noteData, minUnderlyingOut)
    Router->>PoolVault: transact
    PoolVault-->>Router: amountOut in ppUSDC shares
    Router->>Zap: redeemToUnderlying (recipient leg, then fee leg)
    Zap-->>Recipient: USDC ≥ minUnderlyingOut
```

1.  **Prove.** The SDK builds the normal withdrawal proof against the router, binding a `PayoutRouting` (`recipient`, `feeRecipient`, `feeAmount`, `nativeGas`) as the transact params. `amountOut` is the shares leaving the pool; `recipientShares = amountOut - feeAmount`.
2.  **Size the floor.** The session reads `PPRouter.quoteWithdraw(recipientShares)` and sets `minUnderlyingOut` to that figure minus a slippage allowance (`WITHDRAW_SLIPPAGE_BPS`, 1% by default, overridable per call). The floor protects the recipient leg against a rate move or a fee change between quote and execution. It is a router parameter, not a proof signal.
3.  **Submit.** Either you submit `withdrawToUnderlying` yourself, or a relayer does. The router requires `processor == address(this)`, pulls the shares from the vault, unwraps the recipient's shares (reverting if the underlying out is below the floor), unwraps the fee leg for the fee recipient, and forwards any `nativeGas` to the recipient. A fee that would redeem to zero underlying is waived rather than failing the withdrawal.

In native mode (a ppETH wrapper) the router unwraps WETH once and pays the recipient their underlying plus the gas top-up as ETH in a single transfer.

## How to withdraw

Yield methods live on a per-deployment namespace rather than on `PoolSession` directly. `session.yieldFor(token)` returns the `YieldSessions` for the wrapper or underlying you pass (or `undefined` when no yield deployment is configured):

```ts
const yieldSessions = session.yieldFor(ppUsdcAddress);
if (!yieldSessions) throw new Error("no yield deployment configured for this chain");

// Self-submit: one router transaction, gas paid by you.
const prepared = await yieldSessions.withdraw.prepareYieldWithdraw({
    inputCommitments: [yieldNote.commitment],
    amount: sharesToRelease,          // Hex, in ppUSDC shares
    amountOut: sharesLeavingThePool,  // Hex, in ppUSDC shares (includes any fee)
    recipient: publicDestination,
    slippageBps: 100n,                // optional, default WITHDRAW_SLIPPAGE_BPS
});

console.log("recipient gets at least", prepared.minUnderlyingOut, "USDC units");
// submit prepared.withdrawTx from your wallet (value = nativeGas), then
// persist prepared.changePendingNotes and mark prepared.spentNotes spent once mined
```

For a gasless, relayed withdrawal, take a withdrawal quote from the relayer for the share asset, then bind it:

```ts
const relayed = await yieldSessions.withdraw.prepareYieldWithdrawRelay({
    inputCommitments: [yieldNote.commitment],
    amount: sharesToRelease,
    amountOut: sharesLeavingThePool,
    recipient: publicDestination,
    signedFeeCommitment: quote.feeCommitment,   // from the relayer's withdrawal quote
    extraGas: false,
});

// relayed.relayParams carries processorAddress = PPRouter and minUnderlyingOut;
// hand it to the relayer exactly as you would a plain withdrawal.
const txHash = await relayerInteractor.relayWithdrawal(relayerInfo, relayed.relayParams);
```

The prepared result also exposes `recipientShares`, `quotedUnderlyingOut`, and `minUnderlyingOut`, so a UI can show what the recipient will get before submitting.

## Constraints

-   **One wrapper per router.** `tokenIdOut` must be the router's own share token; a note in a different asset takes the plain [withdraw](withdraw) path.
-   **Cannot be batched.** A yield-to-underlying withdrawal binds the router as processor, and a [batch item](batch-withdraw) must bind PrivacyPoolRelay. To move many yield notes at once, withdraw them as shares through the plain relay and unwrap afterwards.
-   **Depends on Aave liquidity.** The unwrap ends in Aave's `withdraw`. If the reserve is paused, frozen, or fully utilised, the router reverts and the note is not spent; withdraw the shares as shares instead and unwrap later, or exit to the aToken with `PPYieldToken.redeem`, which never calls Aave.
-   **The floor is not proof-bound.** `minUnderlyingOut` is set by whoever submits, so on the relayed path the SDK sizes it from an honest quote and passes it in the typed relay params; the relayer forwards it unchanged.
-   **Fee in shares.** On the relayed path the relayer quotes the share asset through its `Underlying` price alias, scaling the underlying's price by the wrapper's live `previewRedeem`, so the committed `feeAmount` is denominated in shares and tracks accrued yield.

## Behind the scenes

SDK call `session.yieldFor(token).withdraw.prepareYieldWithdraw(...)` for self-submit, or `.prepareYieldWithdrawRelay(...)` then `relayerInteractor.relayWithdrawal(...)` for the relayed path.
Contract method [`PPRouter`](/protocol/contracts/pp-router)`.withdrawToUnderlying(proof, transactParams, noteData, minUnderlyingOut)`, which calls `PoolVault.transact` and then `PPYieldTokenZap.redeemToUnderlying` once per leg.
Relayer route The normal withdrawal quote and relay routes, with `processorAddress` set to the router. The relayer requires that address to be in its `allowed_processors` and routes the request to `withdrawToUnderlying`, appending `minUnderlyingOut`. Discover the router from `/v1/details` (`yield[].router`).
Circuit [`transact_NxM`](/protocol/circuits/transact) with non-zero `amountOut` and `tokenIdOut = ppUSDC`; nothing yield-specific is proven.

## Loose shares after a ragequit

[Ragequit](ragequit) pays the note's `tokenId`, so a yield note ragequits to ppUSDC shares in the owner's wallet, not USDC. Two exits exist for loose shares: [`PPYieldTokenZap`](/protocol/contracts/pp-yield-token-zap)`.redeemToUnderlying(shares, receiver)` unwraps to USDC through Aave in one call, and `PPYieldToken.redeem(shares, receiver, owner)` exits to the aToken without touching Aave. The SDK exposes both ABIs; neither is a pool operation.

## What's next

-   [Yield deposit](yield-deposit): the inbound half.
-   [Withdraw and swap](withdraw-and-swap): spend a yield note into a different token by unwrapping through `RelaySwaps` instead.
-   [Manage notes](manage-notes): the change note, if any, is an ordinary yield-share note.
