Skip to main content
POST
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 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) 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
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.

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.
  • 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_CHAINliquidate: true (with the default USDC settlement asset) but the recipient’s chain has no canonical USDC to swap into.
  • 400 API_220 NOTHING_TO_LIQUIDATEliquidate: true but no listed asset’s value clears the tenant swap threshold.
  • 400 API_221 UNSUPPORTED_SETTLEMENT_ASSETsettlementAssetId 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_FOUNDportfolioId doesn’t exist or belongs to a different tenant.
  • 500 on unexpected server errors.