Vault withdraw
GET/v1/actions/vaults/withdraw
Build calldata for an ERC-4626 vault withdraw. When the user wants the underlying back (no native unwrap), the dispatcher calls vault.withdraw / vault.redeem directly with no approval needed. When the receiver wants native ETH, falls back to the composer (unwraps WETH).
This endpoint targets passive ERC-4626 vaults. For Fluid's NFT-position margin vaults use /v1/actions/lending/withdraw.
Pendle Principal Tokens exit the same way they are entered — by SELLING on Pendle's AMM, since a PT has no redeem leg before maturity. Detected from the token list and built as a trade through the spot meta-aggregator: amount is the PT amount, receiveAsset is free (defaults to the PT's underlying), and slippage (bps) is REQUIRED because the fill is priced by pool depth, not by the protocol.
Execution mode (mode query param)
auto(default) — direct whenreceiveAsset === underlyingand not native, otherwise composer.direct— force direct; returns 400 when not eligible.proxy— force the composer (legacy behavior).
Direct withdraw skips the share-token approval entirely — the user calls vault.withdraw(assets, receiver, owner=operator) themselves and the vault uses its own balance check (msg.sender == owner).
Assets vs shares
isShares=false(default):amountis assets — issuesvault.withdraw(assets, receiver, owner).isShares=true:amountis shares — issuesvault.redeem(shares, receiver, owner).
Withdraw all (isAll=true)
Two ways to drive a full-balance withdraw — both resolve the operator's vault-share balance and encode vault.redeem(shares, …). They differ only in where the share balance is read:
- GET +
isAll=true— worker readsbalanceOf(vault, operator)against its own configured RPC. Simple but only works when the worker can see the state (i.e. live mainnet, not fork). ReturnsBALANCE_READ_FAILEDon RPC failure. - POST +
isAll=true+ body{ "sharesRaw": "<uint string>" }— caller pre-reads the balance against any RPC (fork, custom, premium) and supplies it. The worker just trusts and encodes. Mirrors the lending simulation pattern (SimulationBody).
The POST body's amount query param is ignored when isAll=true.
Non-4626 interfaces (interface query param)
Default erc4626 is the composer/direct path above. Otherwise:
erc7540— async redeem.action=requestRedeem(default) burns shares into the queue;claimRedeem/claimWithdrawsettle once fulfilled.controllerwhen notoperator.erc7575— multi-asset. Requiresshare(the share-token address);isSharestoggles assets-out vs shares-in.hypercore— HyperLiquid multi-leg withdraw route.amountis uint64 micro-USD;destinationpicks where the funds land. Returns a structuredlegs[]array. (Withdrawing out to Arbitrum is an L1 signed action, not EVM calldata — not offered here.)
Savings cooldowns (sUSDe) use /v1/actions/vaults/savings; LST exits use /v1/actions/vaults/lst.
Plain-text reference — GET /v1/actions/vaults/withdraw
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
chainId | query | string | yes | Chain ID See the ChainId schema for the full set of supported chains. |
vault | query | string | yes | ERC-4626 share-token address |
underlying | query | string | yes | Vault's asset() |
amount | query | string | yes | Amount in wei. Interpreted as assets unless isShares=true. |
operator | query | string | yes | User wallet executing the withdraw (share holder) |
receiveAsset | query | string | no | What the user receives. Defaults to underlying. Zero address or the 0xEEEE… sentinel = native ETH (composer unwraps before forwarding). |
slippage | query | string | no | Basis points. Pendle PTs only, and REQUIRED there — the exit is a sale into the PT pool, priced by its depth. Ignored by every other vault family. |
receiver | query | string | no | Final recipient of the withdrawn assets. Defaults to operator. |
isShares | query | boolean | no | When true, amount is treated as shares (the composer calls vault.redeem). |
isAll | query | boolean | no | Full-balance withdraw. Relaxes the sweep tolerance so dust does not revert the tx. |
mode | query | auto, direct, proxy | no | Execution mode. auto (default) routes direct when no unwrap is needed, composer otherwise. direct forces vault-direct (returns 400 if ineligible). proxy forces the composer. |
interface | query | erc4626, erc7540, erc7575, hypercore, native-wnlp | no | Vault interface. erc4626 (default) = composer/direct path. Others dispatch to dedicated builders (see description). Native wNLP is auto-detected from the savings registry, which also fills in underlying. |
instant | query | boolean | no | interface=native-wnlp only — take the immediate instantRedeem instead of the free queue. Costs the vault’s withdrawFeeBps (100 bps by default), deducted from the underlying paid out rather than charged separately, and is capped by its reported liquidity. The default is the free queued leg: a fee is never charged unless asked for. |
action | query | requestRedeem, claimRedeem, claimWithdraw | no | interface=erc7540 only — requestRedeem (default), claimRedeem, or claimWithdraw. interface=native-wnlp — requestWithdraw (default), claim, or cancel. |
controller | query | string | no | interface=erc7540 controller, when not operator. |
share | query | string | no | interface=erc7575 — the share-token address (required for that interface). |
destination | query | hypercore-perp, hypercore-spot, hyperevm | no | interface=hypercore withdraw destination. |
Response 200
| Field | Type | Description |
|---|---|---|
success | True | |
data | object | Informational data (quotes, simulation results, etc.) |
actions | object | Transaction calldata and approvals. Null for quote-only responses (no account provided). |
actions.transactions | object[] | Pre-trade setup transactions (e.g. e-mode switch, collateral enable). Execute these before the main swap. Empty when no setup is needed. |
actions.transactions[].to | string | Target contract address |
actions.transactions[].data | string | Encoded calldata |
actions.transactions[].value | string | ETH value to send with the transaction |
actions.transactions[].description | string | Human-readable label. For alternatives, this is the aggregator name (e.g. "Paraswap"). For transactions, describes the setup action (e.g. "Switch e-mode to 1"). |
actions.alternatives | object[] | DEX aggregator swap transactions sorted by best output (descending). Each entry's description is the aggregator name. The client should pick one to execute. Present on loop action endpoints. |
actions.alternatives[].to | string | Target contract address |
actions.alternatives[].data | string | Encoded calldata |
actions.alternatives[].value | string | ETH value to send with the transaction |
actions.alternatives[].description | string | Human-readable label. For alternatives, this is the aggregator name (e.g. "Paraswap"). For transactions, describes the setup action (e.g. "Switch e-mode to 1"). |
actions.permissions | object[] | Approval/delegation transactions that must execute before both transactions and alternatives. Includes ERC20 allowances (targeting the composer contract) and lender borrow/withdrawal delegations (targeting the lending protocol contract directly). Filtered against on-chain state so only missing approvals are returned. Null when no approvals are needed. |
actions.permissions[].to | string | Target contract address |
actions.permissions[].data | string | Encoded calldata |
actions.permissions[].value | string | ETH value |
actions.permissions[].description | string | Human-readable description of the approval (e.g. "Approve borrow for AAVE_V3", "Approve ERC20") |
actions.permissions[].spender | string | ERC-20 approve spender (x-chain permissions). Match it against the selected quote's approvalTarget — several bridges can share one spender, so do not match by description. |
Example response
{
"success": true,
"data": {},
"actions": {
"transactions": [
{
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"alternatives": [
{
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"permissions": [
{
"to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string",
"spender": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
]
}
}
Request
Responses
- 200
- 400
- 429
- 500
- 502
Transaction calldata + any approval(s) needed for the vault withdraw. interface=hypercore returns a structured legs[] route instead of transactions[].
Validation error
Rate limited. Unauthenticated callers share a per-IP budget; send an x-api-key header to lift it. Retry with exponential backoff.
Unexpected server error. Safe to retry with backoff.
An upstream data source or protocol origin failed (error.code is ORIGIN_FAILED). error.details carries the per-origin status. This is also what a missing or malformed required parameter currently returns, rather than a 400.