# leveraged token

An LT represents fractional ownership of an aggregated Avantis perp. One LT exists per **`(pairIndex, leverage, direction)`** triplet, shared across every basefun token that uses the same configuration.

## Why shared

If every basefun token had its own LT, you'd have N nearly-identical Avantis positions for the same underlying. Each suffers from:

* Higher gas (N separate open/close per market move)
* Worse fills (small positions get worse Avantis spread/funding)
* Operational sprawl (the keeper has to babysit N positions instead of 1)

Sharing a single LT per `(pair, lev, dir)` means: when ten 5×-long-BTC coins exist, all ten curves' USDC funnels into the same aggregated BTC perp. Better fills, lower gas, cleaner accounting.

## Constructor

```solidity
constructor(
  string name_, string symbol_,
  address usdc_,
  address avantisTrading_, address avantisStorage_, address avantisPairInfos_,
  address keeper_,
  uint256 pairIndex_, uint256 leverage_, bool buyLong_,
  uint256 minMintUSDC_  // = 0 — no per-mint floor in current deploys
)
```

`keeper` is the off-chain bot's EOA. It's the only address allowed to call `notePositionOpened`, `noteMarginAdded`, `noteCollateralWithdrawn`.

## Net Asset Value (NAV)

```solidity
function nav() public view returns (uint256) {
  int256 totalPnL = 0;
  for (uint256 i = 0; i < nextTradeIndex; i++) {
    try avantisPairInfos.getTradePnL(keeper, pairIndex, i) returns (int256 p) {
      totalPnL += p;
    } catch {}
  }
  int256 v = int256(collateralDeposited) + totalPnL;
  return v > 0 ? uint256(v) : 0;
}
```

Sums:

* `collateralDeposited` — cumulative USDC paid into the keeper for this LT (minus what's been withdrawn).
* Live unrealized PnL from each open Avantis trade.

PnL queries that revert are treated as zero so NAV never reverts. This is the value the bonding curve reads via `getReserveUSDC()`:

```
curveReserveUSDC = LT.balanceOf(curve) × LT.nav() / LT.totalSupply()
```

## mint(usdcAmount, minLTOut)

Called by `BondingCurveV2._buyFor` after a buy:

{% stepper %}
{% step %}
`require usdcAmount > 0 && usdcAmount >= MIN_MINT_USDC`
{% endstep %}

{% step %}
`supply == 0 || nav == 0`:

```
ltOut = usdcAmount × 1e12         // first-mint: 1 USDC = 1e12 LT
```

Otherwise:

```
ltOut = usdcAmount × supply / nav
```

{% endstep %}

{% step %}
`require ltOut >= minLTOut`
{% endstep %}

{% step %}
`usdc.transferFrom(msg.sender, keeper, usdcAmount)` // ← USDC to keeper EOA
{% endstep %}

{% step %}
`collateralDeposited += usdcAmount`
{% endstep %}

{% step %}
`_mint(msg.sender, ltOut)`
{% endstep %}

{% step %}
`emit Minted`
{% endstep %}
{% endstepper %}

The USDC lands in the **keeper EOA**, not in the LT contract. That's because Avantis's `openTrade` pulls from `msg.sender` (which, via `delegatedAction`, is the keeper). Keeping USDC on the keeper short-cuts the chain: USDC arrives → keeper opens perp → USDC consumed by Avantis.

After this tx mines, the off-chain keeper:

* Calls `Avantis.delegatedAction(keeper, openTradeCalldata)` to actually open the perp.
* Calls `LT.notePositionOpened()` to bump `nextTradeIndex` so future NAV reads see the new trade.

## redeem(ltAmount, minUSDCOut)

Called by `BondingCurveV2.sell`:

{% stepper %}
{% step %}
`require ltAmount > 0`
{% endstep %}

{% step %}
`supply > 0`
{% endstep %}

{% step %}
`usdcOut = ltAmount × nav / supply`
{% endstep %}

{% step %}
`require usdcOut >= minUSDCOut`
{% endstep %}

{% step %}
`_burn(msg.sender, ltAmount)`
{% endstep %}

{% step %}
`usdc.transferFrom(keeper, address(this), usdcOut)` // pull USDC back from keeper
{% endstep %}

{% step %}
`collateralDeposited -= usdcOut (clamped to 0)`
{% endstep %}

{% step %}
`usdc.transfer(msg.sender, usdcOut)` // forward to curve
{% endstep %}
{% endstepper %}

For step 6 to succeed, **the keeper EOA must have given the LT an unlimited USDC `approve`**. This is a one-time setup the team performs whenever a new LT is deployed. If missing, every sell on every basefun token sharing that LT reverts with `ERC20: transfer amount exceeds allowance`.

## Keeper hooks

| Function                        | Caller | Purpose                                                                                            |
| ------------------------------- | ------ | -------------------------------------------------------------------------------------------------- |
| `notePositionOpened()`          | keeper | Bump `nextTradeIndex` after `delegatedAction(openTrade)` confirms                                  |
| `noteMarginAdded(usdc)`         | keeper | Bump `collateralDeposited` after the keeper tops up margin on a stressed perp                      |
| `noteCollateralWithdrawn(usdc)` | keeper | Decrement `collateralDeposited` + forward USDC back into the LT after a partial close              |
| `encodeOpenTrade(usdc)`         | view   | Returns the calldata for `Avantis.openTrade(...)` that the keeper should use via `delegatedAction` |

## LTFactory

Idempotent deployer. Anyone can call it; gas is on the caller.

```solidity
function getOrCreateLT(
  uint256 pairIndex, uint256 leverage, bool buyLong,
  string calldata name_, string calldata symbol_
) external returns (address)
```

Computes `keccak256(pairIndex, leverage, buyLong)` as the key. If an LT already exists for that key, returns it. Otherwise deploys a new `LeveragedToken` and stores it.

The basefun factory's `createToken` requires the LT to already exist — so the `/create` flow checks and, if needed, fires `getOrCreateLT` first.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://basefun.gitbook.io/basefun-docs/leveraged-token.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
