Withdraw
GET/v1/actions/lending/withdraw
Build calldata for withdrawing from a lending pool. Identify the market via marketUid (format: lender:chainId:address). Approval transactions are automatically filtered: if the user already has sufficient allowances, permissions (envelope) and permissionTxns (per-entry) will be empty. Pass simulate=true to include projected post-trade metrics. When on-chain deposit data is available and amount covers the full deposit balance, the API uses protocol-level max-withdraw mechanisms automatically (e.g. maxUint256 or share-based redemption). If isAll=true but amount is less than the deposit balance, the API falls back to a partial withdrawal to avoid reverts.
Plain-text reference — GET /v1/actions/lending/withdraw
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
marketUid | query | string | yes | Market identifier (lender:chainId:address). The address encodes the underlying token (Aave/Morpho/CompoundV3), cToken (CompoundV2), or pool (Init Capital). |
amount | query | string | yes | Amount in wei |
mode | query | proxy, direct | no | Execution mode. Omit it (recommended) and the API picks the path per request: direct (a raw protocol call) when the protocol can serve the request as given, otherwise proxy (the 1delta composer) — e.g. a custom receiver the protocol call cannot express, or a payAsset that needs wrapping. A payAsset that needs a swap routes through a DEX aggregator. |
The transaction to is therefore NOT always the protocol contract: on the proxy path it is the composer, and the ERC-20 approval in actions.permissions targets the composer too. Send what the response returns.
Pass direct or proxy only to force a path; a forced path that cannot serve the request (for instance a custom receiver on a lender whose direct call has no recipient slot) is refused with 400 INVALID_PARAM, never built. |
| operator | query | string | yes | Wallet address that signs and sends the transaction (and pays, for deposit/repay). Not necessarily the position owner — see receiver. |
| receiver | query | string | no | Recipient of the lender position (deposit), of the underlying tokens (borrow/withdraw), or the account whose debt is repaid (repay).
Defaults to operator when omitted — so an on-behalf deposit MUST pass this parameter; there is no onBehalf / onBehalfOf alias, and an unknown parameter name is ignored.
Custom-receiver support is per-lender. The API auto-selects the execution path:
Direct path supported
- Aave V2/V3 —
supply/withdrawhonorto. - Aave V4 — deposit via GiverPM
supplyOnBehalfOf. Requires the GiverPM to be curated inaave-v4-peripherals.jsonwith a name containing "giver". - Compound V3 —
supplyTo/withdrawTo(a borrow is awithdrawToof the base asset). - Morpho Blue & Lista DAO ERC-20 markets — Morpho
onBehalf(repay included). - Euler V2 ERC-20 vaults — ERC-4626
deposit(receiver),repay(assets, receiver). Collateral auto-enable is dropped for non-sub-account receivers. - Exactly —
repay/repayAtMaturitytake theborrower. - Silo V2/V3 ERC-20 —
deposit(_, receiver, _). - Gearbox V3 passive-pool deposits — ERC-4626
deposit(_, receiver). - Fluid borrow & withdraw —
operate(..., to_)outflow slot. - Fluid deposit to an existing NFT when
accountIdis supplied — pure-depositoperate()calls bypass Fluid's owner auth gate, and the API folds a pre-flightVaultFactory.ownerOf(nftId) == receivercheck into the merged multicall (rejects with 400INVALID_PARAMon mismatch,VALIDATION_FAILEDif the RPC can't confirm ownership).
Routes to composer (proxy) path
- Compound V2 family — direct
mintismsg.sender-only. - Fluid fresh-open deposits — no
accountIdoraccountId=0. The composer mints the new position NFT to itself, then transfers it toreceivervia the encoder'snftReceiverslot. - Native-ETH vaults on Fluid (deposit / withdraw / repay) and the CompoundV2 family — the composer forwards
msg.valueto the vault's payable entrypoint. Other lenders' composer paths reject native asset and require wrapped-native aspayAsset.
Refused with a receiver (the on-chain call has no recipient)
- Gearbox V3 credit-side
addCollateral— CA bound to operator — and Gearbox V3 borrow (the funds stay in the credit account). - Liquity V2 family — Stability Pool deposit / withdrawal (
provideToSP/withdrawFromSP) and trove collateral withdrawal (withdrawCollpays the trove's own receiver). - Aave V4 native-gateway path —
supplyAsCollateralNativehas no recipient slot.
Unsupported in any single tx
- Init Capital — position NFT swept to operator.
- Euler V2 / Silo V2/V3 native deposit paths — router/orchestrator credits msg.sender.
- Fluid repay — on-chain primitive permits it, direct handler doesn't expose a receiver slot yet (routes to proxy as a conservative default).
Repay with a receiver settles the RECEIVER's debt with the operator's funds. On Euler V2, Morpho Blue / Lista and Exactly, isAll=true with a receiver is refused: the amount owed would be read from the operator, not the receiver — pass an explicit amount.
When the lender cannot honor a custom receiver on the direct path and mode is omitted, the API routes the request through the composer automatically — the response then targets the composer (see mode). Forcing mode=direct with a custom receiver the direct call cannot express (Compound V2, Init, a Fluid fresh open) is refused with 400 INVALID_PARAM, and a leg with no recipient (above) with 400 ACTION_FAILED; a receiver is never silently replaced by the operator.
The optional simulation preview is computed for the operator, not the receiver: with a custom receiver it shows the position change on the operator's account. Read the receiver's position from the user-positions endpoint after execution. |
| isAll | query | boolean | no | Withdraw/repay full balance.
When on-chain position data is available, the API compares amount against the actual balance to decide the effective mode:
- If
amountcovers the full position the action is treated as "withdraw/repay all" regardless of this flag. - If
isAllis true butamountis less than the position, the API falls back to a partial action with the providedamountto avoid on-chain reverts.
Without on-chain data, the flag is trusted as-is. |
| receiveAsset | query | string | no | Asset to receive.
Use the zero address to receive native ETH on lenders whose vault returns native (e.g. Init Capital, Fluid native-ETH vaults, CompoundV2 cETH markets).
Proxy mode: defaults to market asset. Native delivery is supported only when the lender does so on-chain (e.g. Fluid, CompoundV2); otherwise the composer rejects. |
| isShares | query | boolean | no | Amount is in shares (direct mode) |
| accountId | query | string | no | Per-position identifier. Lender-specific:
- Init Capital — account ID, required for borrow/withdraw/repay.
- Euler V2 — sub-account index (0..255), defaults to 0.
- Gearbox V3 — credit-account address (alias:
creditAccount). - Dolomite — margin account number (uint256,
0= the default cross-margin account), oriso:<marketId>:<n>for a position inside an isolation-mode VAULT (dGM, dsavETH, dGMX …). The vault, not the user, owns that DolomiteMargin account;nmust be >= 100 for a borrow position and0is the vault's supply-only balance. Position ids come back in this form from/v1/data/lending/positions. - Fluid — position NFT id. Omit or pass
0to mint a new position; supply an existing nftId to act on an existing position. Required for borrow/withdraw/repay; for deposit it unlocks the direct path with a customreceiver(deposit lands on the receiver-owned NFT after pre-flightownerOfvalidation). | |slippage| query | number | no | Slippage tolerance in basis points (50= 0.5%,100= 1%).
Required whenever the action has to price something:
- Aggregator swap — when
payAsset/receiveAssetdiffers from the market's underlying, the currency conversion is routed through a DEX aggregator and this bounds that swap. - Order-book take (Morpho Midnight) — bounds the worst execution price accepted while filling offers.
Ignored when neither applies (same-asset action on a pool lender). |
| simulate | query | boolean | no | When true, the API fetches the operator's on-chain balances and returns projected post-trade metrics in the simulation field. The projection is always of the operator's account: with a custom receiver it does not show the receiver's position. Default: false. |
Response 200
| Field | Type | Description |
|---|---|---|
success | True | |
data | object | Informational data (quotes, simulation results, etc.) |
data.rateImpact | object[] | Projected interest-rate impact per market. Single-market actions produce 1 entry; loop actions produce 2. Null if IRM data is unavailable. |
data.rateImpact[].marketUid | string | Market identifier (format: lender:chainId:address) |
data.rateImpact[].utilization | object | A current/projected pair for a single rate metric. |
data.rateImpact[].utilization.current | number | Current value |
data.rateImpact[].utilization.projected | number | Projected value after the action |
data.rateImpact[].borrowRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].borrowRate.current | number | Current value |
data.rateImpact[].borrowRate.projected | number | Projected value after the action |
data.rateImpact[].depositRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].depositRate.current | number | Current value |
data.rateImpact[].depositRate.projected | number | Projected value after the action |
data.rateImpact[].intrinsicYield | number | The asset's own yield in percent (LST staking yield, RWA NAV growth, a Gearbox quota rate as a negative), size-invariant, ADDED to both sides: earned on a deposit, forgone on a borrow. Absent when the lender meta was not readable when the response was built — never defaulted to 0 in that case. |
data.rateImpact[].depositRewardApr | number | Sum of reward-stream APRs (%) on the deposit side. Absent with intrinsicYield. |
data.rateImpact[].borrowRewardApr | number | Sum of reward-stream APRs (%) rebated on the borrow side. Absent with intrinsicYield. |
data.rateImpact[].effectiveDepositRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].effectiveDepositRate.current | number | Current value |
data.rateImpact[].effectiveDepositRate.projected | number | Projected value after the action |
data.rateImpact[].effectiveBorrowRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].effectiveBorrowRate.current | number | Current value |
data.rateImpact[].effectiveBorrowRate.projected | number | Projected value after the action |
data.rateImpact[].flat | boolean | True when the market has no utilization curve to shift (Morpho Blue collateral, a fixed-rate broker): current === projected, taken from the published rates, and utilization is NaN. Present so the leg is not silently missing from a position preview. |
data.rateImpact | object[] | Projected interest-rate impact per market. Single-market actions produce 1 entry; loop actions produce 2. Null if IRM data is unavailable. |
data.rateImpact[].marketUid | string | Market identifier (format: lender:chainId:address) |
data.rateImpact[].utilization | object | A current/projected pair for a single rate metric. |
data.rateImpact[].utilization.current | number | Current value |
data.rateImpact[].utilization.projected | number | Projected value after the action |
data.rateImpact[].borrowRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].borrowRate.current | number | Current value |
data.rateImpact[].borrowRate.projected | number | Projected value after the action |
data.rateImpact[].depositRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].depositRate.current | number | Current value |
data.rateImpact[].depositRate.projected | number | Projected value after the action |
data.rateImpact[].intrinsicYield | number | The asset's own yield in percent (LST staking yield, RWA NAV growth, a Gearbox quota rate as a negative), size-invariant, ADDED to both sides: earned on a deposit, forgone on a borrow. Absent when the lender meta was not readable when the response was built — never defaulted to 0 in that case. |
data.rateImpact[].depositRewardApr | number | Sum of reward-stream APRs (%) on the deposit side. Absent with intrinsicYield. |
data.rateImpact[].borrowRewardApr | number | Sum of reward-stream APRs (%) rebated on the borrow side. Absent with intrinsicYield. |
data.rateImpact[].effectiveDepositRate | object | A current/projected pair for a single rate metric. |
data.rateImpact[].effectiveDepositRate.current | number | Current value |
data.rateImpact[].effectiveDepositRate.projected | number | Projected value after the action |
Example response
{
"success": true,
"data": {
"rateImpact": [
{
"marketUid": "AAVE_V3:8453:0x4200000000000000000000000000000000000006",
"utilization": {
"current": 1.0,
"projected": 1.0
},
"borrowRate": {
"current": 1.0,
"projected": 1.0
},
"depositRate": {
"current": 1.0,
"projected": 1.0
},
"intrinsicYield": 10.37,
"depositRewardApr": 1.0,
"borrowRewardApr": 1.0,
"effectiveDepositRate": {
"current": 1.0,
"projected": 1.0
},
"effectiveBorrowRate": {
"current": 1.0,
"projected": 1.0
},
"flat": true
}
]
},
"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
Transaction calldata and approvals for withdraw
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.