Savings-vault request-withdraw · claim · deposit · cancel
GET/v1/actions/vaults/savings
Build calldata for savings-vault exits — the cooldown / async-redeem mechanics that plain ERC-4626 withdraw can't express (Ethena sUSDe, erc7540 async vaults, instant erc4626 redeem, and Native Credit Pool wNLP) — plus the deposit leg for the one family that isn't ERC-4626. The protocol is resolved from the calldata-sdk savings registry by (chainId, shareToken), or forced with kind=ethena|erc7540|erc4626|native-wnlp.
ERC-4626 savings deposits go through /v1/actions/vaults/deposit. Native
wNLPworks on either route —/vaults/depositauto-detects it from the registry and needs nounderlying.
Actions
request-withdraw(default) — start the exit:- Ethena →
cooldownShares(amount)(orcooldownAssetswhenbyAssets=true); escrows the assets for the cooldown window. - erc7540 →
requestRedeem. - erc4626 → instant
redeem(no cooldown). - native-wnlp →
WithdrawQueue.requestWithdrawal(amount)— the free leg; the payout is snapshotted at request time and claimable after the queue window. Passinstant=trueforinstantRedeeminstead: one transaction, no wait, but it costs the vault'swithdrawFeeBps(100 bps by default) — deducted from the underlying paid out, never charged as a separate transfer — and is capped by its reportedliquidity.
- Ethena →
claim— settle a matured exit:- Ethena →
unstake(receiver). - erc7540 / erc4626 →
redeem(passshares). - native-wnlp →
claimWithdrawalTo(receiver); noshares— the queue always pays the full request.
- Ethena →
deposit— native-wnlp only (depositAndWrap(receiver, amount)). Every other kind is a plain ERC-4626 mint and is rejected here with a pointer to/vaults/deposit.cancel— native-wnlp only; withdraw an open queued request and take the shares back. One request per address, so cancel is also how you resize one.- Apyx (
apyx: apyUSD, aptUSD) has no cancel: the redeem mints an unlock receipt whose contract has nocancelfunction (onlyclaim), so the exit is irreversible. Claim withtokenId(from the redeem's ERC-721Transferlog) once it unlocks.
Superform SuperVaults (kind=superform)
Deposits are plain synchronous ERC-4626 and go through /vaults/deposit (or action=deposit here). The exit is ERC-7540, settled by Superform's manager (typically within an hour, not guaranteed):
request-withdraw→requestRedeem(amount, owner, owner). The request is booked under the owner, and no share approval is needed. PassredeemSlippageBpsto set your own fulfilment floor first (default 50 bps below the request price).claim→redeem(shares, receiver, controller). Omitsharesto claim the whole fulfilled balance (maxRedeem, read on-chain), or passassetsto claim by assets.cancel→cancelRedeemRequest. After the manager fulfils the cancel, callcancelagain withstep=claimto take the shares back.claim-rewards→ Merklclaimfor the sUP/USDC incentives. They are paid on Base for every chain's vaults, so call it withchainId=8453and any Base SuperVault.
Track pending requests via /v1/data/vaults/withdrawals.
Plain-text reference — GET /v1/actions/vaults/savings
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
chainId | query | string | yes | Chain ID See the ChainId schema for the full set of supported chains. |
action | query | request-withdraw, claim, claim-rewards, deposit, cancel | no | Operation to build. deposit and cancel are Native wNLP only. |
shareToken | query | string | yes | Savings share-token address (e.g. sUSDe 0x9d39a5de30e57443bff2a8307a4256c8797a3497). |
operator | query | string | yes | User wallet executing the action (share holder). receiver defaults to it. |
receiver | query | string | no | Recipient of the claimed assets. Defaults to operator. Kinds whose on-chain calls act on msg.sender only (wren, bitway, zerobase, and any leg whose calldata does not depend on the receiver — probed per request, e.g. an Ethena-style cooldown or a sUSDai request) reject a value that differs from operator with a 400. On a request leg pass the receiver on the claim step instead. |
kind | query | ethena, erc7540, erc4626, native-wnlp, susd3, strata, … (35 values) | no | Protocol override. Otherwise resolved from (chainId, shareToken). |
maxPriceAge | query | string | no | deposit / request-withdraw, aera — oldest vault price (seconds) an Aera solver may fill the request at. Default 86400. Aera requests also REQUIRE minShares (deposit: least units minted) or minAssets (request-withdraw: least USDC paid) — non-zero, the only bound on the fill price — and take deadline (unix seconds, default now + 7 days). There is no claim (the solver pays the receiver); action=cancel refunds an EXPIRED unsolved request and needs request = URI-encoded JSON of the booked Request {requestType, user, receiver, units, tokens, solverTip, deadline, maxPriceAge} (bigints as strings; receiver only on the ProvisionerV2 vault, Base). The Ethereum and Arbitrum vaults run the V1 provisioner: no receiver anywhere — the units / USDC go to the sender, and a receiver other than operator is refused. |
positionId | query | string | no | request-withdraw, alchemix-transmuter (REQUIRED) — the Transmuter position NFT to claim (lots[].id on /v1/data/vaults/user or /v1/data/earn/positions). A Transmuter exit burns one whole position: route=early before it has fully vested (the vested part at par, the rest back as alAsset less the exit fee) or route=maturity after; both are the same claimRedemption and pay the sender only. amount is ignored. No claim step, no cancel. |
payout | query | string | no | request-withdraw, alchemix-transmuter — what the vested part arrives as. underlying (default): the Alchemist router takes the position by ERC-721 approval (emitted as the first transaction), claims it and redeems the MYT shares to the underlying, bounded by minAssetOut (omitted ⇒ the vested amount less redeemSlippageBps, default 50) and deadline; receiveAsset=<native> unwraps to ETH on the alETH markets. myt: the Transmuter's own claim — no approval, no floor, the vested part as MYT shares (mixUSDC / mixWETH). |
timestamps | query | string | no | claim, mellow — comma-separated request timestamps (unix seconds) of the async redemptions to claim (RedeemQueue.claim(receiver, uint32[])). Optional: omitted, the route reads requestsOf(operator, 0, 64) on the queue and claims every request it reports isClaimable; NOTHING_TO_CLAIM when none is. action=claim&step=deposit instead mints the shares of a settled async DEPOSIT (DepositQueue.claim(account)), and action=cancel&step=deposit refunds a pending one. Mellow redemptions have no cancel. async=true on action=deposit books an async deposit (priced at the next oracle report) instead of the default sync mint. |
requestNonce | query | string | no | claim / cancel, Royco only — the RoycoEntryPoint nonce of the redemption request (its RedemptionRequested event; getLastRequestNonce() right after the request). Claim executes it (executeRedemption) once the 24 h delay has passed — Royco keepers usually do this unasked; cancel returns the escrowed shares. |
requestId | query | string | no | claim / cancel, eva — the vault's request id from its RequestSubmitted event. Claim (claimRequest) pays the request's receiver once eva has queued it into a Wildcat batch and the 24 h cycle has passed — anyone may send it; cancel (cancelRequest) is the requester's, and only before the request is queued. cancel, ember — the sequence number from the vault's RequestRedeemed (cancelPendingWithdrawalRequest; the shares return at the operator's next processing), or with step=deposit from the parking lot's DepositRequested (cancelDeposit, refunded at once). Ember has no claim: the operator pays the receiver; action=claim&step=deposit-refund pulls a parking-lot refund the token could not push. claim / cancel, yuzu (yzSyrup) — the order id from CreatedRedeemOrder: claim = finalizeRedeemOrder once Yuzu has filled it (owner or the order's controller), cancel = cancelRedeemOrder, only for an UNFILLED order past its due time (24 h). claim, makina — the request NFT id from the AsyncRedeemer's RedeemRequestCreated: claimAssets(requestId), sent by the NFT's owner once finalised; no cancel. usdai (sUSDai) needs no id: claim sizes itself off maxRedeem(controller) (serviced shares) unless shares is given; no cancel. |
epochIds | query | string | no | claim, Concrete only — comma-separated PROCESSED epoch ids the redemption was booked in (QueuedWithdrawal event / getUserEpochRequest(receiver, epoch)); claimWithdrawal(epochIDs) pays msg.sender, which must be the receiver the redeem named. |
epochId | query | string | no | cancel, Concrete only — the still-OPEN epoch whose request to cancel (cancelRequest(epochID); refused once the epoch is closed or when the vault has cancellation disabled). |
maxWithdrawDelay | query | string | no | request-withdraw, Inverse jrDOLA only — the longest queue delay in seconds the request accepts (the escrow assigns 1–10 days, rising with the queue); queueWithdrawal reverts above it. Default: accept any. Complete with action=claim inside the 48 h window that follows; action=cancel works only once that window has opened. |
executorBonusWAD | query | string | no | request-withdraw, Royco only — share of the payout (WAD) a third-party executor may keep. Default 0 = free keeper execution; 18446744073709551615 restricts execution to the requester. |
queueId | query | string | no | claim / cancel, Bitway and Zerobase — the claim-queue id returned by requestClaim (also in its RequestClaim event). Zerobase claim: omitted ⇒ the oldest matured request of operator (read via getClaimQueueIDs). |
amount | query | string | no | request-withdraw: shares to cool down (or underlying assets when byAssets=true). deposit: underlying to wrap. Wei. |
byAssets | query | boolean | no | request-withdraw, Ethena only — interpret amount as the underlying asset amount (cooldownAssets) instead of shares (cooldownShares). |
instant | query | boolean | no | request-withdraw, native-wnlp only — take the immediate instantRedeem instead of the free queue. Costs the vault’s withdrawFeeBps (deducted from the underlying paid out) and is capped by its liquidity; check both on /v1/data/vaults first. |
underlying | query | string | no | deposit, native-wnlp only — the asset to wrap, when the pool is too new to be in the registry. Otherwise resolved automatically. |
shares | query | integer | no | claim: share amount to redeem (erc7540 / erc4626). |
owner | query | string | no | request-withdraw: share owner, when not operator (erc4626 redeem). |
controller | query | string | no | erc7540 / superform controller, when not operator. |
step | query | claim | no | cancel, superform only — claim takes back the shares of a cancel Superform has fulfilled (claimCancelRedeemRequest). Omit to ask for the cancel. |
redeemSlippageBps | query | integer | no | request-withdraw, superform only — set your fulfilment floor (bps below the request price) before requesting. Default on-chain is 50. |
assets | query | integer | no | claim, superform — claim by asset amount (withdraw) instead of shares. request-withdraw route=queued, origin (wOETH / wsuperOETHb → WETH) — the OToken amount to request from Origin's vault after the unwrap; omitted, the route reads the wrapper's previewRedeem(amount) less 2 wei. The queued exit and its action=claim&requestId=<id> (from WithdrawalRequested, claimable after the vault's withdrawalClaimDelay once the queue holds the WETH) are msg.sender-only; route=instant (the default) is the plain unwrap to the OToken. No cancel. |
account | query | string | no | claim-rewards — whose rewards to claim. Defaults to operator. Superform (Merkl) claims must be sent by the account itself, so it must equal operator. |
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[].chainType | evm | Which VM executes this step. ABSENT means evm, which is the only value any endpoint returns today — every EVM response is unchanged. A non-EVM chain would return a different shape (a serialized, PERISHABLE transaction rather than to/data/value) carrying its own chainType, so a client that wants to stay forward-compatible should branch on this field rather than assume to is present. |
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[].chainType | evm | Which VM executes this step. ABSENT means evm, which is the only value any endpoint returns today — every EVM response is unchanged. A non-EVM chain would return a different shape (a serialized, PERISHABLE transaction rather than to/data/value) carrying its own chainType, so a client that wants to stay forward-compatible should branch on this field rather than assume to is present. |
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. |
actions.permissions[].type | ERC20, Lender | What kind of grant this is. A signatures[] offer names the type it replaces via its replaces field. |
actions.signatures | object[] | EIP-712 payloads the user can sign INSTEAD of sending the corresponding permissions entry. Only present when the caller opted in with permit=auto|required and a permit path exists. Sign with eth_signTypedData_v4, then POST the same endpoint with {permits: [{permitId, signature}]} (plus builds where the response issued buildIds) — the permit executes inside the main transaction. |
actions.signatures[].permitId | string | Opaque, self-describing handle for this permit. Round-trip it verbatim: POST {permits: [{permitId, signature}]} back to the same endpoint. |
actions.signatures[].kind | erc2612, dai, permit2, aaveCredit, morphoAuth, cometAuth | Permit flavour |
actions.signatures[].typedData | object | Ready for eth_signTypedData_v4 (domain, types incl. EIP712Domain, primaryType, message). All numeric fields are decimal strings. |
actions.signatures[].replaces | string | Which permissions entry this signature replaces (ERC20 | Lender) |
actions.signatures[].spender | string | Who the signature authorises. Match against the permission / quote (approvalTarget) it replaces — on multi-spender responses (x-chain) only routes whose approvalTarget equals this spender are covered by the signature. |
actions.signatures[].description | string | Human-readable label for this entry. |
actions.signatures[].unscoped | boolean | True when the grant is NOT amount-scoped (full position control until revoked) — surface this to the user. |
actions.signatures[].deadline | string | Unix seconds after which the signature is worthless (default: 30 minutes). |
actions.signs | object[] | Messages that MUST be signed before anything can be sent (unlike signatures, which are optional permit offers). When non-empty, transactions and alternatives are EMPTY: sign typedData with eth_signTypedData_v4 (or, for format: personal_sign, message with personal_sign), then call submit with the signature under submit.signatureField, and execute what that answers. permissions may still be present and can be sent before or after signing. Produced by Inverse FiRM loops (/loop/leverage, /loop/close), Morpho Midnight make, Term order-sign and the Lista leg of /rewards/claim. |
actions.signs[].purpose | authorize-action, publish-order, authenticate | authorize-action: the signature authorises an on-chain action and submit answers its transaction (Inverse ALE loops). publish-order: the signature makes an off-chain order and submit publishes it (Midnight make, Term order-sign). authenticate: the signature is a LOGIN to a third-party API that serves the data the transaction needs (Lista reward proofs); it authorises nothing on-chain. |
actions.signs[].format | eip712, personal_sign | eip712: sign typedData with eth_signTypedData_v4. personal_sign: sign the UTF-8 message with personal_sign. |
actions.signs[].typedData | object | format: eip712 only. Ready for eth_signTypedData_v4. All numeric fields are decimal strings. |
actions.signs[].message | string | format: personal_sign only. The exact text to sign; send it back verbatim (it is part of submit.params). |
actions.signs[].signer | string | The address that must sign. The server recovers the signer and refuses any other. |
actions.signs[].deadline | string | Unix seconds after which the signature is worthless. |
actions.signs[].spender | string | Who the signature authorises. |
actions.signs[].description | string | Human-readable label for this entry. |
Example response
{
"success": true,
"data": {},
"actions": {
"transactions": [
{
"chainType": "evm",
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"alternatives": [
{
"chainType": "evm",
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"permissions": [
{
"to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string",
"spender": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"type": "ERC20"
}
],
"signatures": [
{
"permitId": "string",
"kind": "erc2612",
"typedData": {},
"replaces": "string",
"spender": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"description": "string",
"unscoped": true,
"deadline": "string"
}
],
"signs": [
{
"purpose": "authorize-action",
"format": "eip712",
"typedData": {},
"message": "string",
"signer": "string",
"deadline": "string",
"spender": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"description": "string",
"submit": {
"method": "GET",
"endpoint": "string",
"params": {},
"body": {},
"signatureField": "string"
}
}
],
"permitSkipped": [
{
"replaces": "string",
"reason": "string"
}
]
}
}
Request
Responses
- 200
- 400
- 429
- 500
- 502
Ordered transactions[] + ERC-20 permissions[] (run first). Each step carries its builder label as description.
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.