Skip to main content

Why This Matters

Leverage is the most requested feature in every lending product, and the hardest to ship. Doing it by hand means a dozen supply/borrow/swap round trips, a new liquidation risk on every leg, and a partially-built position if any step fails. Most teams never ship it. We’ve done that work. One call returns one transaction that builds the whole position - or unwinds it - atomically. If any leg fails, nothing happens.

What It Does

Let users take a leveraged position on a lending market: supply collateral, borrow against it, swap the borrowed token back to collateral, and repeat until they reach the leverage they asked for. Unwinding runs the same loop in reverse. Both are a single signature. You call our API, we return a transaction payload. You or your users sign and broadcast. Users get leverage, you capture the flow. Important: Users must first create a Credit Account and fund it with the collateral token. Looping never pulls from the user’s wallet mid-transaction - the Credit Account must already hold initial_collateral_amount.

How a Loop Works

A loop is one transaction containing many iterations. Each iteration:
  1. Supply the collateral token to the lending market
  2. Borrow the loan token at the requested loan_to_value
  3. Swap the borrowed token back to the collateral token
The output of step 3 becomes the next iteration’s supply. Iterations continue until total collateral reaches multiplier × initial_collateral_amount.

Every swap has a guaranteed floor

The API never chains one leg onto a hoped-for swap output. Each swap carries a minimum output enforced on-chain, and the next supply is sized against that minimum. A fill anywhere inside your slippage tolerance can never break a later step. Anything a swap delivers above the floor stays in the Credit Account as recoverable surplus, never lost. preview.estimated_max_dust is the worst-case bound on it. Tighter max_slippage_percent means less surplus.
estimated_max_dust is not a cost - it is money kept. The cost of entering a loop is preview.estimated_swap_cost: DEX fees plus price impact, in collateral-token units, excluding gas. That’s the amount the position has to earn back before it’s ahead.

When the collateral is a vault share

Some markets take an ERC-4626 vault share as collateral - a yield-bearing token like a Morpho vault’s steakUSDC. Those tokens have no honest DEX route: aggregators will still quote them, but through dust pools at prices far off net asset value. Since the loop derives its guaranteed floors from the quote, a mispriced quote would be executable rather than merely unroutable. So the conversion goes through the vault instead of a DEX: a loop swaps the borrow token to the vault’s underlying asset and calls the vault’s own deposit(), and an unwind redeem()s the shares and swaps the underlying back. Both settle at net asset value. A direct route for the share token is only ever used when it prices within 1% of NAV. You do not configure any of this - pass the share token as collateral_token and the engine picks the path. Browse the vaults themselves with GET /v2/earn/vaults.

Supported Venues

Select the protocol per request with the protocol field. It defaults to AAVE, so existing integrations need no changes. HyperLend and Felix are not separate protocol values — they are what Aave and Morpho resolve to on HyperEVM. Euler V2 markets are available today for plain borrowing through Crypto-Backed Loans; full leverage support for them is on the way.

Finding a market to loop

Both discovery endpoints return everything you need to populate a market picker, and both accept a chain filter:
  • /v2/earn/aave_markets returns each Aave reserve with its supply and borrow APY per chain. Despite living under earn, it is the rates source for Aave-style credit too — including HyperLend, which it serves as a read-only exception on chain=hyperevm. Pick a collateral token and a borrow token from it and pass their symbols straight to /loop.
  • /v2/credit/morpho_markets returns the curated Morpho market set with live lltv, borrow_apy, utilization, and available_liquidity, read on-chain per request. Sort by tvl_usd, liquidity_usd, or lltv. The market_id you pass to /loop comes from here, and the market’s own pair fixes your collateral_token and borrow_token.
Two things to check before offering a market: lltv (Morpho) or the reserve LTV (Aave) is the ceiling on loan_to_value, and available_liquidity must exceed the loop’s total borrow or the build returns a 400. Neither discovery endpoint knows anything about a particular user - both describe the market, not the account. Two per-account reads complement them:
  • GET /v2/credit/looped_positions - the leveraged positions this Credit Account already holds, with live leverage, health factor, and net APY. This is the one to poll for a dashboard; see Track Looped Positions.
  • GET /v2/credit/positions - the account’s raw collateral and debt, plus a borrowable_tokens array giving the maximum each token can still be borrowed at and its current APY. Only this endpoint reports remaining borrow capacity; looped_positions does not carry it.

Chain coverage

Other chains in the chain enum have no credit venue deployed yet and return a 422 naming the chains that do.

Preview Before You Build

preview: true gives you the projected end state without spending a firm quote. Use it on every call made while a user is moving a slider, and leave it false only for the build they actually intend to sign. This matters: on firm-quoted pairs, quotes are single-use maker commitments. Requesting them for displays that are never executed degrades the pricing available to everyone on the API.
preview: true guarantees no firm quote was spent. It does not guarantee transaction is absent. On a pair no firm provider covers - and on pricing: "market" - the request falls through to the aggregator and a signable transaction comes back anyway. Decide what to do from the field you actually got, not from the flag you sent.
Three outcomes are possible:
These endpoints omit null fields from the JSON entirely rather than sending null. Test for absence (?. / .get(...)), not for a null value - "quote_expires_at": null never appears on the wire.

Pricing policy

The pricing field controls how swap legs are routed: pricing: "firm" is incompatible with gas_sponsorship: true - sponsored transactions always route through the aggregator. The two routes differ in where the price comes from. A firm provider commits a quote at a fixed price for a short window, so the fill is exact: zero slippage and no dust, but the quote expires. A DEX aggregator routes the swap across on-chain pool liquidity at the prevailing rate, so the fill moves with the market and is protected by an on-chain minimum output derived from max_slippage_percent. The pricing values are named firm and market after the two.

Reading the pricing fields back

The signing window on a firm build is short - a few tens of seconds, set by the maker, not by us. Read quote_expires_at; never hardcode a TTL. If the user takes too long, re-call the endpoint for a fresh payload rather than broadcasting the old one. Expired quotes surface as a 409 Firm quotes expired on pricing: "firm", and are silently rebuilt on the aggregator route under auto.

Open a Loop

Build a 2x WETH position on Aave, borrowing USDC at 70% LTV against 1 WETH already sitting in the Credit Account.
Set preview: false for the build the user is about to sign, then sign and broadcast the returned transaction as usual.

What the loop preview tells you

There is no breakeven field, but you can derive one. Multiply estimated_swap_cost by the collateral price to get the entry cost in USD, then divide by the daily carry on equity (not on total collateral - net_apy is already a return on equity):days ≈ entry_cost_usd ÷ (equity_usd × net_apy ÷ 100 ÷ 365)net_apy and net_usd_value (the equity) both come from looped positions. If net_apy is at or below zero the position never breaks even at the current rates - say that rather than showing a large number.

Looping on Morpho

Same call, swapping the market identifier for a market_id. This is the wstETH/WETH market at 96.5% LLTV on Ethereum; the body also carries owner and chain.
collateral_token and borrow_token must match the market’s own pair, or the call is a 422. market_id is Morpho-only and emode_category is Aave-only — sending either on the wrong protocol is a 422 rather than a silent no-op.

Track Looped Positions

GET /v2/credit/looped_positions reconstructs each leveraged position from indexed on-chain events - a transaction carrying lending, borrowing, and swap legs is a loop. It takes chain and owner, with no filters or pagination. Detection is event-shaped, not API-created: a position assembled by hand through /v2/credit/bundle shows up the same way. The events do have to belong to the Credit Account derived from owner - leverage held directly by an EOA is invisible here. If the Credit Account isn’t deployed yet, the call is a 400.
Each position carries status (OPEN / CLOSED), live current state, lifetime totals, and a full history of classified transactions (loop_open, loop_increase, unwind, liquidation, and single-sided actions), each with the detected swap leg. Two fields deserve care:
  • health_factor_scope is "market" on Morpho (isolated per market) and "account" on Aave. Aave collateral is pooled, so one health factor is shared by every Aave position on the account - see aave_account_summary for the account-level figures.
  • net_apy is the levered carry on equity: leverage × collateral_apy − (leverage − 1) × borrow_apy. It is a snapshot at today’s rates, not a forecast, and it is denominated in the debt asset - a positive net_apy still loses USD value if the pair moves against the user. It is null when either leg is unknown rather than guessed.

Unwind a Loop

POST /v2/credit/unloop runs the loop in reverse: withdraw collateral, swap it to the borrow token at a guaranteed minimum, repay.

Positions that need two transactions

Each withdrawal is sized to keep the health factor at or above 1.02 at that step. A position opened very close to its liquidation threshold may not have enough headroom to reach the target in one transaction. Set allow_partial: true to take the maximum single-transaction progress instead of a 400. The response comes back with preview.fully_unwound: false; call unloop again from the now much lower leverage to finish. Without it, an unreachable target is an error. The unloop preview adds ending_collateral, ending_debt, ending_multiplier, and fully_unwound to the shared fields.

Closing an Aave position that shares a reserve

Aave holds one pooled balance per reserve, not one per pair. So if an account has two open Aave loops using the same collateral token, a full close of one does not withdraw the whole reserve balance - it withdraws only that position’s attributed share, leaving the rest supplied to back the other loop. preview.ending_collateral reports what stays behind, so read it rather than assuming zero on a full close. Morpho markets are isolated and have no such interaction.

Rebalance a Whole Book

POST /v2/credit/rebalance moves several positions toward their desired end states in one atomic transaction. Each entry in targets names a position and a goal - target_equity_usd (size), target_multiplier (leverage), both, or close: true. The engine derives one of seven directions per target and reports it as preview.per_target[].direction: Three properties are worth designing around:
  • Scoped, not a portfolio wipe. Positions not named in targets are left untouched.
  • It releases capital before it consumes it. Delevers and closes run first, freed collateral is routed between positions with capital-routing swaps when the tokens differ, then grows and opens run. A shrink on one position can fund a grow on another - including across protocols, in the same transaction.
  • Growth beyond freed capital comes from the Credit Account’s idle balance. If the book grows past what’s available you get a 422 asking you to fund the account first with /v2/credit/transfer.
When every named target is already at its goal, the plan is a no-op: preview still comes back with every direction set to noop, and there is no transaction to sign. That makes rebalance safe to poll in a converge loop.
Rebalance runs on Ethereum, Base, Arbitrum, and BNB Chain. It is not available on HyperEVM - use /loop and /unloop there. Also note that at most one Aave target per collateral token and per borrow token is allowed per call, because Aave pools collateral and debt per reserve rather than per pair.

HyperEVM: HyperLend and Felix

Looping runs on HyperEVM (chain hyperevm) against two venues: HyperLend is registered as the chain’s Aave, so you address it with protocol: "AAVE" - there is no separate protocol value.
A few things behave differently here: The entry conversion is a mint, not a swap. There is no honest generic DEX path from WHYPE to wstHYPE, so the loop unwraps to native HYPE and mints through the staking Overseer at 1:1 - instant, permissionless, no venue spread. Exits route through a HyperEVM aggregator instead, because redemption through the Overseer queues for roughly a week and can never sit inside an atomic bundle. Loop bundles exceed the small-block gas limit. HyperEVM small blocks cap out at 2M gas; a multi-iteration loop needs more. The sending EOA must have HyperCore big blocks enabled before broadcasting, or the transaction will not be included. Capacity is tight. Supply caps and borrowable liquidity on these markets are small relative to the majors. Sizing is cap-aware and fails with an actionable 422 telling you the reachable size, rather than reverting at execution - surface that message to the user. Nothing else on the chain is swappable. Only the wstHYPE conversion pairs have a route on HyperEVM. Any credit action needing a general swap returns a 400 - including /v2/credit/borrow when token_in differs from collateral_token. Supply the collateral token directly. Rebalance is not available on HyperEVM. Use /loop and /unloop. Only four tokens are registered on HyperEVM: WHYPE, wstHYPE, stHYPE, and a chain-native USDC at its own address (not the mainnet one).

Errors Worth Handling

The OpenAPI spec only declares 200 and 422. These runtime errors are the ones a real integration hits, so handle them explicitly. Each carries a prose message with the actionable figure - surface it rather than a generic failure. Loop Unloop Both Under pricing: "auto" the 409 and 502 cases never surface - a firm-provider failure falls back to the aggregator transparently.

Use Cases

Levered staking carry: Loop a liquid-staking token against the asset it stakes. The collateral earns the staking rate, the debt costs the borrow rate, and leverage multiplies the spread. Example: 10 wstETH looped to 4x against WETH. Collateral earns 3.1%, WETH borrows at 2.4%. Levered carry on equity: 4 × 3.1% − 3 × 2.4% = 5.2% versus 3.1% unlevered. One-click leverage in a lending UI: Add a leverage slider next to an existing borrow flow. Call /loop with preview: true on every slider move to show projected health factor and entry cost, then build once on confirm. Automated deleveraging: Poll looped_positions, watch health_factor, and call /unloop with a target_multiplier when a position drifts toward its threshold. Users never see a liquidation. Call /unloop with preview: true first to read projected_health_factor, so you can pick the target multiplier that restores the buffer you want. Book-level portfolio moves: Use /rebalance to shift a whole set of loops at once - close a Morpho position and open an Aave one, funded by the same freed collateral, in a single signature.

API Reference

Open a Leveraged Loop

Build a levered position in one atomic transaction.

Unwind a Leveraged Loop

Delever or fully close a position.

List Looped Positions

Live leverage, health factor, net APY, and full history.

Rebalance the Book

Move several positions to their target states at once.

Next Steps

Crypto-Backed Loans

Plain borrowing, repayment, and position tracking.

Product Accounts

Create and fund the Credit Account a loop runs from.

Gas Sponsorship

Sponsor gas so users never need a native token.