<!-- Generated by `npm run skills` in apps/web from apps/web/lib/modules.ts (what list_modules and get_module return). Edit that, not this file. -->

# Modules

Everything a Flowscript strategy on ftw.trade can import: the `uses` line declares market data, order books and protocols by name, and `import "…";` after it brings in a std contract module or one of your own libraries. Over MCP, `list_modules { kind?, chain?, search? }` lists them and `get_module { id }` gives one whole, with how far back each market's history goes; in the app, Developers → Modules. Every example here compiles.

## Market data

### Bars (`bars`)

The open, high, low, close and volume of the series a strategy runs on (a pool, a perp or a feed), at the run's interval or a longer one. `bars.close(market, "4h")` is the last closed four-hour bar.

- **Import**: `uses { bars: "^1" }`
- **Reads**: `bars.open(market, interval)`, `bars.high(market, interval)`, `bars.low(market, interval)`, `bars.close(market, interval)`, `bars.volume(market, interval)`, `bars.time(market, interval)`, `bars.usd(market, interval)`
- **Real funds**: n/a
- **More**: `get_module { id: "bars" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", paper: "^1" }
const close = bars.close(market, interval);
const fast = ema(close, 12);
const slow = ema(close, 26);
when (crossover(fast, slow) && !inPosition()) { paper.buy({ quote: 100 }); }
when (crossunder(fast, slow) && inPosition()) { paper.sell({}); }
metric("fast", fast);
metric("slow", slow);
```

### Pool (`pool`)

A DEX pool's bars, and every swap in it as a tape of buys and sells (`pool.swaps()`; the Flowscript skill's Tapes section shows one). In a strategy over more than one market, the pool its range sits on.

- **Import**: `uses { pool: "^1" }`
- **Reads**: `pool.open(market, interval)`, `pool.high(market, interval)`, `pool.low(market, interval)`, `pool.close(market, interval)`, `pool.volume(market, interval)`, `pool.swaps()`
- **Real funds**: n/a
- **More**: `get_module { id: "pool" }`, ftw-flowscript/SKILL.md

```flowscript
uses { pool: "^1", paper: "^1" }
const close = pool.close(market, interval);
const vol = pool.volume(market, interval);
when (vol > 3 * sma(vol, 24) && close > close[1] && !inPosition()) { paper.buy({ quote: 100 }); }
when (inPosition() && close < sma(close, 24)) { paper.sell({}); }
metric("volume", vol);
```

### Feeds (`feed`)

The shared market data every account reads for free: crypto, stocks and commodities prices and lending rates (search_market_data lists them). A strategy runs on a feed as `feed:<name>`.

- **Import**: `uses { feed: "^1" }`
- **Reads**: `feed.open(market, interval)`, `feed.high(market, interval)`, `feed.low(market, interval)`, `feed.close(market, interval)`, `feed.volume(market, interval)`
- **Real funds**: n/a
- **Backtest on**: `feed:coinbase-eth-usd`
- **More**: `get_module { id: "feed" }`, ftw-flowscript/SKILL.md

```flowscript
uses { feed: "^1", paper: "^1" }
const eth = feed.close(market, interval);
const dd = (eth - highest(eth, 24)) / highest(eth, 24) * 100;
metric("eth", eth);
metric("drawdown_24h", dd);
```

### Funding (`funding`)

A perps market's hourly funding rate, `funding.rate(market, interval)`, on Hyperliquid or Lighter.

- **Import**: `uses { funding: "^1" }`
- **Reads**: `funding.rate(market, interval)`
- **Real funds**: n/a
- **Backtest on**: `perp:hyperliquid:ETH`
- **More**: `get_module { id: "funding" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", funding: "^1", perp: "^1" }
const f = funding.rate(market, interval);
when (f > 0.01 && perp.sizeUsd() == 0) { perp.short({ usd: 500 }); }
when (f < 0 && perp.sizeUsd() != 0) { perp.close({}); }
metric("funding", f);
```

### Order book (`book`)

A perps market's or a Polymarket outcome's order book at each bar's close, recorded every 15 s: best bid and ask and their sizes, the microprice, levels 2 to 5, the spread, USD on the top 20 levels and within 10, 25, 50 and 100 basis points, the imbalance, and the snapshot's age.

- **Import**: `uses { book: "^1" }`
- **Reads**: `book.bid(market, interval)`, `book.ask(market, interval)`, `book.mid(market, interval)`, `book.micro(market, interval)`, `book.spread(market, interval)`, `book.bidSize(market, interval)`, `book.askSize(market, interval)`, `book.bidDepth(market, interval)`, `book.askDepth(market, interval)`, `book.imbalance(market, interval)`, `book.age(market, interval)`, `book.bids10(market, interval)`, `book.asks10(market, interval)`, `book.imbalance10(market, interval)`, `book.bids25(market, interval)`, `book.asks25(market, interval)`, `book.imbalance25(market, interval)`, `book.bids50(market, interval)`, `book.asks50(market, interval)`, `book.imbalance50(market, interval)`, `book.bids100(market, interval)`, `book.asks100(market, interval)`, `book.imbalance100(market, interval)`, `book.bid2(market, interval)`, `book.ask2(market, interval)`, `book.bidSize2(market, interval)`, `book.askSize2(market, interval)`, `book.bid3(market, interval)`, `book.ask3(market, interval)`, `book.bidSize3(market, interval)`, `book.askSize3(market, interval)`, `book.bid4(market, interval)`, `book.ask4(market, interval)`, `book.bidSize4(market, interval)`, `book.askSize4(market, interval)`, `book.bid5(market, interval)`, `book.ask5(market, interval)`, `book.bidSize5(market, interval)`, `book.askSize5(market, interval)`
- **Real funds**: n/a
- **Backtest on**: `perp:hyperliquid:ETH`
- **More**: `get_module { id: "book" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", book: "^1", perp: "^1" }
const imb = book.imbalance10(market, interval);
when (imb > 0.3 && perp.sizeUsd() == 0) { perp.long({ usd: 200 }); }
when (imb < -0.3 && perp.sizeUsd() > 0) { perp.close({}); }
metric("imbalance", imb);
```

### Liquidations (`liq`)

A Hyperliquid perp's liquidation map from real positions (BTC, ETH, ZEC, SOL, HYPE), recorded every minute: USD to be liquidated within 1, 2, 5 and 10% below (longs) and above (shorts), the densest cluster each side within 3% and 10% with its price and USD, the imbalance, the USD covered, and the row's age.

- **Import**: `uses { liq: "^1" }`
- **Reads**: `liq.longs1(market, interval)`, `liq.longs2(market, interval)`, `liq.longs5(market, interval)`, `liq.longs10(market, interval)`, `liq.shorts1(market, interval)`, `liq.shorts2(market, interval)`, `liq.shorts5(market, interval)`, `liq.shorts10(market, interval)`, `liq.imbalance(market, interval)`, `liq.longPx(market, interval)`, `liq.longUsd(market, interval)`, `liq.shortPx(market, interval)`, `liq.shortUsd(market, interval)`, `liq.nearLongPx(market, interval)`, `liq.nearLongUsd(market, interval)`, `liq.nearShortPx(market, interval)`, `liq.nearShortUsd(market, interval)`, `liq.covered(market, interval)`, `liq.age(market, interval)`
- **Real funds**: n/a
- **Backtest on**: `perp:hyperliquid:BTC`
- **More**: `get_module { id: "liq" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", liq: "^1", perp: "^1" }
const close = bars.close(market, interval);
const above = liq.nearShortPx(market, interval);
const near = (above - close) / close * 100 < 0.5 && liq.nearShortUsd(market, interval) > 5000000;
metric("shorts_2pct", liq.shorts2(market, interval));
metric("near_short_cluster", near ? 1 : 0);
```

### Sources (`sources`)

Your own typed sources (pushed, polled, TypeScript or contract events), each a tape of events read as `sources.<name>()` with `-` written `_`. list_sources lists yours; create_source makes one.

- **Import**: `uses { sources: "^1" }`
- **Reads**: `sources.<name>()`
- **Real funds**: n/a
- **More**: `get_module { id: "sources" }`, ftw-sources/SKILL.md

```flowscript
uses { bars: "^1", paper: "^1" }
const close = bars.close(market, interval);
// With a source named kol-calls: uses { bars: "^1", sources: "^1", paper: "^1" }
// const calls = sources.kol_calls();
// when (count(window(calls, "15m")) >= 3 && !inPosition()) { paper.buy({ quote: 100 }); }
metric("close", close);
```

## Trading and books

### Spot orders (`paper`)

Buy and sell the series a strategy runs on: market orders sized in quote or a percent of equity, and resting limits and stops. On a project's wallets they are its swaps. The book reads `inPosition()`, `avgEntry()`, `realizedPnl()`, `unrealizedPnl()`.

- **Import**: `uses { paper: "^1" }`
- **Actions**: `paper.buy({ quote })`, `paper.buyPct({ pct })`, `paper.sell({})`, `paper.sellPct({ pct })`, `paper.buyLimit({ quote, at })`, `paper.buyStop({ quote, at })`, `paper.buyPctLimit({ pct, at })`, `paper.buyPctStop({ pct, at })`, `paper.sellLimit({ at })`, `paper.sellStop({ at })`
- **Real funds**: yes
- **More**: `get_module { id: "paper" }`, ftw-strategies/SKILL.md

```flowscript
uses { bars: "^1", paper: "^1" }
const close = bars.close(market, interval);
const r = rsi(close, 14);
when (r < 30 && !inPosition()) { paper.buyPct({ pct: 50 }); }
when (r > 70 && inPosition()) { paper.sell({}); }
metric("rsi", r);
```

### Perps (`perp`)

Trade a perps market on a margin account (Hyperliquid or Lighter): long, short, close, hold a target size or hedge a range. Its bars and funding are `perp.close(…)` and `perp.funding(…)`; its book `perp.sizeUsd()`, `perp.equity()`, `perp.entry()`.

- **Import**: `uses { perp: "^1" }`
- **Reads**: `perp.open(market, interval)`, `perp.high(market, interval)`, `perp.low(market, interval)`, `perp.close(market, interval)`, `perp.volume(market, interval)`, `perp.funding(market, interval)`
- **Its book**: `perp.sizeUsd()`, `perp.equity()`, `perp.entry()`
- **Actions**: `perp.long({ usd, maxImpactBps? })`, `perp.short({ usd, maxImpactBps? })`, `perp.close({})`, `perp.hedge({ ratio?, minUsd?, maxImpactBps? })`, `perp.target({ usd?, minUsd?, maxImpactBps? })`, `perp.bid({ usd, at })`, `perp.ask({ usd, at })`, `perp.cancel({})`
- **Real funds**: yes
- **Backtest on**: `perp:hyperliquid:ETH`
- **More**: `get_module { id: "perp" }`, ftw-strategies/SKILL.md

```flowscript
uses { bars: "^1", perp: "^1" }
const close = bars.close(market, interval);
const trend = close > sma(close, 50);
when (trend && perp.sizeUsd() <= 0) { perp.long({ usd: 500 }); }
when (!trend && perp.sizeUsd() > 0) { perp.close({}); }
metric("size", perp.sizeUsd());
```

### Liquidity range (`lp`)

Hold a liquidity range on a Uniswap-style pool, as wide as you say, and close it. Its book: `lp.value()`, `lp.baseUsd()`, `lp.inRange()`, `lp.widthPct()`, `lp.fees()`. Pair it with a perp hedge for a hedged range.

- **Import**: `uses { lp: "^1" }`
- **Its book**: `lp.value()`, `lp.baseUsd()`, `lp.inRange()`, `lp.widthPct()`, `lp.fees()`
- **Actions**: `lp.range({ widthPct?, usd? })`, `lp.close({})`
- **Real funds**: yes
- **More**: `get_module { id: "lp" }`, ftw-strategies/SKILL.md

```flowscript
uses { pool: "^1", lp: "^1" }
const close = pool.close(market, interval);
when (!lp.inRange()) { lp.range({ widthPct: 10, usd: 1000 }); }
metric("value", lp.value());
metric("close", close);
```

### Wallet (`wallet`)

The cash a strategy's wallet holds, `wallet.cash()`, after the bar's orders.

- **Import**: `uses { wallet: "^1" }`
- **Its book**: `wallet.cash()`
- **Real funds**: yes
- **More**: `get_module { id: "wallet" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", wallet: "^1", paper: "^1" }
const close = bars.close(market, interval);
when (wallet.cash() > 100 && close < sma(close, 20)) { paper.buy({ quote: 100 }); }
metric("cash", wallet.cash());
```

### Funds (`funds`)

Move dollars between a strategy's wallet and its perp account, at once: `funds.toPerp({ usd })`, `funds.toWallet({ usd })`.

- **Import**: `uses { funds: "^1" }`
- **Actions**: `funds.toPerp({ usd? })`, `funds.toWallet({ usd? })`
- **Real funds**: yes
- **Backtest on**: `perp:hyperliquid:ETH`
- **More**: `get_module { id: "funds" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", perp: "^1", funds: "^1" }
const close = bars.close(market, interval);
when (perp.equity() < 100) { funds.toPerp({ usd: 200 }); }
metric("equity", perp.equity());
metric("close", close);
```

### Husher (`husher`)

Move funds between the wallet and the perp account privately, landing `delayMinutes` later; `husher.inFlight()` until then. A move that cannot be done reverts with why. Paper only in a strategy; real private sends, fan-outs to many wallets and bought wallets are workflow steps (`husher`, `buyWallets`) whose status a run branches on.

- **Import**: `uses { husher: "^1" }`
- **Its book**: `husher.inFlight()`
- **Actions**: `husher.toPerp({ usd, delayMinutes? })`, `husher.toWallet({ usd, delayMinutes? })`
- **Real funds**: paper only
- **Backtest on**: `perp:hyperliquid:ETH`
- **More**: `get_module { id: "husher" }`, ftw-workflows/SKILL.md

```flowscript
uses { bars: "^1", perp: "^1", husher: "^1" }
const close = perp.close(market, interval);
when (perp.equity() < 100 && husher.inFlight() == 0) { husher.toPerp({ usd: 200, delayMinutes: 15 }); }
metric("in_flight", husher.inFlight());
metric("close", close);
```

### Poll a source (`poll`)

Ask one of your polled sources to run now, `poll.source({ name })`, so what it publishes reaches the next bar without waiting for its schedule. A backtest polls nothing.

- **Import**: `uses { poll: "^1" }`
- **Actions**: `poll.source({ name })`
- **Real funds**: yes
- **More**: `get_module { id: "poll" }`, ftw-sources/SKILL.md

```flowscript
uses { bars: "^1", poll: "^1" }
const close = bars.close(market, interval);
when (abs(change(close)) / close > 0.02) { poll.source({ name: "my-source" }); }
metric("close", close);
```

### Uniswap (`uniswap`)

Swaps through Uniswap's v2 pairs, v3 pools and v4 pools on Ethereum, Base, Arbitrum, Optimism, Unichain and Robinhood Chain, computed with Uniswap's own math to the wei and sent through its Universal Router, one pool or the best route across up to three, and liquidity in v2 pairs, v3 pools and hookless v4 pools put in, taken out and its fees collected, with what it holds, earns and has made.

- **Import**: `uses { uniswap: "^1" }`
- **Reads**: `uniswap.price("unichain:token", interval)`, `uniswap.ratio("unichain:token", interval)`, `uniswap.fee("unichain:token", interval)`, `uniswap.depthDown("unichain:token", interval)`, `uniswap.depthUp("unichain:token", interval)`, `uniswap.liquidityUsd("unichain:token", interval)`
- **Its book**: `uniswap.held("unichain:token")`, `uniswap.heldUsd("unichain:token")`, `uniswap.lpCount("deployment")`, `uniswap.lpUsd("deployment")`, `uniswap.lpFeesUsd("deployment")`, `uniswap.lpInRange("deployment")`, `uniswap.lpDrift("deployment")`, `uniswap.lpPnlUsd("deployment")`, `uniswap.lpIlUsd("deployment")`, `uniswap.stateAge("deployment")`
- **Actions**: `uniswap.swap({ market, sell, amountIn | amountOut | all, slippageBps? })`, `uniswap.route({ market, sell, amountIn | amountOut | usd | all, buy?, via?, hops?, slippageBps? })`, `uniswap.addLiquidity({ market | nft, amount0 | amount1 | usd | all, widthPct?, tickLower?, tickUpper?, eth?, slippageBps? })`, `uniswap.removeLiquidity({ market | nft, pct?, eth?, slippageBps? })`, `uniswap.collectFees({ market | nft, eth? })`
- **Package**: `@ftw-trade/protocol-uniswap@1.0.0` (packages/protocols/uniswap), with its library `import "protocols/uniswap";`
- **Functions**: `fn uniswapDip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn uniswapRip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn uniswapSize(liquidityUsd: Series<number>, share: number, most: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:uniswap-base-weth-usdc-v3-500-price`
- **More**: `get_protocol_guide { protocol: "uniswap" }`, ftw-protocols/references/uniswap.md

```flowscript
uses { uniswap: "^1" }
const p = uniswap.price("base:weth-usdc-v3-500", interval);
when (p < 2500) { uniswap.swap({ market: "base:weth-usdc-v3-500", sell: "USDC", amountIn: 250 }); }
when (p > 4000) { uniswap.swap({ market: "base:weth-usdc-v3-500", sell: "WETH", all: true }); }
metric("price", p);
```

### KyberSwap (`kyberswap`)

Swaps routed by KyberSwap's aggregator across every DEX it indexes on Ethereum, Base, Arbitrum and Robinhood Chain: quoted by its API at the head for exactly what is sold, and sent through its router only after the call its API builds is checked to sell exactly that, pay only the wallet, take no fee and return at least what the slippage allows.

- **Import**: `uses { kyberswap: "^1" }`
- **Reads**: `kyberswap.price("robinhood:token", interval)`, `kyberswap.spread("robinhood:token", interval)`
- **Its book**: `kyberswap.held("robinhood:token")`, `kyberswap.heldUsd("robinhood:token")`, `kyberswap.stateAge("deployment")`
- **Actions**: `kyberswap.swap({ market, sell, amountIn | usd | all, buy?, slippageBps? })`
- **Package**: `@ftw-trade/protocol-kyberswap@1.0.0` (packages/protocols/kyberswap), with its library `import "protocols/kyberswap";`
- **Functions**: `fn kyberswapTight(spread: Series<number>, pct: number): Series<bool>`; `fn kyberswapDip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn kyberswapRip(price: Series<number>, pct: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:kyberswap-base-weth-price`
- **More**: `get_protocol_guide { protocol: "kyberswap" }`, ftw-protocols/references/kyberswap.md

```flowscript
uses { kyberswap: "^1" }
const p = kyberswap.price("base:weth", interval);
when (p < 2500 && kyberswap.spread("base:weth", interval) < 0.2) {
  kyberswap.swap({ market: "base:weth", sell: "USDC", usd: 250 });
}
when (p > 4000) { kyberswap.swap({ market: "base:weth", sell: "WETH", all: true }); }
metric("price", p);
```

### CoW Swap (`cow`)

Swaps settled by CoW Protocol's solvers in batch auctions on Ethereum, Base and Arbitrum: a sell order quoted by CoW's API at the head, built and hashed by the crate as CoW's settlement contract hashes it (selling exactly what was asked, paying only the wallet, no fee in the order, at least the quote less the slippage), signed by the wallet, and settled by a solver or expired with nothing spent. The wallet sends only an approval of what it sells to CoW's vault relayer.

- **Import**: `uses { cow: "^1" }`
- **Reads**: `cow.price("arbitrum:token", interval)`, `cow.spread("arbitrum:token", interval)`
- **Its book**: `cow.held("arbitrum:token")`, `cow.heldUsd("arbitrum:token")`, `cow.open("arbitrum:token")`, `cow.stateAge("deployment")`
- **Actions**: `cow.swap({ market, sell, amountIn | usd | all, buy?, slippageBps? })`
- **Package**: `@ftw-trade/protocol-cow@1.0.0` (packages/protocols/cow), with its library `import "protocols/cow";`
- **Functions**: `fn cowTight(spread: Series<number>, pct: number): Series<bool>`; `fn cowIdle(open: number): bool`; `fn cowDip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn cowRip(price: Series<number>, pct: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:cow-base-weth-price`
- **More**: `get_protocol_guide { protocol: "cow" }`, ftw-protocols/references/cow.md

```flowscript
uses { cow: "^1" }
const p = cow.price("base:weth", interval);
when (p < 2500 && cow.spread("base:weth", interval) < 0.2 && cow.open("base:weth") == 0) {
  cow.swap({ market: "base:weth", sell: "USDC", usd: 250 });
}
when (p > 4000) { cow.swap({ market: "base:weth", sell: "WETH", all: true }); }
metric("price", p);
```

### Aerodrome (`aerodrome`)

Swaps through Aerodrome's and Aero's pools on Base, computed with each pool's own math to the wei: its classic pools, volatile (x·y) and stable (x³y + xy³), through Aerodrome's Router, and its concentrated (Slipstream) pools, Uniswap v3's math, through the Slipstream router of the pool's factory, an exact amount in or out. Liquidity put in and taken out (a classic pool's LP token, or a concentrated pool's range as an NFT), and the trading fees it earns claimed, with what it holds, earns and has made. An Aero pool's range staked in its gauge for AERO, taken out and its rewards claimed, with each gauge's reward rate and what a range would earn staked.

- **Import**: `uses { aerodrome: "^1" }`
- **Reads**: `aerodrome.price("ethereum:token", interval)`, `aerodrome.ratio("ethereum:token", interval)`, `aerodrome.fee("ethereum:token", interval)`, `aerodrome.depthDown("ethereum:token", interval)`, `aerodrome.depthUp("ethereum:token", interval)`, `aerodrome.liquidityUsd("ethereum:token", interval)`, `aerodrome.feeGrowth("ethereum:token", interval)`, `aerodrome.rewardRate("ethereum:token", interval)`, `aerodrome.rewardRateUsd("ethereum:token", interval)`, `aerodrome.rewardApr("ethereum:token", interval)`, `aerodrome.stakedShare("ethereum:token", interval)`
- **Its book**: `aerodrome.held("ethereum:token")`, `aerodrome.heldUsd("deployment")`, `aerodrome.lpShare("ethereum:token")`, `aerodrome.lpCount("deployment")`, `aerodrome.lpInRange("deployment")`, `aerodrome.lpDrift("deployment")`, `aerodrome.lpUsd("deployment")`, `aerodrome.lpFeesUsd("deployment")`, `aerodrome.lpPnlUsd("deployment")`, `aerodrome.lpIlUsd("deployment")`, `aerodrome.stakedCount("deployment")`, `aerodrome.stakedApr("deployment")`, `aerodrome.rewardsEarned("deployment")`, `aerodrome.rewardsEarnedUsd("deployment")`, `aerodrome.claimableUsd("deployment")`, `aerodrome.stateAge("deployment")`
- **Actions**: `aerodrome.swap({ market, sell, amountIn | amountOut | all, slippageBps? })`, `aerodrome.addLiquidity({ market, amount0 | amount1 | usd | all, tickLower?, tickUpper?, widthPct?, slippageBps? })`, `aerodrome.removeLiquidity({ market | nft, pct?, slippageBps? })`, `aerodrome.collectFees({ market | nft })`, `aerodrome.stake({ market | nft })`, `aerodrome.unstake({ market | nft })`, `aerodrome.claimRewards({ market | nft })`
- **Package**: `@ftw-trade/protocol-aerodrome@1.0.0` (packages/protocols/aerodrome), with its library `import "protocols/aerodrome";`
- **Functions**: `fn aerodromeDip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn aerodromeRip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn aerodromeSize(liquidityUsd: Series<number>, share: number, most: number): Series<number>`; `fn aerodromeClaimable(feesUsd: number, usd: number): bool`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "aerodrome" }`, ftw-protocols/references/aerodrome.md

```flowscript
uses { aerodrome: "^1" }
const pool = "base:vamm-weth-usdc";
when (aerodrome.lpCount("base") == 0) {
  aerodrome.swap({ market: pool, sell: "USDC", amountIn: 500 });
  aerodrome.addLiquidity({ market: pool, all: true });
}
when (every("1d") && aerodrome.lpFeesUsd("base") > 2) { aerodrome.collectFees({ market: pool }); }
metric("price", aerodrome.price(pool, interval));
metric("pnl", aerodrome.lpPnlUsd("base"));
```

### Pons (`pons`)

Tokens launched on Pons (v2, Robinhood Chain), bought and sold on each one's own bonding curve until it graduates to a Uniswap v4 pool: a constant-product curve on a phantom quote reserve, trading against native ETH (or the ERC-20 the launch named). Every trade pays the launch's base fee and its creator's tax off the quote; a buy in the launch's opening seconds pays a snipe tax as well, halving fourteen times across its window. The buy that reaches graduation is filled only up to the tokens left and refunded the rest. Computed with the curve's own math, ported from Pons's verified source, to the wei.

- **Import**: `uses { pons: "^1" }`
- **Reads**: `pons.price("robinhood:token", interval)`, `pons.priceQuote("robinhood:token", interval)`, `pons.marketCap("robinhood:token", interval)`, `pons.raised("robinhood:token", interval)`, `pons.raisedUsd("robinhood:token", interval)`, `pons.progress("robinhood:token", interval)`, `pons.graduated("robinhood:token", interval)`, `pons.fee("robinhood:token", interval)`, `pons.launchTax("robinhood:token", interval)`, `pons.age("robinhood:token", interval)`
- **Its book**: `pons.held("robinhood:token")`, `pons.heldUsd("robinhood:token")`, `pons.worthQuote("robinhood:token")`, `pons.stateAge("deployment")`
- **Actions**: `pons.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `pons.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `pons.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-pons@1.0.0` (packages/protocols/pons), with its library `import "protocols/pons";`
- **Functions**: `fn ponsPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn ponsDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn ponsRipe(progress: Series<number>, graduated: Series<number>, launchTax: Series<number>, pct: number): Series<bool>`; `fn ponsRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "pons" }`, ftw-protocols/references/pons.md

```flowscript
uses { pons: "^1" }
import "protocols/pons";
const t = "robinhood:0xd24a5ca6e8da9c1ac327b5d12879710623a3bb81";
const ripe = ponsRipe(pons.progress(t, interval), pons.graduated(t, interval), pons.launchTax(t, interval), 50);
when (ripe && pons.held(t) == 0) { pons.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (ponsDumped(pons.price(t, interval), 30, 24) && pons.held(t) > 0) { pons.sell({ market: t, all: true }); }
```

### pump.fun (`pumpfun`)

Coins launched on pump.fun (Solana), bought and sold on each one's own bonding curve until it completes and the coin moves to its PumpSwap pool: a constant-product curve on virtual SOL and token reserves. Every trade pays the protocol's and the creator's fees off the SOL, at the fee program's market-cap tier; the last buy is filled only up to the tokens the curve has left. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the curve's own math, held to the program's answers on mainnet.

- **Import**: `uses { pumpfun: "^1" }`
- **Reads**: `pumpfun.price("solana:token", interval)`, `pumpfun.priceQuote("solana:token", interval)`, `pumpfun.marketCap("solana:token", interval)`, `pumpfun.raised("solana:token", interval)`, `pumpfun.raisedUsd("solana:token", interval)`, `pumpfun.progress("solana:token", interval)`, `pumpfun.graduated("solana:token", interval)`, `pumpfun.fee("solana:token", interval)`, `pumpfun.launchTax("solana:token", interval)`, `pumpfun.solUsd("solana:token", interval)`
- **Its book**: `pumpfun.held("solana:token")`, `pumpfun.heldUsd("solana:token")`, `pumpfun.worthQuote("solana:token")`, `pumpfun.stateAge("deployment")`
- **Actions**: `pumpfun.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `pumpfun.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `pumpfun.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-pumpfun@1.0.0` (packages/protocols/pumpfun), with its library `import "protocols/pumpfun";`
- **Functions**: `fn pumpfunPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn pumpfunDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn pumpfunRipe(progress: Series<number>, graduated: Series<number>, from: number, to: number): Series<bool>`; `fn pumpfunRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "pumpfun" }`, ftw-protocols/references/pumpfun.md

```flowscript
uses { pumpfun: "^1" }
import "protocols/pumpfun";
const t = "solana:5v2Te9gzA1YxQ6VmfX7pMHgySRcy8zjskCZxQdPzpump";
const ripe = pumpfunRipe(pumpfun.progress(t, interval), pumpfun.graduated(t, interval), 40, 90);
when (ripe && pumpfun.held(t) == 0) { pumpfun.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (pumpfunDumped(pumpfun.price(t, interval), 30, 24) && pumpfun.held(t) > 0) { pumpfun.sell({ market: t, all: true }); }
```

### PumpSwap (`pumpswap`)

PumpSwap (Solana): the constant-product pools pump.fun coins trade on once their bonding curve completes, quoted in SOL. Every trade pays the pool, the protocol and the coin's creator their fees off the SOL, at the fee program's market-cap tier. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the pool's own math, held to the program's answers on mainnet.

- **Import**: `uses { pumpswap: "^1" }`
- **Reads**: `pumpswap.price("solana:token", interval)`, `pumpswap.priceQuote("solana:token", interval)`, `pumpswap.marketCap("solana:token", interval)`, `pumpswap.fee("solana:token", interval)`, `pumpswap.solUsd("solana:token", interval)`, `pumpswap.liquidity("solana:token", interval)`
- **Its book**: `pumpswap.held("solana:token")`, `pumpswap.heldUsd("solana:token")`, `pumpswap.worthQuote("solana:token")`, `pumpswap.stateAge("deployment")`
- **Actions**: `pumpswap.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `pumpswap.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `pumpswap.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-pumpswap@1.0.0` (packages/protocols/pumpswap), with its library `import "protocols/pumpswap";`
- **Functions**: `fn pumpswapTrend(price: Series<number>, bars: number, pct: number): Series<bool>`; `fn pumpswapDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn pumpswapDeepEnough(liquidity: Series<number>, usd: number, impactPct: number): Series<bool>`; `fn pumpswapRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "pumpswap" }`, ftw-protocols/references/pumpswap.md

```flowscript
uses { pumpswap: "^1" }
import "protocols/pumpswap";
const t = "solana:8VdrHbx444YZJZp1vNb9pXAv5aW4Kv7E7bk7Zwaapump";
const price = pumpswap.price(t, interval);
when (pumpswapTrend(price, 24, 10) && pumpswapDeepEnough(pumpswap.liquidity(t, interval), 25, 1) && pumpswap.held(t) == 0) {
  pumpswap.buy({ market: t, usd: 25, slippageBps: 200 });
}
when (pumpswapDumped(price, 25, 24) && pumpswap.held(t) > 0) { pumpswap.sell({ market: t, all: true }); }
```

### Raydium LaunchLab (`launchlab`)

Coins launched on letsbonk and the other launch sites built on Raydium's LaunchLab (Solana), bought and sold on each one's own pool until it sells out and the coin migrates to its Raydium AMM pool: a constant-product curve on the pool's virtual reserves plus what it holds. Every trade pays the protocol's, the launch site's and the creator's fees off the SOL; the last buy is filled only up to the tokens the pool has left to sell. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the pool's own math, held to the program's answers on mainnet.

- **Import**: `uses { launchlab: "^1" }`
- **Reads**: `launchlab.price("solana:token", interval)`, `launchlab.priceQuote("solana:token", interval)`, `launchlab.marketCap("solana:token", interval)`, `launchlab.raised("solana:token", interval)`, `launchlab.raisedUsd("solana:token", interval)`, `launchlab.progress("solana:token", interval)`, `launchlab.graduated("solana:token", interval)`, `launchlab.fee("solana:token", interval)`, `launchlab.launchTax("solana:token", interval)`, `launchlab.solUsd("solana:token", interval)`
- **Its book**: `launchlab.held("solana:token")`, `launchlab.heldUsd("solana:token")`, `launchlab.worthQuote("solana:token")`, `launchlab.stateAge("deployment")`
- **Actions**: `launchlab.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `launchlab.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `launchlab.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-launchlab@1.0.0` (packages/protocols/launchlab), with its library `import "protocols/launchlab";`
- **Functions**: `fn launchlabPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn launchlabDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn launchlabRipe(progress: Series<number>, graduated: Series<number>, from: number, to: number): Series<bool>`; `fn launchlabRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "launchlab" }`, ftw-protocols/references/launchlab.md

```flowscript
uses { launchlab: "^1" }
import "protocols/launchlab";
const t = "solana:57MSFmB7ypDU57tpVM9AmnArfLwz4k4fvkActu4Gkgzu";
const ripe = launchlabRipe(launchlab.progress(t, interval), launchlab.graduated(t, interval), 40, 90);
when (ripe && launchlab.held(t) == 0) { launchlab.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (launchlabDumped(launchlab.price(t, interval), 30, 24) && launchlab.held(t) > 0) { launchlab.sell({ market: t, all: true }); }
```

### Virtuals (`virtuals`)

Agent tokens launched through Virtuals' BondingV5 on Robinhood Chain, bought and sold against VIRTUAL on each one's bonding pair until it graduates to a Uniswap v2 pool: a constant product held to the k the pair was minted with. A buy pays the factory's buy tax off what it spends, a sell its sell tax off what it gets; a launch may add an anti-sniper tax that falls linearly to nothing over its window (60 seconds, 10 or 98 minutes). The buy that leaves the token's reserve at its threshold graduates it. Computed with BondingV5's and its router's own math, to the wei.

- **Import**: `uses { virtuals: "^1" }`
- **Reads**: `virtuals.price("robinhood:token", interval)`, `virtuals.priceQuote("robinhood:token", interval)`, `virtuals.marketCap("robinhood:token", interval)`, `virtuals.raised("robinhood:token", interval)`, `virtuals.raisedUsd("robinhood:token", interval)`, `virtuals.progress("robinhood:token", interval)`, `virtuals.graduated("robinhood:token", interval)`, `virtuals.fee("robinhood:token", interval)`, `virtuals.launchTax("robinhood:token", interval)`, `virtuals.age("robinhood:token", interval)`
- **Its book**: `virtuals.held("robinhood:token")`, `virtuals.heldUsd("robinhood:token")`, `virtuals.worthQuote("robinhood:token")`, `virtuals.stateAge("deployment")`
- **Actions**: `virtuals.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `virtuals.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `virtuals.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-virtuals@1.0.0` (packages/protocols/virtuals), with its library `import "protocols/virtuals";`
- **Functions**: `fn virtualsPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn virtualsDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn virtualsRipe(progress: Series<number>, graduated: Series<number>, launchTax: Series<number>, pct: number): Series<bool>`; `fn virtualsRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "virtuals" }`, ftw-protocols/references/virtuals.md

```flowscript
uses { virtuals: "^1" }
import "protocols/virtuals";
const t = "robinhood:0x66b4aa563e01613d6deeddf3a1b6569a3dff2876";
const ripe = virtualsRipe(virtuals.progress(t, interval), virtuals.graduated(t, interval), virtuals.launchTax(t, interval), 50);
when (ripe && virtuals.held(t) == 0) { virtuals.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (virtualsDumped(virtuals.price(t, interval), 30, 24) && virtuals.held(t) > 0) { virtuals.sell({ market: t, all: true }); }
```

### Zora (`zora`)

Coins launched through Zora's factory on Base: creator coins against ZORA, content coins against their creator's coin (or WETH or ZORA), trend coins against ZORA, each trading from its launch on its own Uniswap v4 pool through one of Zora's coin hooks. A swap pays the pool's LP fee as the hook sets it: 99% at a coin's creation, falling linearly to 1% over its first 10 seconds, then 1% (0.01% for a trend coin); a coin on an older hook pays its pool's own. A buy goes from the root currency (ZORA or WETH) through every pool down to the coin in one Universal Router call, and a sale back the same way. There is no curve to graduate from. Computed with v4's own math and the hook's fee, to the wei.

- **Import**: `uses { zora: "^1" }`
- **Reads**: `zora.price("base:token", interval)`, `zora.priceQuote("base:token", interval)`, `zora.marketCap("base:token", interval)`, `zora.graduated("base:token", interval)`, `zora.fee("base:token", interval)`, `zora.launchTax("base:token", interval)`, `zora.age("base:token", interval)`
- **Its book**: `zora.held("base:token")`, `zora.heldUsd("base:token")`, `zora.worthQuote("base:token")`, `zora.stateAge("deployment")`
- **Actions**: `zora.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `zora.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `zora.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-zora@1.0.0` (packages/protocols/zora), with its library `import "protocols/zora";`
- **Functions**: `fn zoraPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn zoraDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn zoraSettled(launchTax: Series<number>, age: Series<number>, secs: number): Series<bool>`; `fn zoraRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "zora" }`, ftw-protocols/references/zora.md

```flowscript
uses { zora: "^1" }
import "protocols/zora";
const t = "base:0xac09b7b3e2f6b1b9434f76d30d197ca80736f6c3";
const price = zora.price(t, interval);
const settled = zoraSettled(zora.launchTax(t, interval), zora.age(t, interval), 60);
when (settled && zoraPumped(price, 20, 24) && zora.held(t) == 0) { zora.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (zoraDumped(price, 30, 24) && zora.held(t) > 0) { zora.sell({ market: t, all: true }); }
```

### Clanker (`clanker`)

Tokens launched through Clanker's v4 factory on Base, each trading from its launch on its own Uniswap v4 pool against the token it was paired with (WETH for nearly all), through one of Clanker's static-fee hooks. A buy pays the token's buy-side LP fee and a sale its sell-side one (often 1% each), and the hook takes 20% of that on top. Before each swap the token's LP locker collects its positions' fees and sells the share its recipients want in the other token through the same pool. A pool's first two minutes run Clanker's sniper auction, in which trades are refused. There is no curve to graduate from. Computed with v4's own math, the hook's fees and the locker's sales, to the wei.

- **Import**: `uses { clanker: "^1" }`
- **Reads**: `clanker.price("base:token", interval)`, `clanker.priceQuote("base:token", interval)`, `clanker.marketCap("base:token", interval)`, `clanker.graduated("base:token", interval)`, `clanker.fee("base:token", interval)`, `clanker.launchTax("base:token", interval)`, `clanker.age("base:token", interval)`, `clanker.sellFee("base:token", interval)`
- **Its book**: `clanker.held("base:token")`, `clanker.heldUsd("base:token")`, `clanker.worthQuote("base:token")`, `clanker.stateAge("deployment")`
- **Actions**: `clanker.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `clanker.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `clanker.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-clanker@1.0.0` (packages/protocols/clanker), with its library `import "protocols/clanker";`
- **Functions**: `fn clankerPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn clankerDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn clankerTradable(fee: Series<number>, sellFee: Series<number>, launchTax: Series<number>, maxPct: number): Series<bool>`; `fn clankerRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "clanker" }`, ftw-protocols/references/clanker.md

```flowscript
uses { clanker: "^1" }
import "protocols/clanker";
const t = "base:0x16332535e2c27da578bc2e82beb09ce9d3c8eb07";
const price = clanker.price(t, interval);
const ok = clankerTradable(clanker.fee(t, interval), clanker.sellFee(t, interval), clanker.launchTax(t, interval), 3);
when (ok && clankerPumped(price, 20, 24) && clanker.held(t) == 0) { clanker.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (clankerDumped(price, 30, 24) && clanker.held(t) > 0) { clanker.sell({ market: t, all: true }); }
```

### Flap (`flap`)

Tokens launched through the Flap Portal on Robinhood Chain, a billion each, bought and sold on Flap's bonding curve until a token's circulating supply reaches its threshold and it moves to a DEX: (1e9 + h − s)·(reserve + r) = k, the supply a buy reaches rounded down and the reserve a sell leaves rounded up. A buy pays the Portal's fee and the token's own buy tax off what it spends, a sell the fee and its sell tax off what it gets; the buy that would carry the supply past the threshold stops there, spends only what reaching it costs and is refunded the rest, and the token moves to its DEX. Computed with Flap's own curve, held to the Portal's quotes and trades to the wei.

- **Import**: `uses { flap: "^1" }`
- **Reads**: `flap.price("robinhood:token", interval)`, `flap.priceQuote("robinhood:token", interval)`, `flap.marketCap("robinhood:token", interval)`, `flap.raised("robinhood:token", interval)`, `flap.raisedUsd("robinhood:token", interval)`, `flap.progress("robinhood:token", interval)`, `flap.graduated("robinhood:token", interval)`, `flap.fee("robinhood:token", interval)`, `flap.launchTax("robinhood:token", interval)`, `flap.tax("robinhood:token", interval)`
- **Its book**: `flap.held("robinhood:token")`, `flap.heldUsd("robinhood:token")`, `flap.worthQuote("robinhood:token")`, `flap.stateAge("deployment")`
- **Actions**: `flap.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? })`, `flap.buy({ market, amountIn | usd | pct | all, slippageBps? })`, `flap.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? })`
- **Package**: `@ftw-trade/protocol-flap@1.0.0` (packages/protocols/flap), with its library `import "protocols/flap";`
- **Functions**: `fn flapPumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn flapDumped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn flapRipe(progress: Series<number>, graduated: Series<number>, launchTax: Series<number>, pct: number): Series<bool>`; `fn flapRebalance(heldUsd: number, target: number, band: number): number`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "flap" }`, ftw-protocols/references/flap.md

```flowscript
uses { flap: "^1" }
import "protocols/flap";
const t = "robinhood:0xc02e8990de4c4df017758365f2c07c4cba247777";
const ripe = flapRipe(flap.progress(t, interval), flap.graduated(t, interval), flap.launchTax(t, interval), 50);
when (ripe && flap.held(t) == 0) { flap.buy({ market: t, usd: 50, slippageBps: 300 }); }
when (flapDumped(flap.price(t, interval), 30, 24) && flap.held(t) > 0) { flap.sell({ market: t, all: true }); }
```

### Metric (`metric_omm`)

Metric's oracle-priced pools on Robinhood Chain, Ethereum, Base and Arbitrum: every pool its factory made that trades against a dollar, swapped through at what Metric's own quoter answers (a ladder of sizes read each bar for paper, exactly what is sold at the head with real funds, sent through its SimpleRouter), and liquidity put into a pool's bins pro rata through its LiquidityAdder and taken out of the pool, with the share math its bins keep, to the wei.

- **Import**: `uses { metric_omm: "^1" }`
- **Reads**: `metric_omm.price("arbitrum:token", interval)`, `metric_omm.bid("arbitrum:token", interval)`, `metric_omm.ask("arbitrum:token", interval)`, `metric_omm.oracleSpread("arbitrum:token", interval)`, `metric_omm.roundTrip("arbitrum:token", interval)`, `metric_omm.liquidityUsd("arbitrum:token", interval)`
- **Its book**: `metric_omm.held("arbitrum:token")`, `metric_omm.heldUsd("arbitrum:token")`, `metric_omm.lpUsd("arbitrum:token")`, `metric_omm.lpFeesUsd("arbitrum:token")`, `metric_omm.lpShare("arbitrum:token")`, `metric_omm.lpInRange("arbitrum:token")`, `metric_omm.lpPnlUsd("arbitrum:token")`, `metric_omm.stateAge("deployment")`
- **Actions**: `metric_omm.swap({ market, sell, amountIn | usd | all, slippageBps? })`, `metric_omm.addLiquidity({ market, usd | amount0 | amount1 | all, bins?, slippageBps? })`, `metric_omm.removeLiquidity({ market, pct? })`
- **Package**: `@ftw-trade/protocol-metric@1.0.0` (packages/protocols/metric-omm), with its library `import "protocols/metric_omm";`
- **Functions**: `fn metricTight(roundTrip: Series<number>, pct: number): Series<bool>`; `fn metricDip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn metricRip(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn metricDeep(liquidityUsd: Series<number>, usd: number): Series<bool>`
- **Real funds**: yes
- **More**: `get_protocol_guide { protocol: "metric_omm" }`, ftw-protocols/references/metric_omm.md

```flowscript
uses { metric_omm: "^1" }
import "protocols/metric_omm";
const pool = "robinhood:weth-usdg-166";
when (metric_omm.lpUsd(pool) == 0 && metricTight(metric_omm.roundTrip(pool, interval), 1)) {
  metric_omm.swap({ market: pool, sell: "USDG", amountIn: 500 });
  metric_omm.addLiquidity({ market: pool, all: true });
}
when (every("1d") && !metricTight(metric_omm.roundTrip(pool, interval), 1)) {
  metric_omm.removeLiquidity({ market: pool });
}
metric("lp", metric_omm.lpUsd(pool));
```

## Lending markets

### Aave (`aave`)

Lending and borrowing on Aave v3's pools on Ethereum, Base and Arbitrum.

- **Import**: `uses { aave: "^1" }`
- **Reads**: `aave.supplyApy("arbitrum:token", interval)`, `aave.borrowApy("arbitrum:token", interval)`, `aave.utilization("arbitrum:token", interval)`, `aave.available("arbitrum:token", interval)`, `aave.totalSupplied("arbitrum:token", interval)`, `aave.totalBorrowed("arbitrum:token", interval)`, `aave.supplyRoom("arbitrum:token", interval)`, `aave.borrowRoom("arbitrum:token", interval)`, `aave.price("arbitrum:token", interval)`, `aave.ltv("arbitrum:token", interval)`, `aave.threshold("arbitrum:token", interval)`
- **Its book**: `aave.supplied("arbitrum:token")`, `aave.borrowed("arbitrum:token")`, `aave.healthFactor("deployment")`, `aave.collateralUsd("deployment")`, `aave.debtUsd("deployment")`, `aave.borrowableUsd("deployment")`, `aave.currentLtv("deployment")`, `aave.liquidationLtv("deployment")`, `aave.eMode("deployment")`, `aave.stateAge("deployment")`
- **Actions**: `aave.supply({ asset, units | usd | pct | all, keep?, slippageBps? })`, `aave.withdraw({ asset, units | usd | pct | all, keep?, slippageBps? })`, `aave.borrow({ asset, units | usd | pct, keep?, slippageBps? })`, `aave.repay({ asset, units | usd | pct | all, keep?, slippageBps? })`, `aave.eMode({ category })`, `aave.collateral({ asset, on })`
- **Package**: `@ftw-trade/protocol-aave@1.0.0` (packages/protocols/aave), with its library `import "protocols/aave";`
- **Functions**: `fn leveredApy(earn: Series<number>, borrow: Series<number>, leverage: number): Series<number>`; `fn maxLeverage(ltv: number): number`; `fn loopPays(earn: Series<number>, borrow: Series<number>, margin: number, bars: number): Series<bool>`; `fn borrowSpiked(borrow: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:aave-base-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "aave" }`, ftw-protocols/references/aave.md

```flowscript
uses { aave: "^1" }
const r = aave.supplyApy("base:usdc", interval);
when (r > 4 && aave.supplied("base:usdc") == 0) { aave.supply({ asset: "base:usdc", all: true }); }
when (r < 2.5 && aave.supplied("base:usdc") > 0) { aave.withdraw({ asset: "base:usdc", all: true }); }
metric("apy", r);
```

### Spark (`spark`)

Lending and borrowing on SparkLend, an Aave v3 pool on Ethereum.

- **Import**: `uses { spark: "^1" }`
- **Reads**: `spark.supplyApy("ethereum:token", interval)`, `spark.borrowApy("ethereum:token", interval)`, `spark.utilization("ethereum:token", interval)`, `spark.available("ethereum:token", interval)`, `spark.totalSupplied("ethereum:token", interval)`, `spark.totalBorrowed("ethereum:token", interval)`, `spark.supplyRoom("ethereum:token", interval)`, `spark.borrowRoom("ethereum:token", interval)`, `spark.price("ethereum:token", interval)`, `spark.ltv("ethereum:token", interval)`, `spark.threshold("ethereum:token", interval)`
- **Its book**: `spark.supplied("ethereum:token")`, `spark.borrowed("ethereum:token")`, `spark.healthFactor("deployment")`, `spark.collateralUsd("deployment")`, `spark.debtUsd("deployment")`, `spark.borrowableUsd("deployment")`, `spark.currentLtv("deployment")`, `spark.liquidationLtv("deployment")`, `spark.eMode("deployment")`, `spark.stateAge("deployment")`
- **Actions**: `spark.supply({ asset, units | usd | pct | all, keep?, slippageBps? })`, `spark.withdraw({ asset, units | usd | pct | all, keep?, slippageBps? })`, `spark.borrow({ asset, units | usd | pct, keep?, slippageBps? })`, `spark.repay({ asset, units | usd | pct | all, keep?, slippageBps? })`, `spark.eMode({ category })`, `spark.collateral({ asset, on })`
- **Package**: `@ftw-trade/protocol-spark@1.0.0` (packages/protocols/spark), with its library `import "protocols/spark";`
- **Functions**: `fn sparkPays(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn sparkRateSpiked(apy: Series<number>, points: number, bars: number): Series<bool>`; `fn sparkEarns(usd: number, apy: Series<number>, days: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:spark-ethereum-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "spark" }`, ftw-protocols/references/spark.md

```flowscript
uses { spark: "^1" }
const s = spark.supplyApy("ethereum:usdc", interval);
metric("spark", s);
when (s > 5 && spark.supplied("ethereum:usdc") == 0) { spark.supply({ asset: "ethereum:usdc", all: true }); }
when (s < 3 && spark.supplied("ethereum:usdc") > 0) { spark.withdraw({ asset: "ethereum:usdc", all: true }); }
```

### Compound (`compound`)

Lending and borrowing on Compound III's Comets on Ethereum, Base and Arbitrum: each lends one base asset against its collaterals.

- **Import**: `uses { compound: "^1" }`
- **Reads**: `compound.supplyApy("arbitrum-usdt:token", interval)`, `compound.borrowApy("arbitrum-usdt:token", interval)`, `compound.utilization("arbitrum-usdt:token", interval)`, `compound.available("arbitrum-usdt:token", interval)`, `compound.totalSupplied("arbitrum-usdt:token", interval)`, `compound.totalBorrowed("arbitrum-usdt:token", interval)`, `compound.supplyRoom("arbitrum-usdt:token", interval)`, `compound.borrowRoom("arbitrum-usdt:token", interval)`, `compound.price("arbitrum-usdt:token", interval)`, `compound.ltv("arbitrum-usdt:token", interval)`, `compound.threshold("arbitrum-usdt:token", interval)`
- **Its book**: `compound.supplied("arbitrum-usdt:token")`, `compound.borrowed("arbitrum-usdt:token")`, `compound.healthFactor("deployment")`, `compound.collateralUsd("deployment")`, `compound.debtUsd("deployment")`, `compound.borrowableUsd("deployment")`, `compound.currentLtv("deployment")`, `compound.liquidationLtv("deployment")`, `compound.stateAge("deployment")`
- **Actions**: `compound.supply({ asset, units | usd | pct | all })`, `compound.withdraw({ asset, units | usd | pct | all })`, `compound.borrow({ asset, units | usd | pct })`, `compound.repay({ asset, units | usd | pct | all })`
- **Package**: `@ftw-trade/protocol-compound@1.0.0` (packages/protocols/compound), with its library `import "protocols/compound";`
- **Functions**: `fn compoundPays(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn compoundRateSpiked(apy: Series<number>, points: number, bars: number): Series<bool>`; `fn compoundEarns(usd: number, apy: Series<number>, days: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:compound-arbitrum-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "compound" }`, ftw-protocols/references/compound.md

```flowscript
uses { compound: "^1" }
const r = compound.supplyApy("arbitrum:usdc", interval);
when (r > 4 && compound.supplied("arbitrum:usdc") == 0) { compound.supply({ asset: "arbitrum:usdc", all: true }); }
when (r < 2 && compound.supplied("arbitrum:usdc") > 0) { compound.withdraw({ asset: "arbitrum:usdc", all: true }); }
metric("apy", r);
```

### marginfi (`marginfi`)

Lending and borrowing on marginfi's main group on Solana: every bank it lists, priced by the bank's own oracle.

- **Import**: `uses { marginfi: "^1" }`
- **Reads**: `marginfi.supplyApy("solana:token", interval)`, `marginfi.borrowApy("solana:token", interval)`, `marginfi.utilization("solana:token", interval)`, `marginfi.available("solana:token", interval)`, `marginfi.price("solana:token", interval)`, `marginfi.ltv("solana:token", interval)`, `marginfi.threshold("solana:token", interval)`
- **Its book**: `marginfi.supplied("solana:token")`, `marginfi.borrowed("solana:token")`, `marginfi.healthFactor("deployment")`, `marginfi.collateralUsd("deployment")`, `marginfi.debtUsd("deployment")`, `marginfi.borrowableUsd("deployment")`, `marginfi.stateAge("deployment")`
- **Actions**: `marginfi.supply({ asset, units | usd | pct | all })`, `marginfi.withdraw({ asset, units | usd | pct | all })`, `marginfi.borrow({ asset, units | usd | pct })`, `marginfi.repay({ asset, units | usd | pct | all })`
- **Package**: `@ftw-trade/protocol-marginfi@1.0.0` (packages/protocols/marginfi), with its library `import "protocols/marginfi";`
- **Functions**: `fn marginfiPays(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn marginfiTight(utilization: Series<number>, available: Series<number>, ratio: number, units: number): Series<bool>`; `fn marginfiEarns(usd: number, apy: Series<number>, days: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:marginfi-solana-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "marginfi" }`, ftw-protocols/references/marginfi.md

```flowscript
uses { marginfi: "^1" }
const r = marginfi.supplyApy("solana:usdc", interval);
when (r > 8 && marginfi.supplied("solana:usdc") == 0) { marginfi.supply({ asset: "solana:usdc", all: true }); }
when (r < 4 && marginfi.supplied("solana:usdc") > 0) { marginfi.withdraw({ asset: "solana:usdc", all: true }); }
metric("apy", r);
```

### Kamino (`kamino`)

Lending and borrowing on Kamino Lend's markets on Solana (primary, JLP, Jito, Maple, Ethena, altcoins): every reserve each lists, priced by its Scope oracle, with its elevation groups.

- **Import**: `uses { kamino: "^1" }`
- **Reads**: `kamino.supplyApy("solana-altcoins:token", interval)`, `kamino.borrowApy("solana-altcoins:token", interval)`, `kamino.utilization("solana-altcoins:token", interval)`, `kamino.available("solana-altcoins:token", interval)`, `kamino.price("solana-altcoins:token", interval)`, `kamino.ltv("solana-altcoins:token", interval)`, `kamino.threshold("solana-altcoins:token", interval)`, `kamino.borrowFactor("solana-altcoins:token", interval)`
- **Its book**: `kamino.supplied("solana-altcoins:token")`, `kamino.borrowed("solana-altcoins:token")`, `kamino.healthFactor("deployment")`, `kamino.collateralUsd("deployment")`, `kamino.debtUsd("deployment")`, `kamino.borrowableUsd("deployment")`, `kamino.currentLtv("deployment")`, `kamino.liquidationLtv("deployment")`, `kamino.eMode("deployment")`, `kamino.stateAge("deployment")`
- **Actions**: `kamino.supply({ asset, units | usd | pct | all })`, `kamino.withdraw({ asset, units | usd | pct | all })`, `kamino.borrow({ asset, units | usd | pct })`, `kamino.repay({ asset, units | usd | pct | all })`, `kamino.eMode({ category })`
- **Package**: `@ftw-trade/protocol-kamino@1.0.0` (packages/protocols/kamino), with its library `import "protocols/kamino";`
- **Functions**: `fn kaminoPays(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn kaminoRateSpiked(apy: Series<number>, points: number, bars: number): Series<bool>`; `fn kaminoTight(utilization: Series<number>, available: Series<number>, ratio: number, units: number): Series<bool>`; `fn kaminoEarns(usd: number, apy: Series<number>, days: number): Series<number>`; `fn kaminoCarry(earn: Series<number>, borrow: Series<number>, leverage: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:kamino-solana-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "kamino" }`, ftw-protocols/references/kamino.md

```flowscript
uses { kamino: "^1" }
const r = kamino.supplyApy("solana:usdc", interval);
when (r > 8 && kamino.supplied("solana:usdc") == 0) { kamino.supply({ asset: "solana:usdc", all: true }); }
when (r < 4 && kamino.supplied("solana:usdc") > 0) { kamino.withdraw({ asset: "solana:usdc", all: true }); }
metric("apy", r);
```

## Vaults

### Morpho (`morpho`)

MetaMorpho vaults over Morpho Blue on Ethereum, Base and Arbitrum: a curator lends each vault's deposits (USDC, WETH) across the Blue markets it picks, up to each market's cap.

- **Import**: `uses { morpho: "^1" }`
- **Reads**: `morpho.apy("arbitrum:vault", interval)`, `morpho.sharePrice("arbitrum:vault", interval)`, `morpho.totalAssets("arbitrum:vault", interval)`, `morpho.available("arbitrum:vault", interval)`, `morpho.depositRoom("arbitrum:vault", interval)`
- **Its book**: `morpho.deposited("arbitrum:vault")`, `morpho.stateAge("deployment")`
- **Actions**: `morpho.deposit({ asset, units | pct | all })`, `morpho.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-morpho@1.0.0` (packages/protocols/morpho), with its library `import "protocols/morpho";`
- **Functions**: `fn morphoBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn morphoLoss(price: Series<number>, pct: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:morpho-base-steakusdc-apy`
- **More**: `get_protocol_guide { protocol: "morpho" }`, ftw-protocols/references/morpho.md

```flowscript
uses { morpho: "^1" }
const a = morpho.apy("base:steakusdc", interval);
when (a > 5 && morpho.deposited("base:steakusdc") == 0) { morpho.deposit({ asset: "base:steakusdc", all: true }); }
when (a < 3 && morpho.deposited("base:steakusdc") > 0) { morpho.withdraw({ asset: "base:steakusdc", all: true }); }
metric("apy", a);
```

### Euler (`euler`)

Euler's EVK lending vaults on Ethereum, Base and Arbitrum: each lends one token (USDC) to the borrowers its governor allows, at the rate its rate model sets from how much is borrowed.

- **Import**: `uses { euler: "^1" }`
- **Reads**: `euler.apy("arbitrum:vault", interval)`, `euler.sharePrice("arbitrum:vault", interval)`, `euler.totalAssets("arbitrum:vault", interval)`, `euler.available("arbitrum:vault", interval)`, `euler.depositRoom("arbitrum:vault", interval)`
- **Its book**: `euler.deposited("arbitrum:vault")`, `euler.stateAge("deployment")`
- **Actions**: `euler.deposit({ asset, units | pct | all })`, `euler.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-euler@1.0.0` (packages/protocols/euler), with its library `import "protocols/euler";`
- **Functions**: `fn eulerBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn eulerLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn eulerUtilizationSpiked(apy: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:euler-ethereum-k3prime-apy`
- **More**: `get_protocol_guide { protocol: "euler" }`, ftw-protocols/references/euler.md

```flowscript
uses { euler: "^1" }
const a = euler.apy("ethereum:k3prime", interval);
when (a > 6 && euler.deposited("ethereum:k3prime") == 0) { euler.deposit({ asset: "ethereum:k3prime", all: true }); }
when (a < 4 && euler.deposited("ethereum:k3prime") > 0) { euler.withdraw({ asset: "ethereum:k3prime", all: true }); }
metric("apy", a);
```

### Yearn (`yearn`)

Yearn's v3 vaults on Ethereum, Base and Arbitrum, as Yearn's registry endorses them: each lends one token across a queue of strategies, and its share price grows as the profit each report locks frees by the second.

- **Import**: `uses { yearn: "^1" }`
- **Reads**: `yearn.apy("arbitrum:vault", interval)`, `yearn.sharePrice("arbitrum:vault", interval)`, `yearn.totalAssets("arbitrum:vault", interval)`, `yearn.available("arbitrum:vault", interval)`, `yearn.depositRoom("arbitrum:vault", interval)`
- **Its book**: `yearn.deposited("arbitrum:vault")`, `yearn.stateAge("deployment")`
- **Actions**: `yearn.deposit({ asset, units | pct | all })`, `yearn.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-yearn@1.0.0` (packages/protocols/yearn), with its library `import "protocols/yearn";`
- **Functions**: `fn yearnBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn yearnLoss(price: Series<number>, pct: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:yearn-ethereum-yvusdc-1-apy`
- **More**: `get_protocol_guide { protocol: "yearn" }`, ftw-protocols/references/yearn.md

```flowscript
uses { yearn: "^1" }
const a = yearn.apy("ethereum:yvusdc-1", interval);
when (a > 6 && yearn.deposited("ethereum:yvusdc-1") == 0) { yearn.deposit({ asset: "ethereum:yvusdc-1", all: true }); }
when (a < 4 && yearn.deposited("ethereum:yvusdc-1") > 0) { yearn.withdraw({ asset: "ethereum:yvusdc-1", all: true }); }
metric("apy", a);
```

### Fluid (`fluid`)

Fluid's lending fTokens on Ethereum, Base and Arbitrum, as its LendingFactory made them: each lends one token into Fluid's Liquidity layer, and its share price grows with Liquidity's supply exchange price, by the second.

- **Import**: `uses { fluid: "^1" }`
- **Reads**: `fluid.apy("arbitrum:vault", interval)`, `fluid.sharePrice("arbitrum:vault", interval)`, `fluid.totalAssets("arbitrum:vault", interval)`, `fluid.available("arbitrum:vault", interval)`, `fluid.depositRoom("arbitrum:vault", interval)`
- **Its book**: `fluid.deposited("arbitrum:vault")`, `fluid.stateAge("deployment")`
- **Actions**: `fluid.deposit({ asset, units | pct | all })`, `fluid.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-fluid@1.0.0` (packages/protocols/fluid), with its library `import "protocols/fluid";`
- **Functions**: `fn fluidBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn fluidLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn fluidRewardsEnded(apy: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:fluid-base-fusdc-apy`
- **More**: `get_protocol_guide { protocol: "fluid" }`, ftw-protocols/references/fluid.md

```flowscript
uses { fluid: "^1" }
const a = fluid.apy("base:fusdc", interval);
when (a > 5 && fluid.deposited("base:fusdc") == 0) { fluid.deposit({ asset: "base:fusdc", all: true }); }
when (a < 3 && fluid.deposited("base:fusdc") > 0) { fluid.withdraw({ asset: "base:fusdc", all: true }); }
metric("apy", a);
```

### Gearbox (`gearbox`)

Gearbox's lending pools on Ethereum, as its curators' registers list them: each lends one token to Gearbox's leveraged credit accounts, and its share price grows with the base interest and quota revenue they pay, by the second.

- **Import**: `uses { gearbox: "^1" }`
- **Reads**: `gearbox.apy("ethereum:vault", interval)`, `gearbox.sharePrice("ethereum:vault", interval)`, `gearbox.totalAssets("ethereum:vault", interval)`, `gearbox.available("ethereum:vault", interval)`, `gearbox.depositRoom("ethereum:vault", interval)`, `gearbox.utilization("ethereum:vault", interval)`
- **Its book**: `gearbox.deposited("ethereum:vault")`, `gearbox.stateAge("deployment")`
- **Actions**: `gearbox.deposit({ asset, units | pct | all })`, `gearbox.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-gearbox@1.0.0` (packages/protocols/gearbox), with its library `import "protocols/gearbox";`
- **Functions**: `fn gearboxBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn gearboxLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn gearboxDemandSpiked(apy: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:gearbox-ethereum-tulipa-resolv-usdc-apy`
- **More**: `get_protocol_guide { protocol: "gearbox" }`, ftw-protocols/references/gearbox.md

```flowscript
uses { gearbox: "^1" }
const a = gearbox.apy("ethereum:tulipa-resolv-usdc", interval);
when (a > 8 && gearbox.deposited("ethereum:tulipa-resolv-usdc") == 0) { gearbox.deposit({ asset: "ethereum:tulipa-resolv-usdc", all: true }); }
when (a < 4 && gearbox.deposited("ethereum:tulipa-resolv-usdc") > 0) { gearbox.withdraw({ asset: "ethereum:tulipa-resolv-usdc", all: true }); }
metric("apy", a);
```

### Spark Savings (`spark_savings`)

Spark's savings vaults (spUSDC, spUSDT and spPYUSD on Ethereum, spUSDG on Robinhood Chain): each pays its vault savings rate on a dollar, compounded by the second, while Spark lends what it takes from the vault elsewhere.

- **Import**: `uses { spark_savings: "^1" }`
- **Reads**: `spark_savings.apy("robinhood:vault", interval)`, `spark_savings.sharePrice("robinhood:vault", interval)`, `spark_savings.totalAssets("robinhood:vault", interval)`, `spark_savings.available("robinhood:vault", interval)`, `spark_savings.depositRoom("robinhood:vault", interval)`
- **Its book**: `spark_savings.deposited("robinhood:vault")`, `spark_savings.stateAge("deployment")`
- **Actions**: `spark_savings.deposit({ asset, units | pct | all })`, `spark_savings.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-spark-savings@1.0.0` (packages/protocols/spark-savings), with its library `import "protocols/spark_savings";`
- **Functions**: `fn savingsBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn savingsLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn savingsRateCut(apy: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:spark-savings-ethereum-spusdc-apy`
- **More**: `get_protocol_guide { protocol: "spark_savings" }`, ftw-protocols/references/spark_savings.md

```flowscript
uses { spark_savings: "^1" }
const a = spark_savings.apy("ethereum:spusdc", interval);
when (a > 4 && spark_savings.deposited("ethereum:spusdc") == 0) { spark_savings.deposit({ asset: "ethereum:spusdc", all: true }); }
when (a < 2) { spark_savings.withdraw({ asset: "ethereum:spusdc", all: true }); }
```

### Sky (`sky`)

The Sky Savings Rate through sUSDS on Ethereum, reached with USDC through Sky's PSM: USDS saved grows at the rate governance sets, compounded by the second.

- **Import**: `uses { sky: "^1" }`
- **Reads**: `sky.apy("ethereum:vault", interval)`, `sky.sharePrice("ethereum:vault", interval)`, `sky.totalAssets("ethereum:vault", interval)`, `sky.available("ethereum:vault", interval)`, `sky.depositRoom("ethereum:vault", interval)`
- **Its book**: `sky.deposited("ethereum:vault")`, `sky.stateAge("deployment")`
- **Actions**: `sky.deposit({ asset, units | pct | all })`, `sky.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-sky@1.0.0` (packages/protocols/sky), with its library `import "protocols/sky";`
- **Functions**: `fn skyBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn skyLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn skyRateCut(apy: Series<number>, points: number, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:sky-ethereum-susds-apy`
- **More**: `get_protocol_guide { protocol: "sky" }`, ftw-protocols/references/sky.md

```flowscript
uses { sky: "^1", aave: "^1" }
const ssr = sky.apy("ethereum:susds", interval);
const a = aave.supplyApy("ethereum:usdc", interval);
metric("gap", a - ssr);
when (ssr > a + 1 && sky.deposited("ethereum:susds") == 0) { sky.deposit({ asset: "ethereum:susds", all: true }); }
```

### Ethena (`ethena`)

Ethena's staked USDe (sUSDe) on Ethereum, reached with USDC through Curve's USDe/USDC pool: staked USDe grows as Ethena's rewards vest over each eight hours, and leaving waits out sUSDe's cooldown.

- **Import**: `uses { ethena: "^1" }`
- **Reads**: `ethena.apy("ethereum:vault", interval)`, `ethena.sharePrice("ethereum:vault", interval)`, `ethena.totalAssets("ethereum:vault", interval)`, `ethena.cooldownDays("ethereum:vault", interval)`, `ethena.usdePrice("ethereum:vault", interval)`, `ethena.poolUsdc("ethereum:vault", interval)`, `ethena.poolUsde("ethereum:vault", interval)`
- **Its book**: `ethena.deposited("ethereum:vault")`, `ethena.cooling("ethereum:vault")`, `ethena.claimable("ethereum:vault")`, `ethena.settlesAt("ethereum:vault")`, `ethena.stateAge("deployment")`
- **Actions**: `ethena.deposit({ asset, units | pct | all })`, `ethena.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-ethena@1.0.0` (packages/protocols/ethena), with its library `import "protocols/ethena";`
- **Functions**: `fn heldApy(apy: Series<number>, holdDays: number, cooldown: Series<number>): Series<number>`; `fn beats(apy: Series<number>, other: Series<number>, cooldown: Series<number>, holdDays: number, margin: number, bars: number): Series<bool>`; `fn fading(apy: Series<number>, floor: number, bars: number): Series<bool>`; `fn offPeg(usdePrice: Series<number>, gap: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:ethena-ethereum-susde-apy`
- **More**: `get_protocol_guide { protocol: "ethena" }`, ftw-protocols/references/ethena.md

```flowscript
uses { ethena: "^1" }
const y = ethena.apy("ethereum:susde", interval);
when (y > 12 && ethena.deposited("ethereum:susde") == 0) { ethena.deposit({ asset: "ethereum:susde", all: true }); }
when (y < 5 && ethena.cooling("ethereum:susde") == 0 && ethena.deposited("ethereum:susde") > 0) { ethena.withdraw({ asset: "ethereum:susde", all: true }); }
when (ethena.claimable("ethereum:susde") > 0) { ethena.withdraw({ asset: "ethereum:susde", all: true }); }
```

### Maple (`maple`)

Maple's Syrup pools on Ethereum (syrupUSDC, syrupUSDT, syrupUSDG): pools that lend to institutional borrowers on open-term loans, whose shares grow as the loans' interest accrues. Bought with the pool's asset on Fluid's DEX, left through Maple's withdrawal queue.

- **Import**: `uses { maple: "^1" }`
- **Reads**: `maple.apy("ethereum:vault", interval)`, `maple.sharePrice("ethereum:vault", interval)`, `maple.totalAssets("ethereum:vault", interval)`, `maple.cash("ethereum:vault", interval)`, `maple.depositRoom("ethereum:vault", interval)`, `maple.queuedShares("ethereum:vault", interval)`, `maple.unrealizedLosses("ethereum:vault", interval)`, `maple.dexPrice("ethereum:vault", interval)`
- **Its book**: `maple.deposited("ethereum:vault")`, `maple.queued("ethereum:vault")`, `maple.claimable("ethereum:vault")`, `maple.stateAge("deployment")`
- **Actions**: `maple.deposit({ asset, units?, pct?, all? })`, `maple.withdraw({ asset, units?, pct?, all? })`
- **Package**: `@ftw-trade/protocol-maple@1.0.0` (packages/protocols/maple), with its library `import "protocols/maple";`
- **Functions**: `fn syrupBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn syrupLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn syrupPremium(dexPrice: Series<number>, sharePrice: Series<number>): Series<number>`; `fn syrupQueueDays(): number`; `fn syrupHeldApy(apy: Series<number>, holdDays: number): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:maple-ethereum-syrupusdc-apy`
- **More**: `get_protocol_guide { protocol: "maple" }`, ftw-protocols/references/maple.md

```flowscript
uses { maple: "^1" }
import "protocols/maple";
const a = maple.apy("ethereum:syrupusdc", interval);
const premium = syrupPremium(maple.dexPrice("ethereum:syrupusdc", interval), maple.sharePrice("ethereum:syrupusdc", interval));
when (a > 7 && premium < 0.25 && maple.deposited("ethereum:syrupusdc") == 0) {
  maple.deposit({ asset: "ethereum:syrupusdc", all: true });
}
when (a < 4 && maple.queued("ethereum:syrupusdc") == 0 && maple.claimable("ethereum:syrupusdc") == 0) {
  maple.withdraw({ asset: "ethereum:syrupusdc", all: true });
}
when (maple.claimable("ethereum:syrupusdc") > 0) {
  maple.withdraw({ asset: "ethereum:syrupusdc", all: true });
}
```

### Kamino Earn (`kamino_earn`)

Kamino's curated vaults on Solana: each lends one token across Kamino Lend reserves under its curator, and its holders earn the reserves' interest less its fees.

- **Import**: `uses { kamino_earn: "^1" }`
- **Reads**: `kamino_earn.apy("solana:vault", interval)`, `kamino_earn.sharePrice("solana:vault", interval)`, `kamino_earn.totalAssets("solana:vault", interval)`, `kamino_earn.available("solana:vault", interval)`, `kamino_earn.depositRoom("solana:vault", interval)`
- **Its book**: `kamino_earn.deposited("solana:vault")`, `kamino_earn.stateAge("deployment")`
- **Actions**: `kamino_earn.deposit({ asset, units | pct | all })`, `kamino_earn.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-kamino-earn@1.0.0` (packages/protocols/kamino-earn), with its library `import "protocols/kamino_earn";`
- **Functions**: `fn kvaultBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn kvaultLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn kvaultTakes(room: Series<number>, amount: number): Series<bool>`; `fn kvaultPays(available: Series<number>, amount: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:kamino-earn-solana-steakusdc-apy`
- **More**: `get_protocol_guide { protocol: "kamino_earn" }`, ftw-protocols/references/kamino_earn.md

```flowscript
uses { kamino_earn: "^1" }
import "protocols/kamino_earn";
const a = kamino_earn.apy("solana:steakusdc", interval);
const room = kamino_earn.depositRoom("solana:steakusdc", interval);
when (a > 6 && kvaultTakes(room, 1000) && kamino_earn.deposited("solana:steakusdc") == 0) {
  kamino_earn.deposit({ asset: "solana:steakusdc", all: true });
}
when (a < 3 && kamino_earn.deposited("solana:steakusdc") > 0) {
  kamino_earn.withdraw({ asset: "solana:steakusdc", all: true });
}
```

### Jupiter Lend (`jupiter_lend`)

Jupiter Lend Earn on Solana: jlTokens lend one token into Jupiter Lend's liquidity layer, and their holders earn what its borrowers pay, less its fee, with any rewards.

- **Import**: `uses { jupiter_lend: "^1" }`
- **Reads**: `jupiter_lend.apy("solana:vault", interval)`, `jupiter_lend.sharePrice("solana:vault", interval)`, `jupiter_lend.totalAssets("solana:vault", interval)`, `jupiter_lend.available("solana:vault", interval)`
- **Its book**: `jupiter_lend.deposited("solana:vault")`, `jupiter_lend.stateAge("deployment")`
- **Actions**: `jupiter_lend.deposit({ asset, units | pct | all })`, `jupiter_lend.withdraw({ asset, units | pct | all })`
- **Package**: `@ftw-trade/protocol-jupiter-lend@1.0.0` (packages/protocols/jupiter-lend), with its library `import "protocols/jupiter_lend";`
- **Functions**: `fn jupiterBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn jupiterLoss(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn jupiterPays(available: Series<number>, amount: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:jupiter-lend-solana-jlusdc-apy`
- **More**: `get_protocol_guide { protocol: "jupiter_lend" }`, ftw-protocols/references/jupiter_lend.md

```flowscript
uses { jupiter_lend: "^1" }
import "protocols/jupiter_lend";
const a = jupiter_lend.apy("solana:jlusdc", interval);
when (a > 6 && jupiter_lend.deposited("solana:jlusdc") == 0) {
  jupiter_lend.deposit({ asset: "solana:jlusdc", all: true });
}
when (a < 3 && jupiter_lend.deposited("solana:jlusdc") > 0) {
  jupiter_lend.withdraw({ asset: "solana:jlusdc", all: true });
}
```

### Jito (`jito`)

Jito liquid staking on Solana: JitoSOL is the pool token of Jito's SPL stake pool, which stakes the SOL deposited with it across many validators and grows by what they earn, MEV tips included. A deposit turns the run's USDC into SOL on Raydium's SOL/USDC pool and stakes it; a withdrawal takes SOL out of the pool's reserve (its fee is 0.1%) and sells it back for USDC, unless keepSol. Computed with the stake pool program's own math, held to its answers on mainnet.

- **Import**: `uses { jito: "^1" }`
- **Reads**: `jito.apy("solana:vault", interval)`, `jito.solPrice("solana:vault", interval)`, `jito.price("solana:vault", interval)`, `jito.solUsd("solana:vault", interval)`, `jito.totalStaked("solana:vault", interval)`, `jito.depositFee("solana:vault", interval)`, `jito.withdrawalFee("solana:vault", interval)`
- **Its book**: `jito.held("solana:vault")`, `jito.heldSol("solana:vault")`, `jito.deposited("solana:vault")`, `jito.stateAge("deployment")`
- **Actions**: `jito.deposit({ asset, units | usd | sol | pct | all, slippageBps? })`, `jito.withdraw({ asset, units | usd | tokens | pct | all, keepSol?, slippageBps? })`
- **Package**: `@ftw-trade/protocol-jito@1.0.0` (packages/protocols/jito), with its library `import "protocols/jito";`
- **Functions**: `fn jitoBeats(apy: Series<number>, other: Series<number>, margin: number, bars: number): Series<bool>`; `fn jitoTrend(solUsd: Series<number>, bars: number, pct: number): Series<bool>`; `fn jitoLoss(solPrice: Series<number>, bars: number): Series<bool>`
- **Real funds**: yes
- **Backtest on**: `feed:jito-solana-jitosol-price`
- **More**: `get_protocol_guide { protocol: "jito" }`, ftw-protocols/references/jito.md

```flowscript
uses { jito: "^1" }
import "protocols/jito";
const sol = jito.solUsd("solana:jitosol", interval);
when (jitoTrend(sol, 72, 0) && jito.deposited("solana:jitosol") == 0) {
  jito.deposit({ asset: "solana:jitosol", usd: 100 });
}
when (!jitoTrend(sol, 72, -3) && jito.deposited("solana:jitosol") > 0) {
  jito.withdraw({ asset: "solana:jitosol", all: true });
}
```

### Jupiter (`jupiter`)

Solana's swap aggregator: its routes, limit orders and DCAs on a token book.

- **Import**: `uses { jupiter: "^1" }`
- **Reads**: `jupiter.price("chain:vault", interval)`, `jupiter.impact("chain:vault", interval)`
- **Its book**: `jupiter.held()`, `jupiter.heldUsd()`, `jupiter.units()`, `jupiter.avgPrice()`, `jupiter.locked()`, `jupiter.openOrders()`
- **Actions**: `jupiter.buy({ usd? })`, `jupiter.sell({ usd? })`, `jupiter.limitBuy({ usd? })`, `jupiter.limitSell({ usd? })`, `jupiter.dca({ usd? })`, `jupiter.cancel({ usd? })`
- **Package**: `@ftw-trade/protocol-jupiter@1.0.0` (packages/protocols/jupiter), with its library `import "protocols/jupiter";`
- **Functions**: `fn routeCost(impact: Series<number>, usd: number): Series<number>`; `fn routeOk(impact: Series<number>, usd: number, maxPct: number): Series<bool>`; `fn maxBuyUsd(impact: Series<number>, maxPct: number): Series<number>`; `fn rung(price: Series<number>, n: number, stepPct: number): Series<number>`; `fn dipped(price: Series<number>, pct: number, bars: number): Series<bool>`; `fn upFrom(price: Series<number>, avg: Series<number>, pct: number): Series<bool>`
- **Real funds**: paper only
- **More**: `get_protocol_guide { protocol: "jupiter" }`, ftw-protocols/references/jupiter.md

```flowscript
uses { jupiter: "^1", wallet: "^1" }
import "protocols/jupiter";

const px = jupiter.price("solana:sol", interval);
const depth = jupiter.impact("solana:sol", interval);

// Buy a dip at market only when $300 costs under 0.5% on the route, and rest a limit 4% lower.
when (dipped(px, 5, 168) && routeOk(depth, 300, 0.5) && jupiter.openOrders() == 0) {
  jupiter.buy({ usd: 300, slippageBps: 50, maxImpactPct: 0.5 });
  jupiter.limitBuy({ usd: 300, at: rung(px, 1, 4), expiresHours: 72 });
}
// Take 12% over cost: sell everything and take back what still rests.
when (upFrom(px, jupiter.avgPrice(), 12)) {
  jupiter.sell({ pct: 100, slippageBps: 50 });
}
```

## Fixed rates

### Pendle (`pendle`)

Pendle's fixed rates: a principal token (PT) is worth one unit of its market's asset at expiry and trades below it before, so holding one locks the market's implied APY. Every market Pendle's factories made on Ethereum, Arbitrum, Base and Robinhood Chain, by address, each family's maturities as a roll, and the best roll: the highest fixed rate any market whose SY takes USDC pays, new markets included as they list.

- **Import**: `uses { pendle: "^1" }`
- **Reads**: `pendle.impliedApy("ethereum:0x…", interval)`, `pendle.ptPrice("ethereum:0x…", interval)`, `pendle.ytPrice("ethereum:0x…", interval)`, `pendle.daysLeft("ethereum:0x…", interval)`, `pendle.liquidity("ethereum:0x…", interval)`, `pendle.swapFee("ethereum:0x…", interval)`, `pendle.takesUsdc("ethereum:0x…", interval)`, `pendle.syRate("ethereum:0x…", interval)`
- **Its book**: `pendle.held("ethereum:0x…")`, `pendle.heldApy("ethereum:0x…")`, `pendle.heldDaysLeft("ethereum:0x…")`, `pendle.stateAge("deployment")`
- **Actions**: `pendle.buy({ asset, usd | pct | all })`, `pendle.sell({ asset, units?, pct?, all? })`, `pendle.roll({ asset, to?, pct? })`
- **Package**: `@ftw-trade/protocol-pendle@1.0.0` (packages/protocols/pendle), with its library `import "protocols/pendle";`
- **Functions**: `fn lockedReturn(fixed: Series<number>, days: number): Series<number>`; `fn fixedBeats(fixed: Series<number>, floating: Series<number>, margin: number, bars: number): Series<bool>`; `fn rateFell(fixed: Series<number>, points: number, bars: number): Series<bool>`; `fn nearExpiry(daysLeft: Series<number>, days: number): Series<bool>`; `fn moveEarns(best: Series<number>, held: Series<number>, daysLeft: Series<number>): Series<number>`
- **Real funds**: yes
- **Backtest on**: `feed:pendle-ethereum-roll-siusd-pt`
- **More**: `get_protocol_guide { protocol: "pendle" }`, ftw-protocols/references/pendle.md

```flowscript
uses { pendle: "^1", aave: "^1" }
const fixed = pendle.impliedApy("ethereum:roll:siusd", interval);
const float = aave.supplyApy("ethereum:usdc", interval);
const spread = fixed - float;
metric("spread", spread);
when (spread > 3 && pendle.held("ethereum:roll:siusd") == 0) {
  aave.withdraw({ asset: "ethereum:usdc", all: true });
  pendle.buy({ asset: "ethereum:roll:siusd", all: true });
}
when (spread < 0.5 && pendle.held("ethereum:roll:siusd") > 0) {
  pendle.sell({ asset: "ethereum:roll:siusd", all: true });
  aave.supply({ asset: "ethereum:usdc", all: true });
}
```

## Options

### Derive (`derive`)

Its options on ETH, BTC, SOL, HYPE, XRP, ADA, ZEC, XAUT (gold), LIT, VVV, PUMP and CC: the index and the at-the-money volatility 7 and 30 days out; calls and puts bought and sold, settled at expiry; on a live project each wallet holds the same options on Derive from a subaccount of its own.

- **Import**: `uses { derive: "^1" }`
- **Reads**: `derive.index("eth", interval)`, `derive.iv7d("eth", interval)`, `derive.iv30d("eth", interval)`
- **Its book**: `derive.value()`, `derive.delta()`, `derive.positions()`
- **Actions**: `derive.buy({ type, days, strike?, usd | units })`, `derive.sell({ type, days, strike?, usd | units })`, `derive.close({})`
- **Package**: `@ftw-trade/protocol-derive@1.0.0` (packages/protocols/derive), with its library `import "protocols/derive";`
- **Functions**: `fn ivInverted(iv7: Series<number>, iv30: Series<number>, points: number): Series<bool>`; `fn ivCheap(iv30: Series<number>, floor: number, bars: number): Series<bool>`; `fn indexMoved(index: Series<number>, pct: number, bars: number): Series<bool>`
- **Markets**: `eth`, `btc`, `sol`, `hype`, `xrp`, `ada`, `zec`, `xaut`, `lit`, `vvv`, `pump`, `cc`
- **Real funds**: yes
- **Backtest on**: `feed:derive-eth-index`
- **More**: `get_protocol_guide { protocol: "derive" }`, ftw-protocols/references/derive.md

```flowscript
uses { derive: "^1" }
const iv = derive.iv30d("eth", interval);
when (iv > 70 && derive.positions() == 0) { derive.sell({ type: "put", days: 7, strike: 90, usd: 3000 }); }
```

## Routing

### Earn (`earn`)

Route an asset to its best rate instead of naming a protocol: earn.apy("usdc", interval) is the best APY across 57 lending markets and vaults ("usdc:all" adds paper-only ones, "usdc:aave,morpho" keeps those, "usd" is any dollar), and earn.rebalance moves the money when another route pays enough more. A strategy uses earn or one protocol's book, not both.

- **Import**: `uses { earn: "^1" }`
- **Reads**: `earn.apy("usdc", interval)`
- **Its book**: `earn.deposited()`, `earn.heldApy()`
- **Actions**: `earn.deposit({ usd? })`, `earn.withdraw({ usd? })`, `earn.rebalance({ minGainPct?, costUsd? })`
- **Real funds**: yes
- **Backtest on**: `feed:aave-ethereum-usdc-supply-apy`
- **More**: `get_protocol_guide { protocol: "earn" }`, ftw-protocols/references/earn.md

```flowscript
uses { earn: "^1" }
const best = earn.apy("usdc", interval);
when (earn.deposited() == 0) { earn.deposit({}); }
when (every("1d")) { earn.rebalance({ minGainPct: 1, costUsd: 5 }); }
metric("best", best);
metric("held", earn.heldApy());
```

## Indicators

### MATH (`std/math`)

Exact monetary and signed-counter arithmetic. Results are decimal text, not floats; use u256Cmp/i64Cmp for ordering and explicit display conversions.

- **Import**: `import "std/math";`
- **Functions**: `fn uint256(value)`; `fn long(value)`; `fn parseUnits(amount, decimals)`; `fn formatUnits(amount, decimals)`; `fn mulDiv(a, b, denominator)`; `fn mulDivUp(a, b, denominator)`; `fn basisPoints(amount, bps)`; `fn basisPointsUp(amount, bps)`
- **Real funds**: n/a
- **More**: `get_module { id: "std/math" }`, ftw-flowscript/SKILL.md

```flowscript
import "std/math";
```

### VWAP tools (`std/vwap`)

The reads a VWAP trader makes around the VWAP itself: where price sits in σ, the week's and the month's anchors, whether the day is balanced or trending, and a failed push into a band. The VWAPs are the prelude's: periodVwap (a session's, reset each period), anchoredVwap (from any bar) and their σ, periodVwapDev and anchoredVwapDev.

- **Import**: `import "std/vwap";`
- **Functions**: `fn weekStarts(t: Series<number>): Series<bool>`; `fn monthStarts(t: Series<number>): Series<bool>`; `fn vwapZ(price: Series<number>, vw: Series<number>, dev: Series<number>): Series<number>`; `fn vwapDrift(vw: Series<number>, dev: Series<number>, anchor: Series<bool>): Series<number>`; `fn ridingAbove(close: Series<number>, vw: Series<number>, dev: Series<number>, len: number): Series<number>`; `fn ridingBelow(close: Series<number>, vw: Series<number>, dev: Series<number>, len: number): Series<number>`; `fn failedPushUp(high: Series<number>, open: Series<number>, close: Series<number>, upper: Series<number>, within: number): Series<bool>`; `fn failedPushDown(low: Series<number>, open: Series<number>, close: Series<number>, lower: Series<number>, within: number): Series<bool>`
- **Real funds**: n/a
- **More**: `get_module { id: "std/vwap" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1" }
import "std/vwap";
const t = bars.time(market, interval);
const close = bars.close(market, interval);
const vol = bars.volume(market, interval);
const vw = periodVwap(close, vol, t, 86400);
const dev = periodVwapDev(close, vol, t, 86400);
metric("z", vwapZ(close, vw, dev));
metric("week", anchoredVwap(close, vol, weekStarts(t)));
```

## Contracts

### ERC20 (`std/erc20`)

The ERC-20 token standard (EIP-20), as an interface a program binds to a token's address: `import "std/erc20"; uses { usdc: ERC20("0x…") }`. Parameters that the standard calls `from` are named `owner` here, because `from:` is a call option; the selector is the standard's own.

- **Import**: `import "std/erc20";`
- **Functions**: `ERC20: transfer(address to, uint256 amount), approve(address spender, uint256 amount), transferFrom(address owner, address to, uint256 amount), balanceOf(address who) view returns (uint256), allowance(address owner, address spender) view returns (uint256), totalSupply() view returns (uint256), decimals() view returns (uint8)`; `fn tokenUnits(amount: number, decimals: number): number`
- **Real funds**: paper only
- **More**: `get_module { id: "std/erc20" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", token: ERC20("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") }
import "std/erc20";
metric("totalSupply", token.totalSupply());
metric("close", bars.close(market, interval));
```

### ERC4626 (`std/erc4626`)

The tokenized vault standard (EIP-4626), as an interface a program binds to a vault's address: `import "std/erc4626"; uses { vault: ERC4626("0x…") }`. A vault is also an ERC-20 for its shares; import "std/erc20" and bind the same address as an ERC20 to move shares.

- **Import**: `import "std/erc4626";`
- **Functions**: `ERC4626: deposit(uint256 assets, address receiver), mint(uint256 shares, address receiver), withdraw(uint256 assets, address receiver, address owner), redeem(uint256 shares, address receiver, address owner), asset() view returns (address), totalAssets() view returns (uint256), balanceOf(address who) view returns (uint256), convertToAssets(uint256 shares) view returns (uint256), convertToShares(uint256 assets) view returns (uint256), maxWithdraw(address owner) view returns (uint256), maxRedeem(address owner) view returns (uint256), previewDeposit(uint256 assets) view returns (uint256), previewRedeem(uint256 shares) view returns (uint256)`
- **Real funds**: paper only
- **More**: `get_module { id: "std/erc4626" }`, ftw-flowscript/SKILL.md

```flowscript
uses { bars: "^1", vault: ERC4626("0xbeef01735c132ada46aa9aa4c54623caa92a64cb") }
import "std/erc4626";
metric("totalAssets", vault.totalAssets());
metric("close", bars.close(market, interval));
```
