Skip to main content
Portfolio wallet withdrawals are asynchronous. A withdrawal may involve unwinding yield positions, bridging across chains, and waiting for customer approval — all orchestrated automatically.

Withdrawal lifecycle

The typical flow is: preview -> initiate -> track -> complete.
  1. Preview — call withdrawal-preview to see what’s available and get a sourcing plan.
  2. Initiate — call withdraw to create the withdrawal. The system reserves liquidity and begins sourcing.
  3. Track — poll the withdrawal or subscribe to webhooks. The withdrawal moves through statuses as positions unwind, funds bridge, and payouts execute.
  4. Complete — the withdrawal reaches completed when all funds have been delivered.

Preview a withdrawal

Before initiating, preview what balance is available for a given destination.
curl -X POST "$BASE_URL/v2/wallets/$WALLET_ID/withdrawal-preview" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationChain": "polygon",
    "amountUsd": 65000
  }'
FieldRequiredDescription
destinationChainYesDestination chain for the withdrawal
amountUsdNoAmount to preview as a number (max 6 decimal places). If omitted, previews the maximum available balance.
{
  "amountUsd": "65000.000000",
  "feeUsd": "0.000000",
  "availableUsd": "82500.000000",
  "estimatedCompletionTime": "PT2H"
}
FieldDescription
amountUsdThe withdrawal amount previewed (equals the requested amount, or the max available if amountUsd was omitted)
feeUsdFee for the withdrawal (currently always "0.000000")
availableUsdUnreserved liquidity available for this destination
estimatedCompletionTimeEstimated end-to-end completion time as an ISO 8601 duration

Initiate a withdrawal

curl -X POST "$BASE_URL/v2/wallets/$WALLET_ID/withdraw" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "df8b7be6-e110-4f6d-9b2d-7c44a5b1f0b0",
    "destinationChain": "ethereum",
    "amountUsd": 65000,
    "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
  }'
FieldRequiredDescription
requestIdYesUUID v4 idempotency key
destinationChainYesDestination chain
amountUsdYesAmount as a number (max 6 decimal places)
destinationAddressYesRecipient address on the destination chain
Withdrawals always deliver USDC to the destination address. There is no token field — USDC is the only supported withdrawal token. Sandbox notes:
  • destinationChain: "ethereum" refers to Ethereum Sepolia in sandbox.
  • Solana withdrawals are not currently supported (the endpoint expects an EVM destination address).

List withdrawals

curl -X GET "$BASE_URL/v2/wallets/$WALLET_ID/withdrawals?limit=25" \
  -H "Authorization: Bearer $API_TOKEN"
Query parameters:
ParamDescription
limitMax items (default 50, max 200)
cursorOpaque cursor from nextCursor
sortSort field (default createdAt)
orderdesc (default) or asc
createdAtGteISO-8601 timestamp filter
statusRepeatable status filter (e.g. status=processing&status=completed)

Fetch a withdrawal

curl -X GET "$BASE_URL/v2/wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID" \
  -H "Authorization: Bearer $API_TOKEN"
{
  "id": "11b17950-1f5c-4d36-8f0d-0f3d1d0c6a45",
  "amountUsd": "65000.000000",
  "feeUsd": "0.000000",
  "destinationChain": "ethereum",
  "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "status": "processing",
  "txHash": null,
  "payouts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "yieldSourceId": "usdz",
      "status": "pending_customer_approval",
      "turnkeyActivityId": "act_abc123def456",
      "approvalRequestedAt": "2026-02-05T11:02:00.000Z",
      "approvalValidBefore": "2026-02-05T12:02:00.000Z",
      "amount": 65000,
      "token": "usdc",
      "chain": "ethereum",
      "destinationAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
      "txHash": null,
      "failureReason": null,
      "createdAt": "2026-02-05T11:01:00.000Z",
      "updatedAt": "2026-02-05T11:02:00.000Z"
    }
  ],
  "failureReason": null,
  "createdAt": "2026-02-05T11:00:00.000Z",
  "completedAt": null
}
The payouts array contains the individual payout legs of the withdrawal. Each payout represents a discrete transfer on a specific chain. Key fields:
FieldDescription
yieldSourceIdYield source identifier for the payout leg
statusPayout leg status (see complete list below)
turnkeyActivityIdTurnkey signing activity ID (only present while status is pending_customer_approval; null otherwise)
approvalRequestedAtWhen customer approval was requested (only present while status is pending_customer_approval; null otherwise)
approvalValidBeforeISO-8601 deadline for the approval (only present while status is pending_customer_approval; null otherwise)
amountPayout amount in token units (number)
tokenToken being transferred (e.g. usdc)
chainChain the payout executes on
txHashOnchain transaction hash (populated after broadcast)
The top-level txHash is a convenience field extracted from the primary completed payout.

Payout statuses

Each payout leg moves through the following statuses:
StatusTerminalMeaning
planningNoPayout leg is being planned (sourcing route, computing amounts)
processingNoPayout is actively being processed (unwinding position, bridging)
pending_customer_approvalNoAwaiting customer signature via Turnkey (turnkeyActivityId, approvalRequestedAt, and approvalValidBefore are populated)
pending_broadcastNoSigned transaction is queued for onchain broadcast
broadcastedNoTransaction has been broadcast; awaiting confirmation
completedYesPayout delivered successfully; txHash is populated
failedYesPayout failed; see failureReason
cancelledYesPayout was cancelled (e.g. parent withdrawal cancelled)
skippedYesPayout leg was skipped (e.g. zero-amount leg after partial completion)

Withdrawal statuses

Withdrawals move through five statuses:
StatusMeaning
pendingAccepted and reserved; processing hasn’t started yet
processingUnwinding positions, bridging, or delivering funds
completedTerminal: funds delivered to destination address
failedTerminal: withdrawal failed
cancelledTerminal: withdrawal was cancelled

Cancel a withdrawal

Cancel an in-progress withdrawal:
curl -X POST "$BASE_URL/v2/wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID/cancel" \
  -H "Authorization: Bearer $API_TOKEN"
Cancellation is best-effort. If funds have already been delivered, the withdrawal transitions to completed rather than cancelled.

Timing expectations

Withdrawal time depends on which yield positions need to unwind and whether cross-chain bridging is required.
PositionUnwind timeNotes
Cash (USDC)InstantNo unwind needed
resolv-lpUp to 24h (PT24H)Async Resolv exit window
syrup-usdcUp to 24h (PT24H)ERC-4626 redemption on Ethereum with asynchronous withdrawal window
usdzInstant (PT0H)Convert to USDC on Arbitrum
Cross-chain delivery (CCTP) adds time on top of the unwind. Typical CCTP bridging is ~1 hour (p50), up to ~3 hours (max). For precise per-position estimates to a specific destination, use the withdrawal preview endpoint.

Withdrawal webhooks

Subscribe to portfolio_wallet.withdrawal.status_changed for real-time withdrawal tracking. See Webhooks for registration and payload details.

Bridge domains reference

A bridge domain is a hard boundary for where liquidity can come from. The withdrawal engine never crosses bridge domain boundaries.

USDC unified (CCTP)

USDC on CCTP-supported chains forms a single usdc:unified domain:
  • Arbitrum, Base, Ethereum, Polygon, Solana
A withdrawal to any of these chains can source USDC from any other chain in the domain (via CCTP bridging).

USDT (always per-chain)

USDT has no cross-chain unification:
  • usdt:arbitrum, usdt:base, usdt:ethereum, usdt:polygon, usdt:solana

Default (per-chain)

All other tokens are per-chain:
  • dai:ethereum, usdz:arbitrum, etc.

How cash fits in

Within a bridge domain, cash is always the first source of liquidity. Only if cash is insufficient will the system unwind yield positions in the same domain. Bridge domain IDs are always lowercase and intended to be stable, but use the withdrawal preview as the canonical source of truth for what’s available.