> ## 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.

# Direct Withdrawals

> Submit withdrawal orders by calling the WithdrawQueue contract directly

This guide walks you through withdrawing from an Amplify account by calling the **WithdrawQueue** smart contract directly. It covers every contract method you need to call, in order, including fee calculation, share conversion, and order status polling.

## How Withdrawals Work

Unlike deposits, withdrawals are **not instant**. They use a queue-based model:

<Steps>
  <Step title="Approve account shares">
    Grant the WithdrawQueue permission to transfer your account shares by calling
    `approve()` on the BoringVault (share token) contract.
  </Step>

  <Step title="Submit a withdrawal order">
    Call `submitOrder()` on the WithdrawQueue. Your shares are locked and a
    withdrawal order is created.
  </Step>

  <Step title="Account operator fulfills the order">
    The account operator processes the queue (typically within 24 hours) and
    transfers the want asset (e.g., USDC) to your receiver address.
  </Step>
</Steps>

<Info>
  **Why can't I withdraw instantly?** Account assets may be deployed in DeFi
  strategies. The account operator needs time to unwind positions and source
  liquidity. This is standard across DeFi account protocols.
</Info>

***

## What You'll Need

| Requirement           | Description                                                                                                                                                                                  |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Contract addresses    | `boringVaultAddress`, `withdrawQueueAddress`, and `accountantAddress` — see [Account Discovery](/v0.5.2/intro/products/earn/developers/guides/direct-contract/vault-queries/vault-discovery) |
| ABI                   | Provided below                                                                                                                                                                               |
| RPC endpoint          | An Ethereum node URL                                                                                                                                                                         |
| Private key or wallet | To sign and send transactions                                                                                                                                                                |
| Account shares        | You must have shares from a [prior deposit](/v0.5.2/intro/products/earn/developers/guides/direct-contract/deposits)                                                                          |

***

## ABI Reference

### WithdrawQueue ABI

```json theme={null}
[
  {
    "inputs": [
      {
        "components": [
          { "name": "amountOffer", "type": "uint256" },
          { "name": "wantAsset", "type": "address" },
          { "name": "intendedDepositor", "type": "address" },
          { "name": "receiver", "type": "address" },
          { "name": "refundReceiver", "type": "address" },
          {
            "components": [
              { "name": "approvalMethod", "type": "uint8" },
              { "name": "approvalV", "type": "uint8" },
              { "name": "approvalR", "type": "bytes32" },
              { "name": "approvalS", "type": "bytes32" },
              { "name": "submitWithSignature", "type": "bool" },
              { "name": "deadline", "type": "uint256" },
              { "name": "eip2612Signature", "type": "bytes" }
            ],
            "name": "signatureParams",
            "type": "tuple"
          }
        ],
        "name": "params",
        "type": "tuple"
      }
    ],
    "name": "submitOrder",
    "outputs": [{ "name": "orderIndex", "type": "uint256" }],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [{ "name": "orderIndex", "type": "uint256" }],
    "name": "getOrderStatus",
    "outputs": [{ "name": "", "type": "uint8" }],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minimumOrderSize",
    "outputs": [{ "name": "", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeModule",
    "outputs": [{ "name": "", "type": "address" }],
    "stateMutability": "view",
    "type": "function"
  }
]
```

### FeeModule ABI

```json theme={null}
[
  {
    "inputs": [
      { "name": "amount", "type": "uint256" },
      { "name": "offerAsset", "type": "address" },
      { "name": "wantAsset", "type": "address" },
      { "name": "receiver", "type": "address" }
    ],
    "name": "calculateOfferFees",
    "outputs": [{ "name": "feeAmount", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "offerFeePercentage",
    "outputs": [{ "name": "", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]
```

### BoringVault ABI (ERC-20 for share approval)

```json theme={null}
[
  {
    "inputs": [
      { "name": "spender", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ],
    "name": "approve",
    "outputs": [{ "name": "", "type": "bool" }],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      { "name": "owner", "type": "address" },
      { "name": "spender", "type": "address" }
    ],
    "name": "allowance",
    "outputs": [{ "name": "", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [{ "name": "account", "type": "address" }],
    "name": "balanceOf",
    "outputs": [{ "name": "", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]
```

### Accountant ABI (for share conversion)

```json theme={null}
[
  {
    "inputs": [
      { "name": "quote", "type": "address" }
    ],
    "name": "getRateInQuoteSafe",
    "outputs": [{ "name": "rateInQuote", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]
```

***

## Contract Method Reference

### `submitOrder(params)`

| Field               | Type      | Description                                                                           |
| ------------------- | --------- | ------------------------------------------------------------------------------------- |
| `amountOffer`       | `uint256` | Account shares to offer. Shares have 18 decimals, so `1000000000000000000` = 1 share. |
| `wantAsset`         | `address` | Token you want to receive (e.g., USDC address)                                        |
| `intendedDepositor` | `address` | **Must match the caller** (`msg.sender`)                                              |
| `receiver`          | `address` | Where the want asset is sent when fulfilled                                           |
| `refundReceiver`    | `address` | Where account shares are returned if the order is cancelled                           |
| `signatureParams`   | `tuple`   | For standard ERC-20 approval flow, pass all zeros (see below)                         |

### Empty Signature Params

When you've already called `approve()`, pass empty signature params to indicate standard ERC-20 approval:

| Field                 | Value                |
| --------------------- | -------------------- |
| `approvalMethod`      | `0` (EIP20\_APPROVE) |
| `approvalV`           | `0`                  |
| `approvalR`           | `0x` + 32 zero bytes |
| `approvalS`           | `0x` + 32 zero bytes |
| `submitWithSignature` | `false`              |
| `deadline`            | `0`                  |
| `eip2612Signature`    | `0x` (empty bytes)   |

### Order Statuses

| Value | Status                     | Meaning                                     |
| ----- | -------------------------- | ------------------------------------------- |
| `0`   | NOT\_FOUND                 | Order doesn't exist                         |
| `1`   | PENDING                    | Waiting for the account operator to fulfill |
| `2`   | COMPLETE                   | Fulfilled — want asset sent to receiver     |
| `3`   | COMPLETE\_PRE\_FILLED      | Filled during submission                    |
| `4`   | PENDING\_REFUND            | Being refunded                              |
| `5`   | COMPLETE\_REFUNDED         | Cancelled — shares returned                 |
| `6`   | FAILED\_TRANSFER\_REFUNDED | Transfer failed — shares returned           |

***

## Withdrawal Fees

The WithdrawQueue charges a fee on withdrawal orders via its **FeeModule**. You should check the fee before submitting to display accurate amounts to your users. The fee is deducted from your `amountOffer` in account shares — meaning you receive the want asset equivalent of `amountOffer - feeAmount`.

### How to read the fee

This requires two contract reads:

**Step 1** — Get the FeeModule address from the WithdrawQueue:

```
WithdrawQueue.feeModule() → address feeModuleAddress
```

**Step 2** — Calculate the fee for your specific withdrawal:

```
FeeModule.calculateOfferFees(
  amount,      // vault share amount (uint256)
  offerAsset,  // BoringVault address (the share token)
  wantAsset,   // token you want to receive (e.g., USDC)
  receiver     // address receiving the want asset
) → uint256 feeAmount
```

The `feeAmount` is in account shares (18 decimals). To get the fee percentage:

```
FeeModule.offerFeePercentage() → uint256 feePercentage
```

The returned value uses 18-decimal precision (e.g., `5000000000000000` = 0.5%).

<Warning>
  The fee is subtracted from the account shares you offer. If you offer 100 shares
  and the fee is 0.5 shares, only 99.5 shares worth of the want asset will be
  sent to the receiver. Account for this when displaying withdrawal amounts to
  users.
</Warning>

***

## Converting Withdrawal Amount to Shares

Users typically think in terms of the want asset (e.g., "I want to withdraw 1,000 USDC"), but `amountOffer` is denominated in **account shares**. Use the Accountant's exchange rate to convert.

### Contract call

```
Accountant.getRateInQuoteSafe(wantAssetAddress) → uint256 rateInQuote
```

`rateInQuote` represents the amount of the want asset per 1e18 account shares. Pass the want asset (withdrawal token) address as the `quote` parameter.

### Conversion formula

To withdraw a specific amount of the want asset:

```
sharesNeeded = (wantAmount × 1e18) / rate
```

For example, to withdraw 1,000 USDC (6 decimals):

```
wantAmount   = 1000 × 1e6 = 1000000000
sharesNeeded = (1000000000 × 1e18) / rate
```

<Info>
  Don't forget to account for [withdrawal fees](#withdrawal-fees) when
  calculating the share amount. If there's a 0.5% fee, you'll need to offer
  slightly more shares: `sharesNeeded / (1 - feePercentage)`.
</Info>

***

## Minimum Order Size

Each WithdrawQueue enforces a minimum order size. Check it before submitting:

```
WithdrawQueue.minimumOrderSize() → uint256 minimumShares
```

If your `amountOffer` is below this threshold, the transaction will revert with `AmountBelowMinimum`.

***

## Withdrawal Walkthrough

<Steps>
  <Step title="Check your share balance (read)">
    Confirm you hold enough account shares to withdraw.

    ```
    BoringVault.balanceOf(yourAddress) → uint256 shareBalance
    ```
  </Step>

  <Step title="Convert want amount to shares (read)">
    If you know the want asset amount, convert it to shares.

    ```
    Accountant.getRateInQuoteSafe(wantAssetAddress) → uint256 rateInQuote
    ```

    Then calculate: `sharesNeeded = (wantAmount × 1e18) / rateInQuote`

    If you already know the share amount you want to offer, skip this step.
  </Step>

  <Step title="Check the withdrawal fee (read)">
    Get the FeeModule address and calculate the fee that will be deducted.

    ```
    WithdrawQueue.feeModule() → address feeModuleAddress

    FeeModule.calculateOfferFees(
      sharesNeeded,          // from Step 2
      boringVaultAddress,    // the share token (offer asset)
      wantAssetAddress,      // e.g., USDC
      receiverAddress        // where want asset goes
    ) → uint256 feeAmount
    ```

    The net shares applied to the withdrawal = `sharesNeeded - feeAmount`. Display this to the user so they know the effective withdrawal amount.
  </Step>

  <Step title="Check the minimum order size (read)">
    Ensure your order meets the minimum.

    ```
    WithdrawQueue.minimumOrderSize() → uint256 minimumShares
    ```

    If `sharesNeeded < minimumShares`, the order will be rejected.
  </Step>

  <Step title="Check existing allowance (read)">
    See if the WithdrawQueue already has permission to transfer your shares.

    ```
    BoringVault.allowance(yourAddress, withdrawQueueAddress) → uint256
    ```

    If the returned value is ≥ your `sharesNeeded`, skip to Step 7.
  </Step>

  <Step title="Approve account shares to the WithdrawQueue (transaction)">
    Grant the WithdrawQueue permission to transfer your account shares.

    ```
    BoringVault.approve(withdrawQueueAddress, sharesNeeded) → bool
    ```

    Wait for the transaction to be mined before proceeding.
  </Step>

  <Step title="Submit the withdrawal order (transaction)">
    Call `submitOrder()` on the WithdrawQueue with empty signature params (since you used ERC-20 approval in Step 6).

    ```
    WithdrawQueue.submitOrder({
      amountOffer:       sharesNeeded,
      wantAsset:         wantAssetAddress,      // e.g., USDC
      intendedDepositor: yourAddress,            // must be msg.sender
      receiver:          receiverAddress,        // where want asset goes
      refundReceiver:    yourAddress,            // where shares go if cancelled
      signatureParams: {
        approvalMethod:      0,
        approvalV:           0,
        approvalR:           bytes32(0),
        approvalS:           bytes32(0),
        submitWithSignature: false,
        deadline:            0,
        eip2612Signature:    0x
      }
    }) → uint256 orderIndex
    ```

    The return value `orderIndex` uniquely identifies your order. Save it to check status or cancel later.

    You can also extract `orderIndex` from the `OrderSubmitted` event in the transaction receipt logs.
  </Step>

  <Step title="Poll for order completion (read)">
    The account operator typically fulfills orders within 24 hours. Check the status:

    ```
    WithdrawQueue.getOrderStatus(orderIndex) → uint8 status
    ```

    Poll periodically (e.g., every 60 seconds) until `status ≥ 2`:

    * `2` (COMPLETE) — Want asset has been sent to your receiver address
    * `5` (COMPLETE\_REFUNDED) — Order was cancelled, shares returned
    * `6` (FAILED\_TRANSFER\_REFUNDED) — Transfer failed, shares returned
  </Step>
</Steps>

### Example values (withdrawing \~1,000 USDC worth of shares on Ethereum mainnet)

| Parameter           | Value                                                   |
| ------------------- | ------------------------------------------------------- |
| `amountOffer`       | Calculated from exchange rate (e.g., `990099009900...`) |
| `wantAsset`         | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` (USDC)     |
| `intendedDepositor` | Your wallet address                                     |
| `receiver`          | Your wallet address                                     |
| `refundReceiver`    | Your wallet address                                     |
| `signatureParams`   | All zeros (see empty signature params table above)      |

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="AmountBelowMinimum">
    Your `amountOffer` is below the queue's minimum order size. Call
    `minimumOrderSize()` to check the threshold, and increase your amount.
  </Accordion>

  <Accordion title="InvalidDepositor">
    The `intendedDepositor` field doesn't match the transaction sender. Set it
    to the wallet address that's sending the transaction.
  </Accordion>

  <Accordion title="AssetNotSupported">
    The `wantAsset` address isn't enabled for this account. Query the account config
    to find supported withdrawal assets.
  </Accordion>

  <Accordion title="PermitFailedAndAllowanceTooLow">
    Account shares haven't been approved to the WithdrawQueue. Call
    `BoringVault.approve()` first.
  </Accordion>

  <Accordion title="TellerIsPaused">
    Account operations are temporarily paused by the operator. Wait and retry
    later. You can check programmatically with `Teller.isPaused()` — see
    [Pause State](/v0.5.2/intro/products/earn/developers/guides/direct-contract/vault-queries/pause-state).
  </Accordion>
</AccordionGroup>

***

## Next Steps

* [Direct Cancellations](/v0.5.2/intro/products/earn/developers/guides/direct-contract/cancellations) — Cancel a pending withdrawal order
* [Account Queries & Monitoring](/v0.5.2/intro/products/earn/developers/guides/direct-contract/vault-queries/index) — Read withdrawal fees, check pause state, monitor withdrawal history
* [Direct Deposits](/v0.5.2/intro/products/earn/developers/guides/direct-contract/deposits) — Deposit tokens into an account
* [SDK Withdrawals Guide](/v0.5.2/intro/products/earn/developers/guides/withdrawals) — Use the Amplify SDK instead
