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

> Complete withdrawal integration walkthrough using the Paxos Labs REST API calldata endpoints

This guide walks through the complete withdrawal flow: discover the account, approve share spending, prepare the withdrawal order calldata, submit, and monitor status.

<Info>
  Withdrawals on Amplify are **asynchronous**. Submitting a withdrawal order
  places it in a queue. Once processed by the protocol, the requested asset is
  transferred to the user. Monitor order status via `GET
      /v2/amplify/withdrawalRequests`.
</Info>

## Prerequisites

* A Paxos Labs API key
* An EVM-compatible signer (private key, HSM, or wallet service)
* The user must hold vault shares (the BoringVault ERC-20 token) from a prior deposit

## Step 0: Fetch Account Details

Retrieve the account's contract addresses. You need the `boringVaultAddress` (the share token) and the `withdrawQueueAddress` (the approval spender).

```bash theme={null}
curl "https://api.paxoslabs.com/v2/amplify/vaults?filter=chainId%3D1" \
  -H "x-api-key: pxl_your_key"
```

| Field                  | Usage                                                          |
| ---------------------- | -------------------------------------------------------------- |
| `boringVaultAddress`   | The ERC-20 share token contract; also the `vaultAddress` param |
| `withdrawQueueAddress` | The spender address for the share approval                     |

## Step 1: Approve Share Spending

Before submitting a withdrawal order, the `WithdrawQueue` contract must be approved to spend the user's vault shares.

Construct a standard ERC-20 `approve(spender, amount)` call:

* **Token contract**: the `boringVaultAddress`
* **Spender**: the `withdrawQueueAddress`
* **Amount**: the share amount to withdraw (in share token base units, 18 decimals)

Submit this approval transaction and wait for confirmation before proceeding.

## Step 2: Prepare Withdrawal Calldata

`GET /v2/amplify/withdraw` returns the transaction to submit a withdrawal order.

### Parameters

| Parameter           | Type     | Required | Description                                                                                                                               |
| ------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `vaultAddress`      | `string` | Yes      | BoringVault contract address (`0x` + 40 hex chars)                                                                                        |
| `wantAsset`         | `string` | Yes      | ERC-20 token address to receive upon withdrawal                                                                                           |
| `shareAmount`       | `string` | Yes      | Vault share amount to redeem, in share token base units (decimal string, 18 decimals)                                                     |
| `userAddress`       | `string` | Yes      | Wallet submitting the withdrawal. Also the default for `intendedDepositor`, `receiver`, and `refundReceiver` when those are omitted.      |
| `chainId`           | `number` | Yes      | EVM chain ID                                                                                                                              |
| `intendedDepositor` | `string` | No       | On-chain `SubmitOrderParams.intendedDepositor`. Defaults to `userAddress`.                                                                |
| `receiver`          | `string` | No       | On-chain `SubmitOrderParams.receiver` — address credited with the `wantAsset` on settlement. Defaults to `userAddress`.                   |
| `refundReceiver`    | `string` | No       | On-chain `SubmitOrderParams.refundReceiver` — address credited with refunded shares if the order is cancelled. Defaults to `userAddress`. |
| `responseFormat`    | `string` | No       | `encoded` (default), `full`, or `structured`                                                                                              |

<Info>
  The server decides whether to queue the order (`submitOrder`) or settle it atomically (`submitOrderAndProcessAll`) based on the account's on-chain RolesAuthority configuration. You do not need to pass an `atomic` flag — atomic-eligible accounts are routed automatically.
</Info>

### Response

```json theme={null}
{
  "transaction": {
    "to": "0xdddd000000000000000000000000000000000001",
    "data": "0x1a2b3c4d...",
    "value": "0",
    "abi": [{"type": "function", "name": "submitOrder", "inputs": [...]}],
    "functionName": "submitOrder",
    "args": [...]
  }
}
```

## Step 3: Sign and Submit

Broadcast the transaction using the `to`, `data`, and `value` fields. The vault shares are locked in the WithdrawQueue upon confirmation.

## Step 4: Monitor Status

Poll `GET /v2/amplify/withdrawalRequests` to track order progress. Omit the `status` predicate so the order remains visible as it transitions through terminal states:

```bash theme={null}
curl "https://api.paxoslabs.com/v2/amplify/withdrawalRequests?\
filter=userAddress%3D0x1234...%20AND%20vaultAddress%3D0xbbbb..." \
  -H "x-api-key: pxl_your_key"
```

| Status           | Meaning                                       |
| ---------------- | --------------------------------------------- |
| `PENDING`        | Order is in the queue, waiting for processing |
| `COMPLETE`       | Assets have been transferred to the user      |
| `PENDING_REFUND` | Order is being refunded                       |
| `REFUNDED`       | Vault shares have been returned to the user   |

Filtering on `status=PENDING` will cause the order to disappear from the response as soon as it reaches a terminal state, which hides the final outcome from readers polling for completion.

## Complete Examples

<Tabs>
  <Tab title="Node.js">
    ```ts theme={null}
    import { createWalletClient, createPublicClient, http, encodeFunctionData, erc20Abi } from "viem";
    import { mainnet } from "viem/chains";
    import { privateKeyToAccount } from "viem/accounts";

    const API_KEY = process.env.AMPLIFY_API_KEY!;
    const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
    const BASE = "https://api.paxoslabs.com";
    const HEADERS = { "x-api-key": API_KEY };

    const VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001";
    const WITHDRAW_QUEUE = "0xdddd000000000000000000000000000000000001";
    const WANT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
    const CHAIN_ID = 1;
    const SHARE_AMOUNT = "1000000000000000000"; // 1 share (18 decimals)

    async function main() {
      const account = privateKeyToAccount(PRIVATE_KEY);
      const walletClient = createWalletClient({
        account,
        chain: mainnet,
        transport: http(),
      });
      const publicClient = createPublicClient({
        chain: mainnet,
        transport: http(),
      });

      // Step 1: Approve share spending
      console.log("Approving share spending...");
      const approveData = encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: [WITHDRAW_QUEUE as `0x${string}`, BigInt(SHARE_AMOUNT)],
      });
      const approveHash = await walletClient.sendTransaction({
        to: VAULT_ADDRESS as `0x${string}`,
        data: approveData,
        chain: mainnet,
        account,
      });
      await publicClient.waitForTransactionReceipt({ hash: approveHash });
      console.log(`Approval confirmed: ${approveHash}`);

      // Step 2: Get withdrawal calldata
      console.log("Fetching withdrawal calldata...");
      const withdrawUrl = new URL(`${BASE}/v2/amplify/withdraw`);
      withdrawUrl.searchParams.set("vaultAddress", VAULT_ADDRESS);
      withdrawUrl.searchParams.set("wantAsset", WANT_ASSET);
      withdrawUrl.searchParams.set("shareAmount", SHARE_AMOUNT);
      withdrawUrl.searchParams.set("userAddress", account.address);
      withdrawUrl.searchParams.set("chainId", String(CHAIN_ID));

      const withdrawResp = await fetch(withdrawUrl, { headers: HEADERS }).then(
        (r) => r.json()
      );
      const tx = withdrawResp.transaction;

      // Step 3: Sign and submit
      console.log("Submitting withdrawal order...");
      const hash = await walletClient.sendTransaction({
        to: tx.to as `0x${string}`,
        data: tx.data as `0x${string}`,
        value: BigInt(tx.value),
        chain: mainnet,
        account,
      });
      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      console.log(`Withdrawal submitted in block ${receipt.blockNumber}: ${hash}`);

      // Step 4: Poll status (no status filter — terminal states stay visible)
      console.log("Polling withdrawal status...");
      const statusUrl = new URL(`${BASE}/v2/amplify/withdrawalRequests`);
      statusUrl.searchParams.set(
        "filter",
        `userAddress=${account.address} AND vaultAddress=${VAULT_ADDRESS}`
      );
      const statusResp = await fetch(statusUrl, { headers: HEADERS }).then((r) =>
        r.json()
      );
      console.log(
        `Orders: ${statusResp.withdrawalRequests
          .map((o: { status: string }) => o.status)
          .join(", ")}`
      );
    }

    main().catch(console.error);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import requests
    from web3 import Web3

    API_KEY = os.environ["AMPLIFY_API_KEY"]
    PRIVATE_KEY = os.environ["PRIVATE_KEY"]
    BASE = "https://api.paxoslabs.com"
    HEADERS = {"x-api-key": API_KEY}

    VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001"
    WITHDRAW_QUEUE = "0xdddd000000000000000000000000000000000001"
    WANT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
    CHAIN_ID = 1
    SHARE_AMOUNT = "1000000000000000000"  # 1 share (18 decimals)

    w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
    account = w3.eth.account.from_key(PRIVATE_KEY)

    ERC20_ABI = [{"inputs":[{"name":"spender","type":"address"},
        {"name":"amount","type":"uint256"}],"name":"approve",
        "outputs":[{"name":"","type":"bool"}],"type":"function"}]

    # Step 1: Approve share spending
    print("Approving share spending...")
    vault_token = w3.eth.contract(
        address=Web3.to_checksum_address(VAULT_ADDRESS), abi=ERC20_ABI
    )
    approve_tx = vault_token.functions.approve(
        Web3.to_checksum_address(WITHDRAW_QUEUE),
        int(SHARE_AMOUNT),
    ).build_transaction({
        "from": account.address,
        "nonce": w3.eth.get_transaction_count(account.address),
        "chainId": CHAIN_ID,
    })
    signed = account.sign_transaction(approve_tx)
    tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
    w3.eth.wait_for_transaction_receipt(tx_hash)
    print(f"Approval confirmed: {tx_hash.hex()}")

    # Step 2: Get withdrawal calldata
    print("Fetching withdrawal calldata...")
    withdraw_resp = requests.get(f"{BASE}/v2/amplify/withdraw", headers=HEADERS, params={
        "vaultAddress": VAULT_ADDRESS,
        "wantAsset": WANT_ASSET,
        "shareAmount": SHARE_AMOUNT,
        "userAddress": account.address,
        "chainId": CHAIN_ID,
    }).json()
    tx = withdraw_resp["transaction"]

    # Step 3: Sign and submit
    print("Submitting withdrawal order...")
    withdraw_tx = {
        "to": Web3.to_checksum_address(tx["to"]),
        "data": tx["data"],
        "value": int(tx["value"]),
        "gas": 300_000,
        "nonce": w3.eth.get_transaction_count(account.address),
        "chainId": CHAIN_ID,
    }
    signed = account.sign_transaction(withdraw_tx)
    tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
    print(f"Withdrawal submitted in block {receipt['blockNumber']}: {tx_hash.hex()}")

    # Step 4: Poll status (no status filter — terminal states stay visible)
    print("Polling withdrawal status...")
    status_resp = requests.get(f"{BASE}/v2/amplify/withdrawalRequests", headers=HEADERS, params={
        "filter": f"userAddress={account.address} AND vaultAddress={VAULT_ADDRESS}",
    }).json()
    statuses = [o["status"] for o in status_resp["withdrawalRequests"]]
    print(f"Orders: {', '.join(statuses)}")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "math/big"
        "net/http"
        "net/url"
        "os"
        "strings"

        "github.com/ethereum/go-ethereum/accounts/abi"
        "github.com/ethereum/go-ethereum/accounts/abi/bind"
        "github.com/ethereum/go-ethereum/common"
        "github.com/ethereum/go-ethereum/core/types"
        "github.com/ethereum/go-ethereum/crypto"
        "github.com/ethereum/go-ethereum/ethclient"
    )

    const (
        baseURL        = "https://api.paxoslabs.com"
        vaultAddress   = "0xbbbb000000000000000000000000000000000001"
        withdrawQueue  = "0xdddd000000000000000000000000000000000001"
        wantAsset      = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
        chainID        = 1
        shareAmount    = "1000000000000000000"
    )

    func apiGet(path string, params url.Values) (map[string]interface{}, error) {
        u := fmt.Sprintf("%s%s?%s", baseURL, path, params.Encode())
        req, _ := http.NewRequest("GET", u, nil)
        req.Header.Set("x-api-key", os.Getenv("AMPLIFY_API_KEY"))
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        var result map[string]interface{}
        json.Unmarshal(body, &result)
        return result, nil
    }

    func main() {
        pk, _ := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))
        fromAddr := crypto.PubkeyToAddress(pk.PublicKey)
        client, _ := ethclient.Dial("https://eth.llamarpc.com")
        ctx := context.Background()
        signer := types.NewEIP155Signer(big.NewInt(chainID))

        // Step 1: Approve share spending
        fmt.Println("Approving share spending...")
        erc20ABI, _ := abi.JSON(strings.NewReader(
            `[{"inputs":[{"name":"spender","type":"address"},` +
            `{"name":"amount","type":"uint256"}],` +
            `"name":"approve","outputs":[{"type":"bool"}],"type":"function"}]`))
        amt := new(big.Int)
        amt.SetString(shareAmount, 10)
        approveData, _ := erc20ABI.Pack("approve", common.HexToAddress(withdrawQueue), amt)
        nonce, _ := client.PendingNonceAt(ctx, fromAddr)
        gasPrice, _ := client.SuggestGasPrice(ctx)
        approveTx := types.NewTransaction(nonce, common.HexToAddress(vaultAddress),
            big.NewInt(0), 60000, gasPrice, approveData)
        signedApprove, _ := types.SignTx(approveTx, signer, pk)
        client.SendTransaction(ctx, signedApprove)
        fmt.Printf("Approval submitted: %s\n", signedApprove.Hash().Hex())

        // Wait for approval receipt before requesting withdrawal calldata —
        // otherwise the withdrawal can race ahead of the allowance.
        if _, err := bind.WaitMined(ctx, client, signedApprove); err != nil {
            panic(err)
        }
        fmt.Println("Approval confirmed")

        // Step 2: Get withdrawal calldata
        fmt.Println("Fetching withdrawal calldata...")
        withdrawResp, _ := apiGet("/v2/amplify/withdraw", url.Values{
            "vaultAddress": {vaultAddress},
            "wantAsset":    {wantAsset},
            "shareAmount":  {shareAmount},
            "userAddress":  {fromAddr.Hex()},
            "chainId":      {fmt.Sprint(chainID)},
        })
        txData := withdrawResp["transaction"].(map[string]interface{})

        // Step 3: Sign and submit
        fmt.Println("Submitting withdrawal order...")
        nonce, _ = client.PendingNonceAt(ctx, fromAddr)
        gasPrice, _ = client.SuggestGasPrice(ctx)
        withdrawTx := types.NewTransaction(nonce,
            common.HexToAddress(txData["to"].(string)),
            big.NewInt(0), 300000, gasPrice,
            common.FromHex(txData["data"].(string)))
        signedWithdraw, _ := types.SignTx(withdrawTx, signer, pk)
        client.SendTransaction(ctx, signedWithdraw)
        fmt.Printf("Withdrawal submitted: %s\n", signedWithdraw.Hash().Hex())
    }
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import org.web3j.abi.FunctionEncoder;
    import org.web3j.abi.TypeReference;
    import org.web3j.abi.datatypes.Address;
    import org.web3j.abi.datatypes.Bool;
    import org.web3j.abi.datatypes.Function;
    import org.web3j.abi.datatypes.generated.Uint256;
    import org.web3j.crypto.Credentials;
    import org.web3j.crypto.RawTransaction;
    import org.web3j.crypto.TransactionEncoder;
    import org.web3j.protocol.Web3j;
    import org.web3j.protocol.core.methods.response.TransactionReceipt;
    import org.web3j.protocol.http.HttpService;
    import org.web3j.tx.response.PollingTransactionReceiptProcessor;
    import org.web3j.tx.TransactionManager;
    import org.web3j.utils.Numeric;

    import java.math.BigInteger;
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.util.Arrays;
    import java.util.Collections;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;

    public class AmplifyWithdraw {

        static final String BASE = "https://api.paxoslabs.com";
        static final String API_KEY = System.getenv("AMPLIFY_API_KEY");
        static final String VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001";
        static final String WITHDRAW_QUEUE = "0xdddd000000000000000000000000000000000001";
        static final String WANT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
        static final int CHAIN_ID = 1;
        static final String SHARE_AMOUNT = "1000000000000000000";

        static HttpClient httpClient = HttpClient.newHttpClient();

        static JsonObject apiGet(String path) throws Exception {
            HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(BASE + path))
                .header("x-api-key", API_KEY)
                .GET().build();
            HttpResponse<String> resp = httpClient.send(req,
                HttpResponse.BodyHandlers.ofString());
            return JsonParser.parseString(resp.body()).getAsJsonObject();
        }

        public static void main(String[] args) throws Exception {
            Credentials credentials = Credentials.create(System.getenv("PRIVATE_KEY"));
            String userAddress = credentials.getAddress();
            Web3j web3 = Web3j.build(new HttpService("https://eth.llamarpc.com"));

            // Step 1: Approve share spending
            System.out.println("Approving share spending...");
            Function approve = new Function("approve",
                Arrays.asList(
                    new Address(WITHDRAW_QUEUE),
                    new Uint256(new BigInteger(SHARE_AMOUNT))),
                Collections.singletonList(new TypeReference<Bool>() {}));
            String approveData = FunctionEncoder.encode(approve);
            BigInteger nonce = web3.ethGetTransactionCount(userAddress,
                org.web3j.protocol.core.DefaultBlockParameterName.PENDING)
                .send().getTransactionCount();
            BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();
            RawTransaction approveTx = RawTransaction.createTransaction(
                nonce, gasPrice, BigInteger.valueOf(60000),
                VAULT_ADDRESS, BigInteger.ZERO, approveData);
            byte[] signedApprove = TransactionEncoder.signMessage(
                approveTx, CHAIN_ID, credentials);
            String approveHash = web3.ethSendRawTransaction(
                Numeric.toHexString(signedApprove)).send().getTransactionHash();
            System.out.println("Approval submitted: " + approveHash);

            // Wait for approval receipt before requesting withdrawal calldata —
            // otherwise the withdrawal can race ahead of the allowance.
            PollingTransactionReceiptProcessor receiptProcessor =
                new PollingTransactionReceiptProcessor(
                    web3,
                    TransactionManager.DEFAULT_POLLING_FREQUENCY,
                    TransactionManager.DEFAULT_POLLING_ATTEMPTS_PER_TX_HASH);
            TransactionReceipt approveReceipt =
                receiptProcessor.waitForTransactionReceipt(approveHash);
            System.out.println(
                "Approval confirmed in block " + approveReceipt.getBlockNumber());

            // Step 2: Get withdrawal calldata
            System.out.println("Fetching withdrawal calldata...");
            String withdrawPath = String.format(
                "/v2/amplify/withdraw?vaultAddress=%s&wantAsset=%s" +
                "&shareAmount=%s&userAddress=%s&chainId=%d",
                VAULT_ADDRESS, WANT_ASSET, SHARE_AMOUNT, userAddress, CHAIN_ID);
            JsonObject withdrawResp = apiGet(withdrawPath);
            JsonObject tx = withdrawResp.getAsJsonObject("transaction");

            // Step 3: Sign and submit
            System.out.println("Submitting withdrawal order...");
            nonce = web3.ethGetTransactionCount(userAddress,
                org.web3j.protocol.core.DefaultBlockParameterName.PENDING)
                .send().getTransactionCount();
            RawTransaction withdrawTx = RawTransaction.createTransaction(
                nonce, gasPrice, BigInteger.valueOf(300000),
                tx.get("to").getAsString(), BigInteger.ZERO,
                tx.get("data").getAsString());
            byte[] signedWithdraw = TransactionEncoder.signMessage(
                withdrawTx, CHAIN_ID, credentials);
            String txHash = web3.ethSendRawTransaction(
                Numeric.toHexString(signedWithdraw)).send().getTransactionHash();
            System.out.println("Withdrawal submitted: " + txHash);
        }
    }
    ```
  </Tab>
</Tabs>

## Error Responses

| Status | Meaning                                                   |
| ------ | --------------------------------------------------------- |
| 400    | Invalid parameters                                        |
| 404    | No account found for the given `vaultAddress` + `chainId` |
