Leverage (loop)
GET/v1/actions/loop/leverage
Open a leveraged position via borrow → swap → deposit. Identify the debt market with marketUidIn and collateral market with marketUidOut. Omit account for quote-only (returns data.quotes with price deltas). Include account to build full transaction calldata (populates actions with alternatives, transactions, and permissions).
Response actions fields (when account is provided):
alternatives: DEX aggregator swap transactions sorted best-output-first. Pick one to execute.transactions: Pre-trade setup — mode/e-mode switch (Aave V3) or collateral enable (Venus). Empty if not needed.permissions: ERC20 approvals (targeting the composer contract) and lender borrow delegations (targeting the lending protocol contract). Only missing approvals are returned. Execute these first.
Paying the margin: which payAsset a pair accepts is lender-specific — GET /v1/actions/loop/leverage/pay-assets lists them for the same two market uids, and an asset outside that list is refused here as PAY_ASSET_UNSUPPORTED (with the accepted set in details.payAssets) before anything is quoted.
Lista DAO fixed-term (brokered) markets: when marketUidIn is brokered, pass termId to open the loop's debt leg as a fixed-term broker loan. The collateral deposit leg is handled automatically (routed through the market's collateralProvider when set, e.g. slisBNB). Closing such a loop requires the loan's loanId on /v1/actions/loop/close.
Trade economics (data.quotes[i].economics, best route mirrored at data.economics): a TradeEconomics block per route, so a client never re-derives an entry cost or a break-even from the deltas. It carries entryCostUsd (swap slippage vs the oracle, day-0 fees off the debt leg's term sheet + the flash fee, gas over the outstanding permissions + main transaction), carryPerDayUsd at the PROJECTED rates the same IRM path behind rateImpact produces (with and without incentives — carryBasis says sticker when the projection was unavailable), breakEvenDays, roundTripDays (the exit ASSUMED symmetric to the entry until a close is quoted — closeQuoted: false; null on a debt with no early exit), the trade's clock (horizonDays: PT expiry, debt maturity, rolling term) and breaksEvenBeforeHorizon. With an account, existingExposure makes it MARGINAL to the book the trade lands in: the debt the account already holds in the same market reprices with the new borrow, and that cost is netted out of the carry. Whatever could not be computed is null with a line in notes — the block itself is never omitted. Gas is a real eth_estimateGas when it succeeds (gas.estimated), a typical-size table otherwise; on a quote-only request it is not priced.
Plain-text reference — GET /v1/actions/loop/leverage
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
route | query | auto, bundler3, native, composer, proxy | no | Which assembly to use, for lenders that have more than one. Morpho Blue only today. |
- (omitted) —
auto: prefer the protocol’s own tooling and fall back to the composer for anything it cannot express (a cross-market pair, a margin paid in a foreign asset, anEXACT_OUTPUTclose, a chain with no bundler3 or no swap adapter). bundler3/native— force Morpho’s bundler3. A request it cannot serve becomes an ERROR instead of silently falling back, which is what makes the route testable.composer/proxy— force the 1delta composer.
The default matters: the bundler3 route asks for setAuthorization(GeneralAdapter1),
Morpho’s own audited adapter, where the composer route asks for a standing grant on ours. |
| marketUidIn | query | string | yes | Market identifier for input side (lender:chainId:address). |
| marketUidOut | query | string | yes | Market identifier for output side (lender:chainId:address). |
| slippage | query | number | yes | Slippage tolerance (basis points) |
| account | query | string | no | Account address. Include to build transaction, omit for quote-only. |
| debtAmount | query | string | yes | Debt amount in wei |
| payAsset | query | string | no | Asset the margin is paid in (optional zap-in asset). Which assets a
given pair accepts is LENDER-SPECIFIC — the collateral, the debt, and
native when the wrapped native is one of the two on the composer
lenders; collateral only on Curvance / Fraxlend / Resupply / Twyne;
no native on LlamaLend; the debt asset only on Exactly; the collateral
(or native into a WETH collateral) on Flying Tulip, whose open is a
signed flow: answers carry one actions.signs[0] step each until none
is owed. Ask GET /v1/actions/loop/leverage/pay-assets
with the same marketUidIn / marketUidOut to list them in advance.
An asset outside that list is refused up front with
PAY_ASSET_UNSUPPORTED, details.payAssets carrying the accepted set
(Twyne excepted — it answers ZAP_REQUIRED with a swap quote instead).
Native is the zero address. |
| payAmount | query | string | no | Pay amount in wei |
| hedge | query | boros | no | boros — attach data.hedge: the Pendle Boros SHORT that locks the
perp FUNDING the new collateral earns (sUSDe, wstUSR …) at a fixed rate,
sized on the best route’s collateral USD × hedgeRatio. A second set of
transactions on ARBITRUM (42161), never part of the loop bundle and not
atomic with it; available: false + reason when the collateral earns no
funding. Tuning: hedgeRatio (default 1), hedgeTenor (longest|nearest),
hedgeExposure (ethena|resolv), hedgeMarketIds, hedgeMarginBuffer,
hedgeSlippage (bps of APR). See /v1/actions/boros/hedge. |
| marginFromAccountId | query | integer | no | Dolomite only. Fund the margin from this existing Dolomite sub-account
instead of the wallet, collapsing the open to ONE transaction: the transfer
rides inside the leverage call and no deposit or token approval is emitted.
Omit to pull payAmount from the wallet (two transactions — Dolomite's
trader proxy can only move funds between the caller's own sub-accounts).
0 is the default sub-account and a valid source. Passing the trade
account's own id is treated as absent, since the margin is already there.
The source must hold payAmount of the pay asset or the transaction reverts. |
| leverage | query | number | no | Target leverage multiplier |
| usePendleMintRedeem | query | boolean | no | Use Pendle mint/redeem |
| borrowMode | query | 0, 1, 2 | no | Borrow mode (0=NONE, 1=STABLE, 2=VARIABLE) |
| termId | query | integer | no | Lista DAO fixed-term (brokered) debt markets only. Selects the fixed term to borrow for
the loop's debt leg, from the debt market's terms[] rate card (the MarketTerm.termId).
Required when marketUidIn is a brokered market — the borrow leg routes through the broker;
ignored otherwise. |
| posId | query | string | no | Twyne only. Which collateral vault to lever — a Twyne position is a
contract the borrower deploys, and one account may own several in a market,
each with its own collateral, debt and liquidation LTV. Omit when the account
owns exactly one (or none). Passing none while owning several is refused
rather than guessed. |
| liqLtv | query | integer | no | Twyne only, and only when the account owns NO vault in this market yet.
The liquidation LTV (1e4 scale) the position this call DEPLOYS will carry.
The deploy runs as the first item of the same EVC batch, so a first-time
borrower opens a leveraged position in one transaction; the response then
carries createdPosition and a collateralVault address that does not exist
until the transaction lands.
Defaults to the market’s LIVE ceiling (maxTwyneLTVs) — the extra LTV is the
reason to borrow here, and the value is not a commitment: setTwyneLiqLTV
moves it at any time with no cooldown, and lowering it RELEASES reserved
credit and lowers what the borrower pays. Must sit inside the live band
(externalLiqLTV × externalLiqBuffer … maxTwyneLTVs); both bounds move, so
a value is validated against a fresh read and refused by name rather than
reverting ValueOutOfRange() on chain.
Ignored when a position already exists — moving an existing dial is
/v1/actions/twyne/set-liq-ltv. |
| accountId | query | string | no | Account ID (Init Capital, Euler V2, Dolomite). Dolomite: a uint256 account number, or iso:<marketId>:<n> for a position inside an isolation-mode vault (dGM, dsavETH, dGMX …); borrow positions on those need n >= 100, account 0 is the supply-only vault balance. |
| selectedMode | query | integer | no | Position mode for new positions (Init Capital) |
Response 200
| Field | Type | Description |
|---|---|---|
success | True | |
data | object | Informational data (quotes, simulation results, etc.) |
data.lender | string | Protocol identifier |
data.quotes | object[] | Candidate routes, best output first. Execute exactly one. |
data.quotes[].deltas | object | |
data.quotes[].deltas.aggregator | string | Aggregator source |
data.quotes[].deltas.tradeInput | number | Trade input amount |
data.quotes[].deltas.tradeOutput | number | Trade output amount |
data.quotes[].deltas.deltas | object | Balance deltas |
data.quotes[].rateImpact | object[] | Projected interest-rate impact per market for THIS quote (its own trade amounts). Omitted if IRM data is unavailable. |
data.quotes[].rateImpact[].marketUid | string | Market identifier (format: lender:chainId:address) |
data.quotes[].rateImpact[].utilization | object | A current/projected pair for a single rate metric. |
data.quotes[].rateImpact[].borrowRate | object | A current/projected pair for a single rate metric. |
data.quotes[].rateImpact[].depositRate | object | A current/projected pair for a single rate metric. |
data.quotes[].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.quotes[].rateImpact[].depositRewardApr | number | Sum of reward-stream APRs (%) on the deposit side. Absent with intrinsicYield. |
data.quotes[].rateImpact[].borrowRewardApr | number | Sum of reward-stream APRs (%) rebated on the borrow side. Absent with intrinsicYield. |
data.quotes[].rateImpact[].effectiveDepositRate | object | A current/projected pair for a single rate metric. |
data.quotes[].rateImpact[].effectiveBorrowRate | object | A current/projected pair for a single rate metric. |
data.quotes[].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.quotes[].netApr | object | The trade's carry at post-trade rates — the position net APR. annualUsd is the change in yearly carry the trade causes (deposit/withdraw legs earn, borrow/repay legs cost); aprPct puts it on the net equity the trade moves (the margin on an open) and is null when that equity is not positive (a pure leverage step, a swap). The bare figures exclude rewards; *Total include them. |
data.quotes[].netApr.annualUsd | number | |
data.quotes[].netApr.annualUsdTotal | number | |
data.quotes[].netApr.equityUsd | number | |
data.quotes[].netApr.aprPct | number | |
data.quotes[].netApr.aprPctTotal | number | |
data.quotes[].netApr.legs | object[] | |
data.quotes[].economics | object | THIS route's trade economics — TradeEconomics on an open, CloseEconomics on a close. See the schema for the discipline. |
data.quotes[].economics.notionalUsd | object | The position this route creates, at the oracle. equity is the margin the user brings (0 on a pure leverage step). |
data.quotes[].economics.entryCostUsd | object | Day-0 cost of getting in. slippage = swap input at the oracle − swap output at the oracle (floored at 0; slippageBp keeps the sign). gas is null when it could not be priced. |
data.quotes[].economics.slippageBp | number | Signed swap-vs-oracle, BP of the swap input. Negative = the route costs money. |
data.quotes[].economics.entryCostBp | number | entryCostUsd.total over the DEBT notional, BP. |
data.quotes[].economics.fees | object[] | |
data.quotes[].economics.carryPerDayUsd | object | The trade's carry per day at PROJECTED rates — what it earns after its own borrow has moved the pool. total counts incentives, base does not. With an account, the repricing of the existing book is netted out. |
data.quotes[].economics.carryBasis | projected, sticker, none | |
data.quotes[].economics.breakEvenDays | object | entryCostUsd.total / carryPerDayUsd. Null when the carry is not positive (never pays for itself — see notes) or unknown. |
data.quotes[].economics.roundTripDays | object | (entry + exit cost) / carry. closeQuoted says whether the exit was PRICED (a real close quote) or ASSUMED symmetric to the entry (the default today). Null on a debt that cannot be repaid early (Midnight): the round trip is at maturity, not before. |
data.quotes[].economics.horizonDays | number | Days until the trade's clock runs out. Null = perpetual. |
data.quotes[].economics.horizon | object | |
data.quotes[].economics.breaksEvenBeforeHorizon | boolean |
Request
Responses
- 200
- 400
- 429
- 500
- 502
Quote or full build response
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.