Portfolio wallet withdrawals are asynchronous. A withdrawal may involve unwinding yield positions, bridging across chains, and waiting for customer approval — all orchestrated automatically.
Treat withdrawal creation as acceptance and reservation of a request, not as a universal price lock across every yield source. Morpho ERC-4626 vaults exposed by the live catalog are synchronous exits without a normal queue window. syrup-usdc is an asynchronous exit: if the underlying protocol settles at a lower value before processing, the final payout can be lower than the amount originally requested. syrup-usdc can also be partially processed while the remainder stays pending.
Withdrawal lifecycle
The typical flow is: preview -> initiate -> track -> complete.
- Preview — call
withdrawal-preview to see what’s available and get a sourcing plan.
- Initiate — call
withdraw to create the withdrawal. The system reserves liquidity and begins sourcing.
- Track — poll the withdrawal or subscribe to webhooks. The withdrawal moves through statuses as positions unwind, funds bridge, and payouts execute.
- 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
}'
| Field | Required | Description |
|---|
destinationChain | Yes | Destination chain for the withdrawal |
amountUsd | No | Amount 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",
"availableToInitiateUsd": "82500.000000",
"estimatedCompletionTime": "PT2H"
}
| Field | Description |
|---|
amountUsd | The withdrawal amount previewed (equals the requested amount, or the max available if amountUsd was omitted) |
feeUsd | Fee for the withdrawal (currently always "0.000000") |
availableToInitiateUsd | Conservative amount that can safely start a new withdrawal for this destination right now |
availableUsd | Backwards-compatible alias for availableToInitiateUsd |
estimatedCompletionTime | Estimated end-to-end completion time as an ISO 8601 duration |
withdrawableUsd on the wallet object and availableToInitiateUsd in preview are related but different:
- Wallet
withdrawableUsd is the wallet-level conservative amount that can be withdrawn now.
- Wallet
availableToInitiateUsd is the same value, kept as a backwards-compatible alias on wallet responses.
- Preview
availableToInitiateUsd is the destination-specific amount that can safely start right now.
- Preview
availableToInitiateUsd can be lower than the wallet object’s withdrawableUsd.
Ground keeps wallet-level ownership, wallet-level readiness, and destination-specific readiness separate on purpose. Value can remain customer-owned while still being unavailable to start a fresh withdrawal to a specific destination, for example when a prior unwind, bridge, or payout boundary is still settling and the destination representation has not yet been observed.
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"
}'
| Field | Required | Description |
|---|
requestId | Yes | UUID v4 idempotency key |
destinationChain | Yes | Destination chain |
amountUsd | Yes | Amount as a number (max 6 decimal places) |
destinationAddress | Yes | Recipient 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.
When withdraw succeeds, Ground has accepted the request and reserved liquidity against the wallet. The requested amountUsd remains the target amount for the withdrawal, but the payout legs are the source of truth for what was actually delivered. Integrators should set end-user expectations based on the underlying yield source, especially for asynchronous exits.
Sandbox notes:
- In sandbox, use explicit testnet keys such as
destinationChain: "ethereum_sepolia".
destinationAddress must match the selected chain format: EVM hex for EVM chains, base58 for solana.
List withdrawals
curl -X GET "$BASE_URL/v2/wallets/$WALLET_ID/withdrawals?limit=25" \
-H "Authorization: Bearer $API_TOKEN"
Query parameters:
| Param | Description |
|---|
limit | Max items (default 50, max 200) |
cursor | Opaque cursor from nextCursor |
sort | Sort field (default createdAt) |
order | desc (default) or asc |
createdAtGte | ISO-8601 timestamp filter |
status | Repeatable 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": "morpho-gauntlet-usdc",
"status": "pending_customer_approval",
"turnkeyActivityId": "act_abc123def456",
"approvalRequestedAt": "2026-02-05T11:02:00.000Z",
"approvalValidBefore": "2026-02-05T12:02:00.000Z",
"amount": "65000.000000",
"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:
| Field | Description |
|---|
yieldSourceId | Yield source identifier for the payout leg |
status | Payout leg status (see complete list below) |
turnkeyActivityId | Turnkey signing activity ID (only present while status is pending_customer_approval; null otherwise) |
approvalRequestedAt | When customer approval was requested (only present while status is pending_customer_approval; null otherwise) |
approvalValidBefore | ISO-8601 deadline for the approval (only present while status is pending_customer_approval; null otherwise) |
amount | Actual payout amount for that leg as a fixed-precision token amount string |
token | Token being transferred (e.g. usdc) |
chain | Chain the payout executes on |
txHash | Onchain 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:
| Status | Terminal | Meaning |
|---|
planning | No | Payout leg is being planned (sourcing route, computing amounts) |
processing | No | Payout is actively being processed (unwinding position, bridging) |
pending_customer_approval | No | Awaiting customer signature via Turnkey (turnkeyActivityId, approvalRequestedAt, and approvalValidBefore are populated) |
pending_broadcast | No | Signed transaction is queued for onchain broadcast |
broadcasted | No | Transaction has been broadcast; awaiting confirmation |
completed | Yes | Payout delivered successfully; txHash is populated |
failed | Yes | Payout failed; see failureReason |
cancelled | Yes | Payout was cancelled (e.g. parent withdrawal cancelled) |
skipped | Yes | Payout leg was skipped (e.g. zero-amount leg after partial completion) |
Withdrawal statuses
Withdrawals move through five public statuses:
| Status | Meaning |
|---|
pending | Accepted and reserved; processing hasn’t started yet |
processing | Unwinding positions, bridging, or delivering funds |
completed | Terminal: funds delivered to destination address |
failed | Terminal: withdrawal failed |
cancelled | Terminal: withdrawal was cancelled |
Cancel a withdrawal
Cancel a reversible withdrawal:
curl -X POST "$BASE_URL/v2/wallets/$WALLET_ID/withdrawals/$WITHDRAWAL_ID/cancel" \
-H "Authorization: Bearer $API_TOKEN"
Cancellation is allowed while the withdrawal is still reversible. In practice, that means the API can cancel pending or processing withdrawals until a payout enters the broadcast path (pending_broadcast / broadcasted), gains a txHash, or an irreversible protocol exit has started. After that point the API returns a 409.
You may include an optional requestId in the cancel request body if you want to track client-side idempotency for your own cancellation workflow.
Timing expectations
Withdrawal time depends on which yield positions need to unwind and whether cross-chain bridging is required.
| Position | Typical timing | Developer note | Customer-facing copy |
|---|
| Cash (USDC) | Instant | No unwind needed. | This withdrawal is expected to complete right away. |
morpho-gauntlet-usdc | Instant (PT0H) | Synchronous ERC-4626 redeem on Ethereum. There is no normal queue-based delay. | This withdrawal is expected to complete quickly and is not normally queued. |
morpho-steakhouse-usdc | Instant (PT0H) | Synchronous ERC-4626 redeem on Ethereum. There is no normal queue-based delay. | This withdrawal is expected to complete quickly and is not normally queued. |
syrup-usdc | Up to 24h (PT24H) | Queue-based withdrawal. Low-liquidity periods can take longer. The payout can be processed in parts, and the final amount is based on Maple’s withdrawal value when the queue processes the request. | This withdrawal is queued rather than instant. Most withdrawals are processed within 24 hours, but it can take longer in low-liquidity periods. The payout can be processed in parts, and the final amount can be lower if Maple’s withdrawal value falls before processing. |
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).
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.