Deposit and borrow (simulate)
POST/v1/actions/lending/deposit-and-borrow
Deposit and borrow with post-trade simulation. Same parameters as GET. Optionally send a JSON body with current portfolio state (balanceData, aprData, positions) to receive projected post-trade metrics in the simulation field — if omitted, the API fetches balances on-chain automatically. Use the data returned by the user-positions endpoint directly — always include positions for accurate health-factor and borrow-capacity projections.
Plain-text reference — POST /v1/actions/lending/deposit-and-borrow
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
marketUid | query | string | yes | The COLLATERAL (supply) market, lender:chainId:address. |
debtMarketUid | query | string | yes | The DEBT (borrow) market — same lender and chain (Exactly: any two Exactly markets, it is cross-margin). |
collateralAmount | query | string | yes | Raw amount supplied (alias amount). With a foreign payAsset, the amount of that asset swapped into the collateral. |
borrowAmount | query | string | yes | Raw amount borrowed. |
payAsset | query | string | no | Currency the collateral is paid in. Default: the collateral underlying; native (zero address) wraps where the lender supports it; another token adds an aggregator swap (needs slippage) where supported, else 400 UNSUPPORTED. |
receiveAsset | query | string | no | Delivery currency of the borrow: the debt underlying (default) or native (unwrap of a wrapped-native debt). Any other token is 400 UNSUPPORTED. |
operator | query | string | yes | The position owner and caller (signer). |
receiver | query | string | no | Recipient of the tokens paid out (the borrow, or the withdrawn collateral). Default: operator. |
accountId | query | string | no | Sub-account / position id (Euler sub-account, Morpho-style position, NFT id, Liquity trove id, …). Aliases: nftId, and troveId on withdraw-and-repay. |
borrowMode | query | string | no | Aave rate mode of the debt (alias lendingMode). Default 2 = VARIABLE on Aave-type lenders. |
slippage | query | string | no | Basis points. Required when a payAsset / receiveAsset conversion adds an aggregator swap. |
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. |
modeId | query | string | no | Aave V3 only: the e-mode category the borrow capacity was computed against. When it differs from the account's, a setUserEMode pre-step is prepended (a user transaction — it cannot be composed on behalf). |
debtTermId | query | string | no | Fixed-term debt: Exactly maturity / Lista broker term id (alias termId). collateralTermId is the supply-side equivalent. |
interestRate | query | string | no | Liquity family / River trove open: the user-set interest rate. With maxUpfrontFee, maxFeePercentage, ownerIndex. |
bands | query | string | no | LlamaLend: the number of soft-liquidation bands (4–50). Fixed for the life of the loan; omitted → the market default. |
liqLtv | query | string | no | Twyne: the liquidation LTV (1e4 scale) of a position DEPLOYED by this open; ignored when a position exists. |
bidRate | query | string | no | Term auction: the sealed bid rate (alias rate), with optional termNonce / termSeed seal inputs. |
collateralAsset1 | query | string | no | Two-token (DEX-LP) sides, e.g. Fluid smart vaults: the second token of the collateral, with collateralAmount1; debtAsset1 / debtAmount1 for the debt side; collateralMinShares / debtMaxShares bound the share math. Absent = single-sided. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
balanceData | object | yes | Aggregated balance data for a sub-account. |
balanceData.deposits | number | no | Total deposits in USD |
balanceData.debt | number | no | Total debt in USD |
balanceData.adjustedDebt | number | no | Debt adjusted for borrow factors |
balanceData.collateral | number | no | Collateral value in USD |
balanceData.collateralAllActive | number | no | Collateral if all assets were enabled |
balanceData.borrowDiscountedCollateral | number | no | Collateral discounted by borrow factors |
balanceData.borrowDiscountedCollateralAllActive | number | no | Discounted collateral if all enabled |
balanceData.nav | number | no | Net asset value (deposits - debt) |
balanceData.deposits24h | number | no | Deposits 24h ago (for change calculation) |
balanceData.debt24h | number | no | Debt 24h ago |
balanceData.nav24h | number | no | NAV 24h ago |
balanceData.rewards | object[] | no | Pending reward token claims. Each entry represents a single reward program. |
balanceData.rewards[].asset | string | no | Reward token contract address |
balanceData.rewards[].totalRewards | number | no | Total accumulated rewards (token units) |
balanceData.rewards[].claimableRewards | number | no | Immediately claimable rewards (token units) |
aprData | object | yes | APR breakdown for a sub-account. |
aprData.apr | number | no | Net APR (deposit - borrow) |
aprData.depositApr | number | no | Weighted deposit APR |
aprData.borrowApr | number | no | Weighted borrow APR |
aprData.rewardApr | number | no | Total reward APR |
aprData.rewardDepositApr | number | no | Reward APR on deposits |
aprData.rewardBorrowApr | number | no | Reward APR on borrows |
aprData.intrinsicApr | number | no | Intrinsic yield APR (e.g., stETH staking) |
aprData.intrinsicDepositApr | number | no | Intrinsic yield APR portion from deposits |
aprData.intrinsicBorrowApr | number | no | Intrinsic yield APR portion from borrows |
aprData.rewards | object | no | Per-reward-token APR breakdown. Keys are reward token addresses. |
modeId | string | no | Mode/config key from userConfig.selectedMode (defaults to "0") |
positions | object[] | no | Current lending positions from the matching sub-account's positions array. The full LendingPosition objects returned by user-positions are accepted — only the fields in SimulationPosition are used. Always include this for accurate health-factor and borrow-capacity projections. |
positions[].marketUid | string | yes | Unique market identifier (format: {lender}:{chainId}:{address}) |
positions[].depositsUSD | number | yes | Deposit amount in USD |
positions[].debtUSD | number | yes | Variable debt in USD |
positions[].debtStableUSD | number | yes | Stable debt in USD |
positions[].collateralEnabled | boolean | yes | Whether this asset is enabled as collateral |
Response 200
| Field | Type | Description |
|---|---|---|
success | True | |
data | object | Simulation results including projected health factor and borrow capacity |
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
Transaction(s) with post-trade simulation
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.