Skip to main content

One deposit/withdraw entry point over every earn row

GET 

/v1/actions/earn/:action

One action entry point for every /v1/data/earn row. The earnUid a listing row carries is the only identity needed — this route parses it, decides which existing handler owns the row (lending vs vault, and which vault family), rewrites the query into that handler's vocabulary and calls it in-process. Nothing is built here and no route is duplicated: if a market can be transacted through /v1/actions/lending/* or /v1/actions/vaults/*, it can be transacted through here.

Routing

AAVE_V3:1:0xa0b8…          → /v1/actions/lending/{deposit,withdraw}
vault.savings:1:0x9d39… → /v1/actions/vaults/{deposit,withdraw}
→ /v1/actions/vaults/savings (async verbs)
vault.lst:… (async) → /v1/actions/vaults/lst (queued LST exits)
vault.lagoon:… (async) → /v1/actions/vaults/withdraw (ERC-7540 requestRedeem / claimRedeem)
vault.gmx:… → /v1/actions/vaults/gmx (keeper tickets; no claim — the keeper pays out)
vault.hypercore:… → /v1/actions/vaults/{deposit,withdraw} with interface=hypercore
vault.pendle:… → traded on Pendle's AMM (slippage REQUIRED, bps)

Pay-asset conversion (vault rows)

A deposit whose payAsset differs from the vault's underlying is served HERE and nowhere else: the pay asset is quoted through the aggregators and the whole swap output is deposited in one composed transaction, shares to receiver. slippage (bps) becomes REQUIRED, and the built transactions come back as alternatives[] — one per aggregator, best output first. Only rows whose deposit is a plain synchronous ERC-4626 call qualify (that is what acceptsPayAsset on the row's capability advertises); paying native into a wrapped-native vault stays a plain wrap with no bound. /v1/actions/vaults/deposit deliberately does NOT gain this behaviour.

Verbs

  • deposit / withdraw — the synchronous pair. On a lending market both are same-block; on a vault, withdraw is only published where the exit really is synchronous.
  • request-withdraw / claim / cancel — the async exit cycle, vaults only. Asking for one on a lending market is a 400, never a silent fallback to withdraw.

Which verbs a row supports — and which extra params each needs — is published on the row itself as capabilities[] (see /v1/data/earn). Render the CTA from that, not from provider knowledge: requires names params like slippage (Pendle, bps), executionFee/longToken/shortToken (GMX), or validator (delegated LSTs).

Parameter forwarding

Only earnUid and the verb are consumed. Everything else — payAsset, receiveAsset, slippage, isShares, isAll, mode, Yield Basis's debt/minShares, Strata's claimToken, Apyx's tokenId, an LST's kind, GMX's executionFee, an ERC-7540 sub-action (action=claimWithdraw), … — rides through verbatim to the owning handler, so provider-specific inputs need no support here.

Response is the standard action envelope: permissions[] (approvals, executed first), transactions[] (signed in order), alternatives[] where a trade offers multiple routes, plus per-venue state (e.g. dbr on Inverse) in data.

Plain-text reference — GET /v1/actions/earn/{action}

Parameters

ParameterInTypeRequiredDescription
actionpathdeposit, withdraw, request-withdraw, claim, cancelyesThe verb. May also be passed as ?action= on the bare /v1/actions/earn path.
earnUidquerystringyesThe row identity from /v1/data/earn, verbatim and OPAQUE — never rebuild it from parts. Lending rows: the marketUid (AAVE_V3:1:0xa0b8…). Vault rows: vault.<provider>:<chainId>:<shareToken>.
operatorquerystringyesUser wallet executing the action. receiver defaults to it.
amountquerystringnoRaw amount (wei). Required for deposit / withdraw / request-withdraw unless isAll=true. Denomination follows the owning route: assets by default, shares when isShares=true, PT units for Pendle.
receiverquerystringnoRecipient. Defaults to operator.
payAssetquerystringnodeposit — what the user actually pays, where the row publishes acceptsPayAsset. Zero address / 0xEEEE… = native. On a vault row a payAsset that differs from the underlying triggers the aggregator conversion (see above); amount is then denominated in the PAY asset and slippage is required.
receiveAssetquerystringnowithdraw — what the user wants out, where the row publishes acceptsReceiveAsset.
slippagequerystringnoBasis points. Required wherever the row’s capability lists it in requires (any leg that settles on a book — Pendle PTs in both directions).
isSharesquerybooleannoInterpret amount as shares (mint / redeem) instead of assets.
isAllquerybooleannoFull-balance exit (vault withdraw). Not supported on swap-routed rows (Pendle) — pass the explicit amount there.
simulatequerybooleannoLending rows only — attach a post-trade simulation.

Response 200

FieldTypeDescription
successTrue
dataobjectInformational data (quotes, simulation results, etc.)
actionsobjectTransaction calldata and approvals. Null for quote-only responses (no account provided).
actions.transactionsobject[]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[].tostringTarget contract address
actions.transactions[].datastringEncoded calldata
actions.transactions[].valuestringETH value to send with the transaction
actions.transactions[].descriptionstringHuman-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.alternativesobject[]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[].tostringTarget contract address
actions.alternatives[].datastringEncoded calldata
actions.alternatives[].valuestringETH value to send with the transaction
actions.alternatives[].descriptionstringHuman-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.permissionsobject[]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[].tostringTarget contract address
actions.permissions[].datastringEncoded calldata
actions.permissions[].valuestringETH value
actions.permissions[].descriptionstringHuman-readable description of the approval (e.g. "Approve borrow for AAVE_V3", "Approve ERC20")
actions.permissions[].spenderstringERC-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

Action built by the owning handler. permissions[] first, then transactions[] in order.