Skip to main content
This guide walks you through withdrawing from an Amplify vault 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:
1

Approve vault shares

Grant the WithdrawQueue permission to transfer your vault shares by calling approve() on the BoringVault (share token) contract.
2

Submit a withdrawal order

Call submitOrder() on the WithdrawQueue. Your shares are locked and a withdrawal order is created.
3

Vault operator fulfills the order

The vault operator processes the queue (typically within 24 hours) and transfers the want asset (e.g., USDC) to your receiver address.
Why can’t I withdraw instantly? Vault assets may be deployed in DeFi strategies. The vault operator needs time to unwind positions and source liquidity. This is standard across DeFi vault protocols.

What You’ll Need

RequirementDescription
Contract addressesboringVaultAddress, withdrawQueueAddress, and accountantAddress — see Vault Discovery
ABIProvided below
RPC endpointAn Ethereum node URL
Private key or walletTo sign and send transactions
Vault sharesYou must have shares from a prior deposit

ABI Reference

WithdrawQueue ABI

[
  {
    "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

[
  {
    "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)

[
  {
    "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)

[
  {
    "inputs": [],
    "name": "getRateInQuoteSafe",
    "outputs": [{ "name": "rate", "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]

Contract Method Reference

submitOrder(params)

FieldTypeDescription
amountOfferuint256Vault shares to offer. Shares have 18 decimals, so 1000000000000000000 = 1 share.
wantAssetaddressToken you want to receive (e.g., USDC address)
intendedDepositoraddressMust match the caller (msg.sender)
receiveraddressWhere the want asset is sent when fulfilled
refundReceiveraddressWhere vault shares are returned if the order is cancelled
signatureParamstupleFor 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:
FieldValue
approvalMethod0 (EIP20_APPROVE)
approvalV0
approvalR0x + 32 zero bytes
approvalS0x + 32 zero bytes
submitWithSignaturefalse
deadline0
eip2612Signature0x (empty bytes)

Order Statuses

ValueStatusMeaning
0NOT_FOUNDOrder doesn’t exist
1PENDINGWaiting for the vault operator to fulfill
2COMPLETEFulfilled — want asset sent to receiver
3COMPLETE_PRE_FILLEDFilled during submission
4PENDING_REFUNDBeing refunded
5COMPLETE_REFUNDEDCancelled — shares returned
6FAILED_TRANSFER_REFUNDEDTransfer 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 vault 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 vault shares (18 decimals). To get the fee percentage:
FeeModule.offerFeePercentage() → uint256 feePercentage
The returned value uses 18-decimal precision (e.g., 5000000000000000 = 0.5%).
The fee is subtracted from the vault 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.

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 vault shares. Use the Accountant’s exchange rate to convert.

Contract call

Accountant.getRateInQuoteSafe() → uint256 rate
rate represents the amount of the want asset per 1e18 vault shares.

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
Don’t forget to account for 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).

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

1

Check your share balance (read)

Confirm you hold enough vault shares to withdraw.
BoringVault.balanceOf(yourAddress) → uint256 shareBalance
2

Convert want amount to shares (read)

If you know the want asset amount, convert it to shares.
Accountant.getRateInQuoteSafe() → uint256 rate
Then calculate: sharesNeeded = (wantAmount × 1e18) / rateIf you already know the share amount you want to offer, skip this step.
3

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

Check the minimum order size (read)

Ensure your order meets the minimum.
WithdrawQueue.minimumOrderSize() → uint256 minimumShares
If sharesNeeded < minimumShares, the order will be rejected.
5

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

Approve vault shares to the WithdrawQueue (transaction)

Grant the WithdrawQueue permission to transfer your vault shares.
BoringVault.approve(withdrawQueueAddress, sharesNeeded) → bool
Wait for the transaction to be mined before proceeding.
7

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

Poll for order completion (read)

The vault 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

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

ParameterValue
amountOfferCalculated from exchange rate (e.g., 990099009900...)
wantAsset0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (USDC)
intendedDepositorYour wallet address
receiverYour wallet address
refundReceiverYour wallet address
signatureParamsAll zeros (see empty signature params table above)

Troubleshooting

Your amountOffer is below the queue’s minimum order size. Call minimumOrderSize() to check the threshold, and increase your amount.
The intendedDepositor field doesn’t match the transaction sender. Set it to the wallet address that’s sending the transaction.
The wantAsset address isn’t enabled for this vault. Query the vault config to find supported withdrawal assets.
Vault shares haven’t been approved to the WithdrawQueue. Call BoringVault.approve() first.
Vault operations are temporarily paused by the operator. Wait and retry later. You can check programmatically with Teller.isPaused() — see Pause State.

Next Steps