> ## Documentation Index
> Fetch the complete documentation index at: https://docs.1tx.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Loops API

> Which lending pairs can be levered, and what each leg pays

A **loop** is: supply one asset as collateral, borrow another against it, swap back to the
collateral asset and re-supply, `n` times. It turns a supply APY `rs` and a borrow cost `rb` into
`f(L) = L·rs − (L−1)·rb`, linear in leverage `L`.

This endpoint answers *which pairs exist and roughly what they pay*. It does **not** tell you how
much leverage to run, and it does not publish a loop APY.

<Note>
  `L` follows from a risk budget, a gross-exposure cap, a health-factor floor and the rest of a
  book — none of which this API can see, and two consumers must be able to choose different `L`
  from the same row. `f(L)` additionally depends on whether an emission-priced reward counts, which
  is a judgement, not a venue fact. The API returns the frontier; the caller picks the point.
</Note>

## List Loopable Pairs

<span style={{color: '#10B981', fontWeight: 'bold'}}>GET</span> `/loops`

One row per (collateral, debt) pair inside one lending pool, on one chain. The set is materialized
by the daily sync and served from cache: no chain reads, and no pagination — the whole screen comes
back in one response.

### Query Parameters

<ParamField query="chainId" type="number">
  Restrict to one chain, e.g. `143`.
</ParamField>

<ParamField query="collateralId" type="string">
  Instrument ID of the supplied leg.
</ParamField>

<ParamField query="debtId" type="string">
  Instrument ID of the borrowed leg.
</ParamField>

Filters compose, and an empty match is `[]` rather than an error.

### Request

```bash theme={null}
curl -H "x-api-key: your-api-key" \
  "https://api.1tx.fi/api/v1/loops?chainId=143"
```

### Response

```json theme={null}
{
  "data": [
    {
      "loopId": "0x0000008feb2e28c4aea6972122672ddb25db4d3df4af7e83e7ffcf3ad8fb5adb",
      "chainId": 143,
      "pool": "0x80F00661b13CC5F6ccd3885bE7b4C9c67545D585",
      "protocol": "Neverland",
      "sameAsset": false,
      "collateral": {
        "instrumentId": "0x0000008f4ebefd380701541f3c3b8714bd828824fa2842de58ba96eea9758a3f",
        "symbol": "USDC",
        "tokenAddress": "0x754704Bc059F8C67012fEd69BC8A327a5aafb603",
        "apyBase": 2.6097,
        "apyReward": 7.7891,
        "totalSupplyUsd": 2182921,
        "tvlUsd": 926804
      },
      "debt": {
        "instrumentId": "0x0000008f8b183cc36cd7ab674e79e56a05bc89cd6a733a92f3ca89ac84bc0266",
        "symbol": "AUSD",
        "tokenAddress": "0x...",
        "apyBaseBorrow": 8.3853,
        "apyRewardBorrow": 5.6252,
        "totalSupplyUsd": 1412217,
        "totalBorrowUsd": 1075963,
        "debtCeilingUsd": null,
        "borrowable": true
      },
      "leverage": {
        "ltv": 0.85,
        "maxLeverage": 6.6667,
        "basis": "defillama-pool-default"
      },
      "reward": {
        "basis": "emission",
        "tokens": [
          {
            "address": "0xad96c3dffcd6374294e2573a7fbba96097cc8d7c",
            "symbol": "DUST",
            "legs": ["supply", "borrow"],
            "priceUsd": 0.03073,
            "priceSource": "dex-pair",
            "liquidityUsd": 825.7
          }
        ],
        "liquidityUsd": null,
        "emissionUsdPerDay": 631.66
      },
      "lastSyncedAt": "2026-09-20T06:00:00.000Z"
    }
  ],
  "count": 1,
  "asOf": "2026-09-20T06:00:00.000Z"
}
```

### Reading the row

<AccordionGroup>
  <Accordion title="reward.basis — read this before spending apyReward">
    `emission` means the reward APY is valued at the emission schedule and the token's quoted price,
    not at what a position could actually sell into. That is how the upstream number is built, and on
    a thin reward token the two diverge, and leverage multiplies the gap. The error is directional —
    it floats reward-heavy pairs to the top of any ranking — so discount the number rather than
    inherit it.

    `none` means neither leg pays a reward. `traded` is reserved for a reward APY derived from an
    executable quote and is not produced by this endpoint; a stream that could not be priced reports
    `emission`, never something more optimistic.
  </Accordion>

  <Accordion title="reward.liquidityUsd — where capacity actually binds">
    24h traded volume of the **thinnest** reward stream on the row, and `null` when any of them is
    unmeasured. `null` is unknown, not zero: the two must not be collapsed, because one is a reason to
    size down and the other is a reason to look again.

    Compare it with `emissionUsdPerDay`. A stream emitting more per day than its token trades per day
    has no exit at size, whatever the APY says.
  </Accordion>

  <Accordion title="leverage.ltv — the pool default, not this pair's">
    `basis: "defillama-pool-default"` is a warning, not decoration: the figure carries no e-mode, no
    per-collateral factor and no per-market LLTV, and the gap between a default and an effective LTV
    is the gap between very different ceilings. Size the screen with it; read the effective figure on
    chain before anything is signed.
  </Accordion>

  <Accordion title="sameAsset — a second risk axis when false">
    A cross-asset loop needs a swap at per-turn notional, so its cost grows with `L` and its quote can
    go stale. Its health factor also drifts with the collateral/debt price ratio, not only with
    interest: `HF = (collateral × liqThreshold) / (debt × r)`, so liquidation arrives at `r = HF₀` and
    the divergence budget is exactly `HF₀ − 1`. A same-asset loop at `HF = 1.03` is fine; a
    cross-asset one at `HF = 1.03` is three percent of price divergence from liquidation, and assets
    that are supposed to track each other have moved further than that.
  </Accordion>

  <Accordion title="What this screen cannot see">
    * **Routes.** Nothing here knows whether a swap route exists at per-turn notional. A pair can
      screen well on rates and have no venue that will fill the turns.
    * **Oracles.** Liquidation is decided by the pool's oracle, not by market price.
    * **Freshness.** Rates go stale intraday by design. This endpoint decides what to look at.
  </Accordion>
</AccordionGroup>

### How a row is built

The supply side of each pair is the collateral instrument's own snapshot — the same numbers
`/instruments` serves — and the borrow side comes from DeFiLlama's `/lendBorrow`, joined on the
pool id the two share. A reserve missing from `/lendBorrow` is not lending-shaped and is never
paired. A pair whose debt leg is not a listed instrument is omitted rather than synthesised, and a
collateral leg the venue gives no LTV (`ltv = 0`) can never start a loop however good its supply
rate looks.

`loopId` is deterministic from `(chainId, pool, collateralInstrumentId, debtInstrumentId)`. The
order of the legs is part of it: supplying USDC to borrow AUSD and supplying AUSD to borrow USDC
are different trades, and on the measured snapshot one of them had a negative gradient. Leverage is
deliberately not part of it — `L` is a request parameter, so there is one row per pair rather than
one per (pair, `L`).

## Build a Loop Bundle

<span style={{color: '#10B981', fontWeight: 'bold'}}>GET</span> `/loops/:loopId/calldata`

The tier-2 read. The screen said a pair exists; this re-reads it on chain at a pinned quote block,
builds every turn of the loop as **one ordered batch** for the account to sign, simulates the batch
atomically, and reports the position the simulation left behind. It is the same envelope as
`/instruments/:id/calldata` — `calls`, `requires`, `leftovers`, `minOut`, `expiresAt`, `simulation`
— plus a `leverage` block (the venue fact) and a `simulated` block (the measurement).

It still does not choose `L`. `leverage` is a request parameter, and the API builds whatever
reachable leverage it is asked for.

### Query Parameters

<ParamField query="action" type="string" required>
  `open` supplies `amount` and levers it to `leverage`. `adjust` moves an open position to
  `leverage` with its equity unchanged — up or down. `close` unwinds fully, to zero debt, in one
  batch.
</ParamField>

<ParamField query="account" type="string" required>
  The smart account that supplies, borrows and receives.
</ParamField>

<ParamField query="leverage" type="string">
  Target collateral ÷ equity at the pool's oracle, e.g. `4` or `3.25`. Required for `open` and
  `adjust`; rejected for `close`.
</ParamField>

<ParamField query="amount" type="string">
  Equity to supply, in raw units of the collateral token. Required for `open`, rejected otherwise.
  `max` is never accepted: every amount in a loop is hard-coded into the batch.
</ParamField>

<ParamField query="eMode" type="string" default="auto">
  `auto` picks the e-mode category that admits both legs with the highest LTV; `none` keeps the
  reserve's default parameters; a number forces that category and is refused if it does not admit
  the pair. `close` ignores it and unwinds under the account's current category.
</ParamField>

<ParamField query="slippageBps" type="number" default="50">
  Taken off each swap's quote to size its exact output. What a swap does not spend stays in the
  wallet, listed in `leftovers`.
</ParamField>

<ParamField query="maxPriceImpactBps" type="number" default="100">
  Refuse a route whose quote, at the loop's cumulative notional, is further than this below the
  pool's oracle.
</ParamField>

### Request

```bash theme={null}
curl -H "x-api-key: your-api-key" \
  "https://api.1tx.fi/api/v1/loops/0x0000008feb2e.../calldata?action=open&account=0x...&leverage=4&amount=20000000"
```

### Response

A USDC/AUSD pair under e-mode, calls truncated:

```json theme={null}
{
  "loopId": "0x0000008feb2e...",
  "chainId": 143,
  "action": "open",
  "recipe": "aave-v3-loop",
  "amountIn": "20000000",
  "expectedOut": "78793057",
  "minOut": null,
  "expiresAt": 1789981250,
  "requires": [{ "token": "0x754704Bc...", "amount": "20000000" }],
  "leftovers": [{ "token": "0x00000000eFE3...", "maxAmount": "59095325" }],
  "calls": [{ "type": "set_account_config", "...": "..." }, { "type": "approve" }, { "type": "deposit" }],
  "simulation": { "ok": true, "scope": "protocol_bundle", "engine": "wallet_neutral_atomic", "...": "..." },
  "leverage": {
    "ltv": 0.93,
    "liquidationThreshold": 0.94,
    "maxLeverage": 14.2857,
    "paramsBasis": "emode:2",
    "oracleSource": { "oracle": "0x94bbA110...", "collateral": "0xf5F15f18...", "debt": "0xE20751C7..." },
    "requiresAccountConfig": true,
    "accountConfig": { "kind": "aave-emode", "current": 0, "target": 2 },
    "requestedLeverage": 4
  },
  "simulated": {
    "collateral": "78793057",
    "debt": "59095326",
    "leverage": 4,
    "healthFactor": 1.253333,
    "depegBufferBps": 2533,
    "ltv": 0.93,
    "liquidationThreshold": 0.94,
    "eModeCategory": 2
  },
  "turns": [
    { "borrow": "18553666", "supply": "18458767" },
    { "borrow": "17123890", "supply": "17036304" },
    { "borrow": "15804294", "supply": "15723457" },
    { "borrow": "7613475", "supply": "7574533" }
  ],
  "swap": { "venue": "uniswap-v4", "fee": 50 }
}
```

### Reading the bundle

<AccordionGroup>
  <Accordion title="leverage — effective parameters, never the screen's">
    `ltv` and `liquidationThreshold` are what the pool will apply to this pair after the bundle — the
    e-mode category's where the account will be in one — read at the quote block. `paramsBasis` says
    which (`emode:<id>` or `default`). A pair whose effective parameters cannot be read fails closed
    rather than falling back to the screen's pool default, and after simulating, the recipe reads the
    account's LTV and threshold back from the pool: if they disagree with what it planned under, the
    bundle is refused.
  </Accordion>

  <Accordion title="requiresAccountConfig — the bundle changes account-wide state">
    Aave's e-mode is set per account per pool, not per position. When this is `true` the bundle
    contains a `set_account_config` call, and **every other position the account holds in that pool**
    is re-priced by it — including an unlevered supply it has nothing to do with. Check the book before
    signing. A `close` that leaves no other debt in the pool clears the category back to `0`.
  </Accordion>

  <Accordion title="simulated — measured, not modelled">
    Read back from the simulated block after the batch ran. `collateral`/`debt` and `leverage` are
    this pair's; `healthFactor` and the `total*Base` figures are the pool's own account-level
    accounting, so they include anything else the account holds there. `depegBufferBps` is
    `(HF − 1)` — the adverse move in the debt/collateral price ratio the position survives — for a
    cross-asset pair only. The requested `leverage` is hit on the position itself: swap costs come out
    of equity, not out of the health factor.
  </Accordion>

  <Accordion title="turns — hard-coded, exact-output">
    Each turn is `borrow · swap · supply` (`borrow · supply` for a same-asset pair; `withdraw · swap ·
        repay` on the way down). Every amount is computed at the quote block and written into the
    calldata; every swap is exact-output, so the supply after it is known. Each borrow keeps 25 bps of
    headroom under the LTV and each intermediate withdrawal 50 bps above the liquidation threshold.
    A full unwind buys 10 bps more debt asset than the quote-block debt and repays with `max`, so
    interest accrued before it lands is cleared too.

    Swaps are quoted cumulatively through one pool fixed for the bundle: turn `k` is priced after the
    turns before it, and the route is chosen — Uniswap V3 or the pinned V4 pool, whichever fills the
    whole loop better — at the loop's full notional, not at one turn of it.
  </Accordion>

  <Accordion title="Errors">
    * `400` — `leverage` at or above the pair's effective ceiling (named, with its basis); no liquid
      route at the loop's size; an e-mode that does not admit the pair; `open` over existing debt on the
      pair, or `close`/`adjust` with none; a simulation that reverted or left debt after a full unwind.
    * `501` — no loop recipe for this venue. `aave-v3-loop` serves Aave V3 and its forks; Comet, Morpho
      Blue and Euler loops are separate recipes, not yet built.
    * `503` — chain state, the swap quote or the simulation was unavailable.
  </Accordion>
</AccordionGroup>
