# Leverage

Open a long or short position from x1.1 to x10 on a graduated token, manage its take-profit, and close it. Every number that decides a position comes from the chain: `AtomicMarginLens` for the preview and the maximum leverage, `AtomicMarginTrading` for positions and payouts. Addresses: [deployments.json](https://atomic.green/agents/deployments.json).

## How a position works

| | Long | Short |
|---|---|---|
| Margin you post | quote (ETH or USDG) | quote |
| Loan from the token's lending pool | quote, `notional = margin × leverage` | tokens, sold for exactly `notional` quote |
| Entry | `notional` quote buys tokens in the pool | borrowed tokens sold for `notional` quote |
| Take-profit (TP) range | the tokens, placed in the pool above the price | the quote, placed in the pool below the price |
| Liquidated when | the token price falls to the liquidation level | the token price rises to the liquidation level |

- **Entry fee:** `notional × (hookFeeBps + creatorTaxBps) / 10000`, paid from the margin.
- **Funding:** interest at the pool's borrow rate plus a skew premium for the crowded side, for the whole term (7 days) at once, added to the debt. The debt is fixed, so the liquidation level does not move. Closing early refunds nothing.
- **Liquidation:** the level is set at open so that selling the position there covers the debt with a 30% buffer of the margin. When any swap reaches it, the hook sells the position first. **The margin is kept in full; the trader receives nothing.**
- **Take-profit:** when the price passes your whole TP range, the pool has sold it for you. Anyone may then call `closeTakeProfit`, and the rest after the debt goes straight to your wallet (if the transfer is refused it stays claimable). If the price comes back before that call, the range turns back into the asset and the profit is gone, so close it promptly.
- **Expiry:** after `expiry` without `extend`, anyone may close the position at market (`closeExpired`). Others can do so only one hour after `expiry`, and the call reverts with `PriceMovedFromReference` while the price sits more than 500 ticks (about 5%) on your losing side of the book's reference price, which is older than one transaction. You can close at any time with `closePosition` and a limit.
- **Payouts are pulled:** Trading credits `claimable(owner, asset)`; you call `claim(asset)`. The exception is the take-profit payout above.

## Preconditions

- A wallet that signs on Robinhood Chain (chain id 4663) with the margin plus gas in ETH (for a USDG market: USDG, 6 decimals, its address under `tokens` in deployments.json).
- The token is in `phase` 2 and has a market: `marketOf(token)` on Trading returns `ok = true`.
- `enabled()` on Trading is true, and the current time is past `market.enabledAt` (the first seconds after launch are closed to leverage).
- `notional ≥ minNotional(quote)` on Trading (read it), and at least 0.1% of the pool's locked quote depth: `previewOpen` accounts for it.
- A position is refused with reason 5 if its sale could set off a longer liquidation chain than one swap can carry.

## Functions

```solidity
// AtomicMarginLens (all view)
function maxLeverage(address token, bool isLong, uint256 margin) returns (uint32 leverageBps)   // 0 = not available now
function previewOpen(address token, bool isLong, uint256 margin, uint32 leverageBps) returns (Preview)
//   Preview = (uint256 notional, uint256 fee, uint256 funding, uint256 collateral, uint256 debt, uint256 assetAmount,
//              uint160 entrySqrtPriceX96, int24 liquidationTick, uint160 liquidationSqrtPriceX96,
//              uint256 liquidationPriceWad, uint8 reason, bool allowed)
function tpRange(address token, bool isLong, uint256 notional, int24 targetTick, bool wide) returns (int24 tickLower, int24 tickUpper)
function fundingQuote(address token, bool isLong, uint256 notional) returns (uint256 funding, uint256 borrowAprBps, uint256 skewPremiumPipsPerHour)
function previewClose(uint256 positionId) returns (bool ok, uint256 quoteAmount)
function positionValue(uint256 positionId) returns (int256 equityQuote, uint256 assetValueQuote, uint256 debtQuote)

// AtomicMarginTrading
struct OpenParams { address token; uint256 margin; uint32 leverageBps; int24 tpTickLower; int24 tpTickUpper;
                    uint256 minTokensOut; uint256 maxTokensIn; uint256 deadline; }
function openLong(OpenParams p) payable returns (uint256 positionId)    // ETH market: value = margin
function openShort(OpenParams p) payable returns (uint256 positionId)
function closePosition(uint256 positionId, uint256 limit, uint256 deadline)   // owner
function closeTakeProfit(uint256 positionId)                                  // anyone, once the TP range is filled
function closeExpired(uint256 positionId)                                     // after expiry; others one hour later
function settleLiquidation(uint256 positionId)                                // anyone, after a liquidation
function editTakeProfit(uint256 positionId, int24 tpTickLower, int24 tpTickUpper)   // owner, before the price reaches the range
function extend(uint256 positionId) payable                                   // owner: pays the next term's funding
function claim(address asset) returns (uint256 amount)                        // asset 0x0 = ETH
function claimable(address user, address asset) view returns (uint256)
function getPosition(uint256 positionId) view returns (Position)
//   Position = (address owner, address token, bool isLong, uint8 status, uint32 leverageBps, int24 liquidationTick,
//               int24 tpTickLower, int24 tpTickUpper, uint40 openedAt, uint40 expiry, uint128 margin, uint128 notional,
//               uint128 principal, uint128 debt, uint128 collateral, uint128 assetAmount)
//   status: 0 none, 1 open, 2 liquidated (waiting for settleLiquidation), 3 closed
function liquidationPrice(uint256 positionId) view returns (uint160 sqrtPriceX96, uint256 priceWad)
function marketOf(address token) view returns (bool ok, (bool tokenIsCurrency0, address quote, address lending, bytes32 poolId, uint24 fee, int24 tickSpacing, uint64 enabledAt))
function positionsOf(address owner) view returns (uint256[])
```

`liquidationPriceWad` and `priceWad` are the token price in quote × 10^18 in raw units: for an ETH market (both 18 decimals) divide by 10^18 for ETH per token; for USDG (6 decimals) the whole-unit price is `priceWad / 10^18 × 10^12`.

## Open a position

1. **Market.** `marketOf(token)`: keep `quote`, `tokenIsCurrency0`, `enabledAt`.
2. **Maximum leverage.** `maxLeverage(token, isLong, margin)`. 0 means this side is closed for this margin now (thin locked liquidity, no free lending liquidity, or the book is crowded): try the other side, a smaller margin, or wait. Choose `leverageBps` in steps of 1000 (0.1x), between 11 000 and that maximum.
3. **Preview.** `previewOpen(token, isLong, margin, leverageBps)`. Require `allowed = true`. Otherwise `reason` says why: 1 the locked liquidity is too thin, 2 the level would sit closer than 5% to the entry, 3 the position would be liquidated ahead of open positions and exceed their reserve, 4 the entry would cross existing liquidation levels. Smaller size or lower leverage helps.
4. **Check the risk.** Compare the liquidation price (`liquidationPriceWad`) with the entry price (from `entrySqrtPriceX96`). Show or check `fee`, `funding` and `debt`. Do not open a position whose liquidation distance or cost your policy does not accept.
5. **Take-profit range.** Pick a target price, convert it to a tick, and let the Lens place the range on the pool grid past the post-entry price: `tpRange(token, isLong, preview.notional, targetTick, wide)`. `wide = false` gives one narrow step at the target (the site's default); `wide = true` spans from the entry to the target and sells at the average price. Tick of a target: start from the entry tick `floor(ln((entrySqrtPriceX96 / 2^96)^2) / ln(1.0001))`; a target of `k` times the entry price is `entryTick + round(ln(k) / ln(1.0001))` when the token is `currency0`, and `entryTick − round(ln(k) / ln(1.0001))` when it is not (always the case for ETH markets). Long: `k > 1`; short: `k < 1`.
6. **Bounds.** Long: `minTokensOut = preview.assetAmount × 9900 / 10000` (1%), `maxTokensIn = 0`. Short: `minTokensOut = 0`, `maxTokensIn = preview.debt × 10100 / 10000`. `deadline` = now + at most 20 minutes.
7. **Pay.** ETH market: `value = margin`. USDG market: `approve(Trading, margin)` on USDG first (exactly `margin`), `value = 0`.
8. **Simulate** `openLong(params)` or `openShort(params)` from your address. The result is the future `positionId`. The book is not modelled by the preview, so this call is the real check.
9. **Send** the same call. Read the position with `getPosition(positionId)` and `liquidationPrice(positionId)`.

## Watch and manage

- Value now: `positionValue(id)` → `equityQuote` (what is left for you after the debt, in quote at the spot price; can be negative near liquidation).
- Distance to liquidation: compare the pool price (`getSlot0(poolId)` on the Uniswap StateView, `uniswapV4.stateView` in deployments.json) with `liquidationPrice(id)`.
- Move the TP: `editTakeProfit(id, lower, upper)` with a new range from `tpRange(token, isLong, notional, newTargetTick, false)`, before the price reaches the current range (`RangeReached` otherwise). The liquidation level does not change.
- Extend: before `expiry`, `extend(id)` pays funding for another term. Long ETH: `value` must equal exactly `fundingQuote(token, true, notional).funding` read in the same block range; if it reverts with `NativeValueMismatch`, the rate moved: read it again. Long USDG: approve that amount of USDG. Short: approve that amount of the token (`fundingQuote(token, false, notional)`). Simulate first.

## Close

Pick the call by the position's state, and simulate it first:

| State | Call | Who |
|---|---|---|
| Open, TP not reached | `closePosition(id, limit, deadline)` | owner |
| TP range fully passed | `closeTakeProfit(id)` | anyone |
| `expiry` passed without `extend` | `closeExpired(id)` | anyone |
| Liquidated (`status` 2) | `settleLiquidation(id)` | anyone; the trader receives nothing |

- `limit` for a manual close: `(ok, quoteAmount) = previewClose(id)`. Require `ok`. Long: `limit = quoteAmount × 9900 / 10000` (minimum quote from selling the tokens). Short: `limit = quoteAmount × 10100 / 10000` (maximum quote spent buying the debt back).
- `CloseWouldNotCoverDebt`: selling now would not repay the loan, so the owner cannot close by hand; the position stays protected by its liquidation level.
- Whether a TP is filled: simulate `closeTakeProfit(id)`; `TakeProfitNotFilled` means not yet.
- After a close, collect the payout (a filled take-profit has already paid your wallet, so check first): `claimable(you, asset)` for the quote asset (and for the token after a short), then `claim(asset)`.

## Example (TypeScript, viem)

Opens a 2x long with 0.01 ETH on a graduated token (ETH market) with a take-profit at +50%. Reads and the simulation need no key; the last line sends, and needs your wallet client.

```ts
import { createPublicClient, http, parseAbi, parseEther } from "viem";
import { robinhood as chain } from "viem/chains";

const client = createPublicClient({ chain, transport: http("https://rpc.mainnet.chain.robinhood.com") });
const net = (await (await fetch("https://atomic.green/agents/deployments.json")).json()).networks.find((n: any) => n.chainId === 4663);
const TRADING = net.contracts.AtomicMarginTrading;
const LENS = net.contracts.AtomicMarginLens;
const token = "0x…a graduated token from /launches, lowercase…";
const me = "0x…your address…";

const tradingAbi = parseAbi([
  "struct Market { bool tokenIsCurrency0; address quote; address lending; bytes32 poolId; uint24 fee; int24 tickSpacing; uint64 enabledAt; }",
  "struct OpenParams { address token; uint256 margin; uint32 leverageBps; int24 tpTickLower; int24 tpTickUpper; uint256 minTokensOut; uint256 maxTokensIn; uint256 deadline; }",
  "function marketOf(address token) view returns (bool ok, Market m)",
  "function openLong(OpenParams p) payable returns (uint256 positionId)",
]);
const lensAbi = parseAbi([
  "struct Preview { uint256 notional; uint256 fee; uint256 funding; uint256 collateral; uint256 debt; uint256 assetAmount; uint160 entrySqrtPriceX96; int24 liquidationTick; uint160 liquidationSqrtPriceX96; uint256 liquidationPriceWad; uint8 reason; bool allowed; }",
  "function maxLeverage(address token, bool isLong, uint256 margin) view returns (uint32)",
  "function previewOpen(address token, bool isLong, uint256 margin, uint32 leverageBps) view returns (Preview)",
  "function tpRange(address token, bool isLong, uint256 notional, int24 targetTick, bool wide) view returns (int24 tickLower, int24 tickUpper)",
]);

// 1–2. Market and maximum leverage.
const [ok, market] = await client.readContract({ address: TRADING, abi: tradingAbi, functionName: "marketOf", args: [token] });
if (!ok) throw new Error("no leverage market");
const margin = parseEther("0.01");
const leverageBps = 20_000; // x2
const max = await client.readContract({ address: LENS, abi: lensAbi, functionName: "maxLeverage", args: [token, true, margin] });
if (max < leverageBps) throw new Error(`max leverage now is ${max / 10_000}x`);

// 3–4. Preview and risk check.
const p = await client.readContract({ address: LENS, abi: lensAbi, functionName: "previewOpen", args: [token, true, margin, leverageBps] });
if (!p.allowed) throw new Error(`not allowed, reason ${p.reason}`);
const entryTick = Math.floor(Math.log((Number(p.entrySqrtPriceX96) / 2 ** 96) ** 2) / Math.log(1.0001));
const entryPrice = market.tokenIsCurrency0 ? 1.0001 ** entryTick : 1.0001 ** -entryTick; // ETH per token
const liqPrice = Number(p.liquidationPriceWad) / 1e18;
console.log({ entryPrice, liqPrice, dropToLiquidation: 1 - liqPrice / entryPrice, fee: p.fee, funding: p.funding, debt: p.debt });

// 5. Take-profit at +50%: in an ETH market the token is currency1, so a higher price is a lower tick.
const step = Math.round(Math.log(1.5) / Math.log(1.0001));
const targetTick = market.tokenIsCurrency0 ? entryTick + step : entryTick - step;
const [tpTickLower, tpTickUpper] = await client.readContract({ address: LENS, abi: lensAbi, functionName: "tpRange", args: [token, true, p.notional, targetTick, false] });

// 6–8. Bounds, then simulate the exact transaction from your address.
const params = { token, margin, leverageBps, tpTickLower, tpTickUpper,
  minTokensOut: (p.assetAmount * 9_900n) / 10_000n, maxTokensIn: 0n,
  deadline: BigInt(Math.floor(Date.now() / 1000) + 20 * 60) };
const { request, result: positionId } = await client.simulateContract({
  account: me, address: TRADING, abi: tradingAbi, functionName: "openLong", args: [params], value: margin });
console.log("position id if sent now:", positionId);

// 9. Send with your wallet client: await walletClient.writeContract(request);
```

A side can be closed on a given token (`maxLeverage` 0); the code above reports that instead of sending.
