> ## Documentation Index
> Fetch the complete documentation index at: https://developers.paxoslabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Withdrawals

> Submit, list, and cancel vault withdrawal orders through the WithdrawQueue.

Withdrawals redeem vault share tokens for the underlying `wantAsset` through the `WithdrawQueue` contract. Unlike deposits, **there is no inline-permit path** in `1.0.0`: the share token must have an ERC-20 `approve(WithdrawQueue, amount)` allowance before `submitOrder` can pull the shares.

Throughout this guide, `client` refers to a singleton `AmplifyClient` created on the server — see [Project setup](./project-setup) for the wiring. All amounts are base-units decimal strings.

<Warning>
  Withdrawals have no permit path. For the share token, `authorization.detect` always returns `method: 'approval'` or `'already_approved'` — never `'permit'`. Approve the `WithdrawQueue` before calling `submitOrder`.
</Warning>

<Steps>
  <Step title="Check the share-token allowance">
    Call `authorization.detect` with `tokenAddress` set to the **vault address** (the share token is the vault contract). The response tells you whether you need to send an `approve` transaction or whether sufficient allowance already exists.

    ```ts theme={null}
    const auth = await client.core.authorization.detect({
      vaultAddress: '0xbbbb000000000000000000000000000000000001',
      tokenAddress: '0xbbbb000000000000000000000000000000000001', // share token = vault
      amount: shareAmount, // base-units decimal string
      userAddress,
      chainId: 1,
    })
    ```

    Two possible response shapes:

    * `auth.method === 'already_approved'` — skip to Step 3.
    * `auth.method === 'approval'` — submit `auth.approvalTransaction.encoded` to the share-token address (the vault address). The `spender` baked into the calldata is the `WithdrawQueue` address.
  </Step>

  <Step title="Approve the WithdrawQueue (if needed)">
    For the `approval` branch, just forward the returned transaction:

    ```ts theme={null}
    import { useSendTransaction, usePublicClient } from 'wagmi'

    const { sendTransactionAsync } = useSendTransaction()
    const publicClient = usePublicClient()

    if (auth.method === 'approval') {
      const approvalHash = await sendTransactionAsync({
        to: vaultAddress, // share token = vault contract
        data: auth.approvalTransaction.encoded as Hex,
        chainId,
      })
      await publicClient!.waitForTransactionReceipt({ hash: approvalHash })
    }
    ```

    <Info>
      The `spender` in the `approve` call is always the `WithdrawQueue` address, never the vault address. Using the vault address as `spender` will cause `submitOrder` to panic with `0x11` when it tries to `transferFrom` the shares.
    </Info>
  </Step>

  <Step title="(Optional) Estimate the fee">
    `client.amplify.withdraw.calculateFee` returns the fee the user will pay for a given offer amount. Use it both to render a preview in your UI and to short-circuit submissions that would revert on-chain.

    ```ts theme={null}
    const fee = await client.amplify.withdraw.calculateFee({
      offerAmount: shareAmount, // base-units decimal string of shares to redeem
      wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
      vaultAddress: '0xbbbb000000000000000000000000000000000001',
      chainId: 1,
    })

    // fee = {
    //   feeAmount,                          // base-units decimal string of `wantAsset`
    //   offerFeePercentage: { bps, percentage },
    //   flatFee,
    // }

    if (BigInt(fee.feeAmount) >= BigInt(shareAmount)) {
      throw new Error(
        'Amount is too small — fees would consume the entire withdrawal. Increase the amount and retry.'
      )
    }
    ```
  </Step>

  <Step title="Prepare the withdrawal">
    ```ts theme={null}
    const prepared = await client.amplify.withdraw.prepare({
      vaultAddress: '0xbbbb000000000000000000000000000000000001',
      wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
      shareAmount, // base units of the share token
      userAddress,
      chainId: 1,
      // Optional: override default depositor / receiver / refund receiver
      // intendedDepositor,
      // receiver,
      // refundReceiver,
    })

    // prepared.transaction = { to: WithdrawQueue, data: submitOrder calldata, value: '0' }
    ```

    Required fields:

    * `vaultAddress` — BoringVault contract address.
    * `wantAsset` — ERC-20 the user wants to receive on settlement.
    * `shareAmount` — vault shares to redeem (base-units decimal string).
    * `userAddress` — wallet submitting the order. Also the default `intendedDepositor`, `receiver`, and `refundReceiver`.
    * `chainId` — EVM chain ID.

    Optional fields:

    * `intendedDepositor` — on-chain `SubmitOrderParams.intendedDepositor`. Defaults to `userAddress`.
    * `receiver` — address that receives `wantAsset` on settlement. Defaults to `userAddress`.
    * `refundReceiver` — address that receives refunded shares if the order is cancelled. Defaults to `userAddress`.
    * `responseFormat` — `'encoded'` (default), `'full'`, or `'structured'`.
  </Step>

  <Step title="Submit the transaction">
    ```ts theme={null}
    import type { Address, Hex } from 'viem'

    const tx = prepared.transaction
    const withdrawHash = await sendTransactionAsync({
      to: tx.to as Address,
      data: tx.data as Hex,
      value: BigInt(tx.value),
      chainId,
    })

    await publicClient!.waitForTransactionReceipt({ hash: withdrawHash })
    ```
  </Step>
</Steps>

## End-to-end example

```ts theme={null}
import { AmplifyClient } from '@paxoslabs/amplify-sdk'
import type { Address, Hex } from 'viem'
import {
  createPublicClient,
  createWalletClient,
  custom,
  http,
} from 'viem'
import { mainnet } from 'viem/chains'

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})

async function withdraw({
  userAddress,
  vaultAddress,
  wantAsset,
  shareAmount,
  chainId,
}: {
  userAddress: Address
  vaultAddress: Address
  wantAsset: Address
  shareAmount: string
  chainId: number
}) {
  const publicClient = createPublicClient({ chain: mainnet, transport: http() })
  const walletClient = createWalletClient({
    account: userAddress,
    chain: mainnet,
    transport: custom(window.ethereum),
  })

  // 1. Check the share-token allowance
  const auth = await client.core.authorization.detect({
    vaultAddress,
    tokenAddress: vaultAddress, // share token = vault contract
    amount: shareAmount,
    userAddress,
    chainId,
  })

  // 2. Approve the WithdrawQueue if needed
  if (auth.method === 'approval') {
    const hash = await walletClient.sendTransaction({
      to: vaultAddress, // share token = vault contract
      data: auth.approvalTransaction.encoded as Hex,
    })
    await publicClient.waitForTransactionReceipt({ hash })
  }

  // 3. Prepare
  const prepared = await client.amplify.withdraw.prepare({
    vaultAddress,
    wantAsset,
    shareAmount,
    userAddress,
    chainId,
  })

  // 4. Submit
  const tx = prepared.transaction
  const withdrawHash = await walletClient.sendTransaction({
    to: tx.to as Address,
    data: tx.data as Hex,
    value: BigInt(tx.value),
  })
  await publicClient.waitForTransactionReceipt({ hash: withdrawHash })

  return withdrawHash
}
```

## Listing a user's withdrawals

`client.amplify.withdraw.listRequests` supports cursor pagination and an AIP-160-style `filter` string. Available filter keys: `status` (`PENDING`, `COMPLETE`, `PENDING_REFUND`, `REFUNDED`), `chainId`, `wantAssetAddress`, `vaultAddress`, `userAddress`, `receiverAddress`, `refundReceiverAddress`, `orderIndex`, `isSubmittedViaSignature`, `isForceProcessed`, `isMarkedForRefund`, `isMarkedForRefundByUser`, `didOrderFailTransfer`.

```ts theme={null}
const firstPage = await client.amplify.withdraw.listRequests({
  filter: `userAddress=${userAddress} AND vaultAddress=${VAULT} AND status=PENDING`,
  pageSize: 25,
})

// firstPage = { withdrawalRequests, nextPageToken }
for (const r of firstPage.withdrawalRequests) {
  // r.orderIndex, r.orderAmount, r.wantAssetAddress, r.status, ...
}

// Paginate by passing the previous nextPageToken back in.
let pageToken = firstPage.nextPageToken
while (pageToken) {
  const next = await client.amplify.withdraw.listRequests({
    filter: `userAddress=${userAddress} AND vaultAddress=${VAULT}`,
    pageSize: 25,
    pageToken,
  })
  // ...consume next.withdrawalRequests
  pageToken = next.nextPageToken
}
```

## Cancelling a pending order

`orderIndex` comes from a row returned by `listRequests`. Cancel returns calldata for the `WithdrawQueue.cancel` (or equivalent) call; submit it the same way as the deposit/withdrawal flows above.

```ts theme={null}
import type { Address, Hex } from 'viem'

const prepared = await client.amplify.withdraw.cancel({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  orderIndex: '42', // decimal string, from listRequests
  chainId: 1,
})

const tx = prepared.transaction
const cancelHash = await sendTransactionAsync({
  to: tx.to as Address,
  data: tx.data as Hex,
  value: BigInt(tx.value),
  chainId,
})

await publicClient!.waitForTransactionReceipt({ hash: cancelHash })
```

## Converting share amounts

```ts theme={null}
import { parseUnits } from 'viem'

// Read the share-token decimals live from the vault contract.
const decimals = await publicClient.readContract({
  address: VAULT,
  abi: [{ type: 'function', name: 'decimals', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' }],
  functionName: 'decimals',
})

const shareAmount = parseUnits(userInput, decimals).toString()
```

## Error handling

```ts theme={null}
import { AmplifyError, AmplifyTimeoutError } from '@paxoslabs/amplify-sdk'

try {
  await client.amplify.withdraw.prepare({ /* ... */ })
} catch (err) {
  if (err instanceof AmplifyTimeoutError) {
    // Retry, surface a timeout UI, etc.
  } else if (err instanceof AmplifyError) {
    // err.statusCode  number  — HTTP status (e.g. 400, 401, 500)
    // err.body        unknown — parsed backend error body (when JSON)
    // err.message     string  — human-readable summary
    // err.rawResponse RawResponse — the original Response
    console.error('Amplify error', err.statusCode, err.message)
  } else {
    throw err
  }
}
```

When surfacing errors to the browser, log `err.body` and `err.rawResponse` server-side and return a generic message to the client.

## Next steps

* [Deposits guide](./deposits) — the inverse flow, including permit support.
* [AI Coding Reference](../sdk-ai-reference) — all parameters and response shapes for every subclient.
* [Migrating from 0.5.x](../migrating-from-0-5) — field-by-field rename table for `prepare`, `cancel`, and `listRequests`.
