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

# Prepare Withdrawal Authorization (Stage 1)

> Stage 1 of the two-stage withdrawal flow — returns the authorization (EIP-712 for EVM, an off-chain message for Solana) for the end-user to sign in their wallet.

Stage 1 of the two-stage withdrawal flow. Validates the request, checks
live onchain balances, and returns the authorization message the end-user
signs with their portfolio owner wallet. Stage 2
(`POST /v2/portfolios/{portfolioId}/withdraw`) accepts the signed
authorization and dispatches the onchain transfer.

Wallet addresses are exchanged as
[CAIP-10](https://chainagnostic.org/CAIPs/caip-10) account identifiers.
`recipientAccountId` is always chain-bound — a withdrawal goes to one
address on one specific chain:

* **EVM:** `eip155:<chainId>:<address>`

* **Solana:** `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:<base58>`

* Auth: `x-api-key` header (required)

* Scope: `portfolios:withdraw`

The returned `authorizationId` (= the signed `message.nonce`) is the
idempotency anchor for the matching `POST .../withdraw` call.

The **shape of the authorization depends on the withdrawal chain selected by
`recipientAccountId` and the listed assets**:

* **EVM withdrawals** — stage 1 returns the full EIP-712 typed-data object at
  `data.typedData`. Pass it directly to `signTypedData(data.typedData)` in
  the user's wallet, then echo `typedData.message` back as `body.message` on
  stage 2.
* **Solana withdrawals** — stage 1 returns `data.authorization` instead. Sign
  it per its `kind` (see [Solana withdrawals](#solana-withdrawals)) and echo
  `authorization.message` back as `body.message` on stage 2.

Stage 2's `body.message` shape is identical for both chains — only the
signature encoding and where you read the message from (`typedData.message`
vs `authorization.message`) differ.

The authorization is valid for **10 minutes**. If the user takes longer to
sign — or a balance check fails at stage 2 — restart the flow by calling
this endpoint again.

### Chain-equivalence rule

Every asset's `assetId` must be on the same chain as `recipientAccountId`. A
single authorization withdraws a set of assets from one chain to one
recipient on that chain. Multi-chain withdrawals require separate
authorizations per chain.

```⚠️ Warning theme={null}
  At the moment, withdrawals and liquidations do not perform cross-chain
  settlement or bridging. `recipientAccountId`, every `assets[].assetId`, and
  `settlementAssetId` must reference the same chain.

  For example, Base assets cannot be liquidated directly to Solana USDC. Use a
  Base recipient and Base settlement asset, then bridge the proceeds separately
  if they need to arrive on Solana.
```

Some yield-share assets must be redeemed through their registered yield source
before the underlying asset can leave a portfolio. A direct request for such a
share returns `UNSUPPORTED_WITHDRAW_ASSET`; redeem it first, then authorize the
underlying asset withdrawal.

### Liquidate to a settlement asset

Set `liquidate: true` (default `false`) to have the listed `assets` swapped to
a single settlement asset and delivered to `recipientAccountId`, instead of
transferring each asset as-is. Both the flag and the settlement asset are part
of the signed authorization (the EIP-712 typed-data on EVM, the off-chain
digest/message on Solana), so the swap intent is bound to the user's signature
and cannot be changed at stage 2.

Use `settlementAssetId` to pick the settlement asset. Its CAIP-19 chain must
match both `recipientAccountId` and every listed asset:

* **EVM withdrawal** — USDC by default; configured USDT may be selected
  explicitly on the same EVM chain.
* **Solana withdrawal** — Solana USDC only.

Any asset that isn't an allowed settlement asset on the chain returns
`400 API_221 UNSUPPORTED_SETTLEMENT_ASSET`.
`settlementAssetId` is part of the signed message only when `liquidate: true`; plain
transfers don't carry it.

Assets whose value is below the tenant's configured swap threshold are
**skipped** (left in the vault) — the prepared authorization covers only the
assets that clear the threshold. If none clear it, stage 1 returns
`400 API_220 NOTHING_TO_LIQUIDATE`. Assets already denominated in the
settlement asset are delivered directly without a swap. To liquidate an entire portfolio
without listing assets, use [`POST .../liquidate-all/signature`](./v2-liquidate-all-signature).

### Signed EIP-712 domain

The returned `domain` binds the signature to two things:

* `chainId` — the EVM chain the assets and recipient live on. Required for
  ERC-1271 smart-wallet verification, which re-hashes the typed-data using
  the wallet's own `chainId`.
* `verifyingContract` — the smart account being debited on that chain. Scopes
  the signature to a specific smart account deployment.

Integrators should pass `domain`, `types`, `primaryType`, and `message`
verbatim to `signTypedData` in the user's wallet.

### Solana withdrawals

For Solana portfolios, stage 1 returns `data.authorization` (not
`data.typedData`). Glider's pooled agent executes the SPL/SOL transfer out
of the portfolio's Swig sub-account and the Glider paymaster sponsors the
fee — the user's signature is the **off-chain authorization** proving the
funds' owner approved this exact withdrawal. The authorization binds the
portfolio, the smart account being debited, the recipient, every asset and
amount, the nonce, and the expiry, so a captured signature authorizes one
withdrawal and nothing else.

`authorization` is a discriminated union keyed on `kind`, matching the
portfolio's owner model (the same two models as enrollment):

* **`kind: "ecdsa"` — EVM-rooted (Model A).** The portfolio owner is an EVM
  key (the Swig root authority). Sign `authorization.raw` (a 32-byte hash)
  with the owner's EVM wallet via EIP-191 `personal_sign`. Submit the
  resulting `0x…` hex signature on stage 2.
* **`kind: "solana-message"` — Solana-rooted (Model B).** The portfolio
  owner is a Solana key. The wallet (Phantom, Backpack, Solflare, …) renders
  `authorization.text` verbatim; sign its UTF-8 bytes via the wallet's
  `signMessage` (ed25519). Submit the resulting base58 signature on stage 2.

In both cases, echo `authorization.message` back verbatim as `body.message`
on stage 2. `recipientAccountId` and every asset's `assetId` must be on
Solana (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/…`). Native SOL uses the
`slip44:501` asset reference; SPL tokens use `spl:<mint>`.

### What the user sees in their wallet

When rendering the signature prompt, the wallet displays each field of the
signed struct. Users see `recipientAccountId` as a CAIP-10 string like
`eip155:1:0x4444…` — chain and address are visible together. They also see
`portfolioId`, `assets` (CAIP-19 asset ids + raw atomic amounts),
`nonce`, `expiresAt`, and `liquidate` — plus `settlementAssetId` (the settlement
asset) on a liquidation. Integrators SHOULD surface a decoded summary in
their own UI (asset symbols, human-readable amounts) so users can verify
the recipient and amounts before approving.

Common error responses:

* `400 API_210 INSUFFICIENT_BALANCE` — live balance below `amountRaw` for at
  least one asset.
* `400 API_211 INVALID_RECIPIENT` — zero address, or a self-transfer to the
  smart account / Swig sub-account being debited. The recipient may otherwise
  be any address the owner signs for — see the recipient note on
  [stage 2](./v2-withdraw).
* `403 API_104 PERMISSION_DENIED` — Solana withdrawal requested but the
  `svm_b2b_api` feature is not enabled for the tenant.
* `400 API_212 DUPLICATE_WITHDRAW_ASSET` — two or more assets in `assets`
  share the same `assetId`.
* `400 API_213 WITHDRAWAL_CHAIN_MISMATCH` — at least one asset is on a
  different chain than `recipientAccountId`.
* `400 API_215 PORTFOLIO_HAS_NO_VAULT_ON_CHAIN` — portfolio has no smart
  account deployed on the recipient's chain.
* `400 API_219 WITHDRAW_AS_USDC_UNSUPPORTED_CHAIN` — `liquidate: true` (with the
  default USDC settlement asset) but the recipient's chain has no canonical USDC to swap into.
* `400 API_220 NOTHING_TO_LIQUIDATE` — `liquidate: true` but no listed asset's
  value clears the tenant swap threshold.
* `400 API_221 UNSUPPORTED_SETTLEMENT_ASSET` — `settlementAssetId` is not an allowed
  settlement asset on the recipient's chain (configured USDC or USDT for an
  EVM withdrawal, or USDC for a Solana withdrawal).
* `400` when the request body is otherwise invalid (schema errors).
* `401` when `x-api-key` header is missing or the key is invalid.
* `403` when the API key lacks the `portfolios:withdraw` scope.
* `404 API_200 PORTFOLIO_NOT_FOUND` — `portfolioId` doesn't exist or belongs
  to a different tenant.
* `500` on unexpected server errors.

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://api.glider.fi/v2/portfolios/a1b2c3d4/withdraw/signature' \
    --header 'x-api-key: gldr_sk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{
      "recipientAccountId": "eip155:1:0x4444444444444444444444444444444444444444",
      "assets": [
        {
          "assetId": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "amountRaw": "1000500000"
        }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.glider.fi/v2/portfolios/a1b2c3d4/withdraw/signature",
    {
      method: "POST",
      headers: {
        "x-api-key": "gldr_sk_your_api_key",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        recipientAccountId:
          "eip155:1:0x4444444444444444444444444444444444444444",
        assets: [
          {
            assetId:
              "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
            amountRaw: "1000500000",
          },
        ],
      }),
    },
  );
  ```

  ```bash cURL (liquidate to USDC) theme={null}
  curl --request POST \
    --url 'https://api.glider.fi/v2/portfolios/a1b2c3d4/withdraw/signature' \
    --header 'x-api-key: gldr_sk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{
      "recipientAccountId": "eip155:1:0x4444444444444444444444444444444444444444",
      "liquidate": true,
      "settlementAssetId": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "assets": [
        {
          "assetId": "eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f",
          "amountRaw": "5000000000000000000"
        }
      ]
    }'
  ```

  ```bash cURL (liquidate to USDT on BNB) theme={null}
  curl --request POST \
    --url 'https://api.glider.fi/v2/portfolios/a1b2c3d4/withdraw/signature' \
    --header 'x-api-key: gldr_sk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{
      "recipientAccountId": "eip155:56:0x4444444444444444444444444444444444444444",
      "liquidate": true,
      "settlementAssetId": "eip155:56/erc20:0x55d398326f99059fF775485246999027B3197955",
      "assets": [
        {
          "assetId": "eip155:56/erc20:0x2170ed0880ac9a755fd29b2688956bd959f933f8",
          "amountRaw": "1000000000000000000"
        }
      ]
    }'
  ```

  ```bash cURL (Solana) theme={null}
  curl --request POST \
    --url 'https://api.glider.fi/v2/portfolios/a1b2c3d4/withdraw/signature' \
    --header 'x-api-key: gldr_sk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{
      "recipientAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "assets": [
        {
          "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "amountRaw": "1000000"
        }
      ]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "data": {
      "authorizationId": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
      "expiresAt": "2026-04-17T12:10:00.000Z",
      "typedData": {
        "primaryType": "Withdrawal",
        "domain": {
          "name": "Glider Withdrawal Authorization",
          "version": "1",
          "chainId": 1,
          "verifyingContract": "0x2222222222222222222222222222222222222222"
        },
        "types": {
          "Withdrawal": [
            { "name": "portfolioId", "type": "string" },
            { "name": "recipientAccountId", "type": "string" },
            { "name": "assets", "type": "WithdrawalAsset[]" },
            { "name": "nonce", "type": "bytes32" },
            { "name": "expiresAt", "type": "uint256" }
          ],
          "WithdrawalAsset": [
            { "name": "assetId", "type": "string" },
            { "name": "amountRaw", "type": "string" }
          ]
        },
        "message": {
          "portfolioId": "a1b2c3d4",
          "recipientAccountId": "eip155:1:0x4444444444444444444444444444444444444444",
          "assets": [
            {
              "assetId": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
              "amountRaw": "1000500000"
            }
          ],
          "nonce": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
          "expiresAt": 1744898400
        }
      }
    }
  }
  ```

  ```json 200 (liquidate, EVM) theme={null}
  {
    "success": true,
    "data": {
      "authorizationId": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
      "expiresAt": "2026-04-17T12:10:00.000Z",
      "typedData": {
        "primaryType": "Withdrawal",
        "domain": {
          "name": "Glider Withdrawal Authorization",
          "version": "2",
          "chainId": 1,
          "verifyingContract": "0x2222222222222222222222222222222222222222"
        },
        "types": {
          "Withdrawal": [
            { "name": "portfolioId", "type": "string" },
            { "name": "recipientAccountId", "type": "string" },
            { "name": "assets", "type": "WithdrawalAsset[]" },
            { "name": "nonce", "type": "bytes32" },
            { "name": "expiresAt", "type": "uint256" },
            { "name": "liquidate", "type": "bool" },
            { "name": "settlementAssetId", "type": "string" }
          ],
          "WithdrawalAsset": [
            { "name": "assetId", "type": "string" },
            { "name": "amountRaw", "type": "string" }
          ]
        },
        "message": {
          "portfolioId": "a1b2c3d4",
          "recipientAccountId": "eip155:1:0x4444444444444444444444444444444444444444",
          "assets": [
            {
              "assetId": "eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f",
              "amountRaw": "5000000000000000000"
            }
          ],
          "nonce": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
          "expiresAt": 1744898400,
          "liquidate": true,
          "settlementAssetId": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
        }
      }
    }
  }
  ```

  ```json 200 (Solana, Model B) theme={null}
  {
    "success": true,
    "data": {
      "authorizationId": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
      "expiresAt": "2026-04-17T12:10:00.000Z",
      "authorization": {
        "kind": "solana-message",
        "text": "Glider — authorize Solana withdrawal\n\nPortfolio: a1b2c3d4\nSmart account: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9\nRecipient: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU\nChain: 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\nAsset 1: 1000000 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\nNonce: 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\nExpires at (unix): 1744898400\n\nDomain: glider:svm-withdraw:solana:v1",
        "message": {
          "portfolioId": "a1b2c3d4",
          "recipientAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
          "assets": [
            {
              "assetId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
              "amountRaw": "1000000"
            }
          ],
          "nonce": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
          "expiresAt": 1744898400
        }
      }
    }
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "error": {
      "code": "API_213",
      "message": "Asset eip155:137/erc20:0x... is not on the recipient's chain (eip155:1)"
    }
  }
  ```

  ```json 404 theme={null}
  {
    "success": false,
    "error": {
      "code": "API_200",
      "message": "Portfolio not found or not owned by tenant: a1b2c3d4"
    }
  }
  ```
</ResponseExample>
