---
name: flash-perps
description: Integrate with Flash Tokenized Perps on Base, leveraged long/short tokens such as ETH2xL, BTC2xL, NVDA2xL and ETH2xS. Use when asked to list Flash perp markets, read a perp's NAV or price, quote or build a buy/sell transaction for a perp token (USDC or ETH in, via the Uniswap Universal Router v4), or pair a new token (for example a launchpad meme) against a perp in a Uniswap v4 pool. Read-only: it never signs or holds keys.
---

# Flash Tokenized Perps: agent integration

Flash Tokenized Perps are leveraged long and short positions packaged as plain ERC-20 tokens on Base (chain id 8453). You buy, hold and sell them like any token. This skill lets you read the markets, quote, and build unsigned transactions for a user's wallet to sign.

Everything here is read-only. Nothing in this skill signs, sends or holds keys. You get calldata; the user's wallet signs it.

**The best way to quote prices and to swap Flash assets is Uniswap**: Uniswap v4 via the Universal Router on the Flash hook pools, quoted through the Uniswap v4 Quoter (the tools below wrap it). **Support for Aerodrome is planned as a future expansion but is NOT live yet. Do not quote or route Flash trades through Aerodrome or Slipstream today.**

This is one skill of the Flash kit. Start at [flash](https://tryflash.app/agents/flash/SKILL.md); launching coins is [flash-launch](https://tryflash.app/agents/flash-launch/SKILL.md), Token Wars is [flash-token-wars](https://tryflash.app/agents/flash-token-wars/SKILL.md), FLASH and launched-token trading is [flash-trade](https://tryflash.app/agents/flash-trade/SKILL.md). The same MCP server also exposes those tools.

## Pick an interface

| Interface | URL | Use it when |
| --- | --- | --- |
| MCP server (Streamable HTTP, no auth) | `https://api.tryflash.app/mcp` | Your client speaks MCP. Best path: five perps tools cover everything below (the server has 15 more for launches, wars and trades). |
| CLI (single file, Node 18+, no deps) | `https://tryflash.app/agents/flash-perps/flash-perps.mjs` (perps only) or `https://tryflash.app/agents/flash/flash.mjs` (everything: `perps`, `perp-quote`, `perp-build`, ...) | You can run shell commands. JSON in, JSON out. |
| REST | `https://api.tryflash.app/v1/perps` | You only need market data and a raw quote. Spec: `https://tryflash.app/agents/flash-perps/openapi.json` |

Do not send an `Origin` header to the REST API unless it is `https://tryflash.app`; other origins get 403. Plain `curl` without `Origin` works. The MCP endpoint accepts any origin.

## MCP tools

All five are read-only. Amounts are decimal strings in human units. `market` is a symbol such as `ETH2xL` (case-insensitive), a perp token address, or a vault address.

| Tool | Arguments | Returns |
| --- | --- | --- |
| `list_perps` | none | Every live market: symbol, token, vault, poolId, `priceUsd`, NAV, leverage, LTV and ripcord, fees, Morpho liquidity. |
| `get_perp` | `market` | Full live snapshot of one market plus a compact summary. |
| `quote_perp` | `market`, `side` (`buy`/`sell`), `amount`, optional `asset` (`usdc` default, or `eth`), `slippageBps` | Expected output, minimum output at your slippage, venue, fees, price impact vs NAV, any refusal. |
| `build_perp_swap` | same as `quote_perp`, plus optional `from` (wallet address) | `{to, data, value}` for the swap, plus `approvals` (each `{to, data, value}`) in `executeInOrder`. |
| `perp_pairing_info` | `market` | Token, decimals, pool key, every contract address, and the steps to pair your own token against the perp. |

`side: "buy"` pays USDC (or ETH when `asset: "eth"`) and receives the perp. `side: "sell"` pays the perp and receives USDC (or ETH). `amount` is always the exact input.

## Safety facts you can rely on

- Tokens are Base-native B20 tokens (factory `0xB20f000000000000000000000000000000000000`) with 18 decimals and static balances. They do not rebase.
- Price is NAV per share. There is no transfer fee and no transfer hook, so the token behaves like a plain ERC-20 in any pool.
- Roles are fixed: only the adapter can mint or burn. There is no admin.
- A liquidation shows up as NAV loss, not as a pause. Exits are never gated.
- Fees: issuer fee 5 bps on create/redeem, pool fee 500 pips (0.05%). All fees go to the FlashBuyback (`0x43AFC33E9F876A7029DDc9F31Cf91718685f03Eb`), which buys FLASH.
- The perp is leveraged (about 2x for most markets, 3.2x for ETHMAXL). Its USD price moves with the underlying, magnified. It is not a stable unit.
- The hook owner and the rails' owner are ordinary EOAs (not a contract or multisig). All contracts are source-verified on Basescan.

## Key addresses (Base)

| Contract | Address |
| --- | --- |
| Perps hook (LeveragedLiquidityJitHook) | `0x4f573870eb41d219eA9aB42559f1207918F9F8c0` |
| Long rail | `0xac924Eff4CE25a919Df0F3341BC16036e1F66b62` |
| Short rail (ETH2xS) | `0x4A76E57C7587DF3F0D18a79967B86c8D775E8043` |
| B20 issuer | `0xaf9Cce137eF9EF8102bbF028f50c7473f3Fc3c6E` |
| FlashBuyback fee router | `0x43AFC33E9F876A7029DDc9F31Cf91718685f03Eb` |
| USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Uniswap Universal Router | `0x6fF5693b99212Da76ad316178A184AB56D299b43` |
| Permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |
| Uniswap v4 PoolManager | `0x498581fF718922c3f8e6A244956aF099B2652b2b` |
| Uniswap v4 PositionManager | `0x7C5f5A4bBd8fD63184577525326123B519429bDc` |
| Uniswap v4 Quoter | `0xdc1b201BDd8731e0274168e4EFFFEB845bDb4303` |

Perp token and vault addresses change per market; read them from `list_perps`, never hard-code them.

## Recipe 1: list markets and read NAV and price

```bash
curl -s https://api.tryflash.app/v1/perps \
  | node -e 'const j=JSON.parse(require("fs").readFileSync(0,"utf8")); for (const m of j.items) console.log(m.symbol, m.token, "price", m.priceUsd, "navPerShareUsdg", m.navPerShareUsdg)'
```

Fields that matter on each item of `items`: `symbol`, `direction` (`long`/`short`), `token`, `vault`, `poolId`, `poolKey` (`currency0` USDC, `currency1` the token, `fee` `8388608` which is the dynamic-fee flag `0x800000`, `tickSpacing` `1`, `hooks` the hook above), `navPerShareUsdg` (USDC with 6 decimals), `priceUsd`, `currentLeverageWad`, `currentLtvWad`, `ripcordLtvWad` (all 1e18 fixed point), `issuerFeeBps`, `lpFeePips`, `morpho.availableUsd`, `activity`. Skip any item where `recoveryOnly` is true.

Same thing through MCP:

```bash
curl -s https://api.tryflash.app/mcp -H 'content-type: application/json' -H 'accept: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_perps","arguments":{}}}'
```

## Recipe 2: quote

```bash
# REST: raw units. buy: amount is USDC (6 decimals). sell: amount is perp tokens (18 decimals).
curl -s "https://api.tryflash.app/v1/perps/0xB200000000000000000000FfFF60CC8138cDf452/quote?side=buy&amount=100000000"

# CLI: human units
node flash-perps.mjs quote buy ETH2xL 100
node flash-perps.mjs quote sell ETH2xL 50
node flash-perps.mjs quote buy ETH2xL 0.05 --asset eth
```

REST quote fields: `amountOut` (raw), `referenceAmountOut` (what the same size is worth at NAV), `executionImpactBps`, `poolFeeBps`, `venue`, `poolKey`. When `estimate` is `true` the `amountOut` is a conservative floor, not a point estimate. When a size is too large the response has `amountOut: "0"` and a `refusal` with the largest fillable size: re-quote smaller.

## Recipe 3: build a buy or sell transaction

The route is the one the Flash app uses: one Uniswap Universal Router `execute(commands, inputs, deadline)` call with a single `V4_SWAP` (command `0x10`), exact-input.

- USDC to perp, or perp to USDC: one hop through the perp's hook pool (`SWAP_EXACT_IN_SINGLE`, `SETTLE_ALL`, `TAKE_ALL`).
- ETH to perp, or perp to ETH: one multi-hop `native ETH -> USDC -> perp` (or reverse) through a hookless native ETH/USDC v4 pool (fee 3000 / tickSpacing 60, or fee 500 / tickSpacing 10, whichever pays more) and then the hook pool. Buying with ETH sends the ETH as `msg.value` and needs no approvals.
- ERC-20 input (USDC or the perp) needs two approvals first: `token.approve(Permit2, max)` and `Permit2.approve(token, UniversalRouter, max160, max48)`. `build_perp_swap` returns them in `approvals`; pass `from` and it returns only the ones still missing.
- The calldata embeds a 20-minute deadline. If it has passed, build again.
- `minimumOut` is the quote reduced by `slippageBps` (default 100 = 1%, max 2000). The router reverts if the output would be lower.

```bash
node flash-perps.mjs build buy ETH2xL 100 --from 0xYourWallet
# -> { "to": "0x6fF5…", "data": "0x3593564c…", "value": "0", "approvals": [ {to,data,value,step}, … ], "executeInOrder": [ "erc20-approve-permit2", "permit2-approve-router", "swap" ], "minimumOut": {…} }
```

With viem (this exact script was run against the live API; it simulates the swap with `eth_call` and a state override instead of sending):

```ts
import { createPublicClient, http } from 'viem'; // add createWalletClient to execute
import { base } from 'viem/chains';

const MCP = 'https://api.tryflash.app/mcp';
const FROM = '0xYourWallet' as const;

async function tool(name: string, args: Record<string, unknown>) {
  const res = await fetch(MCP, {
    method: 'POST',
    headers: { 'content-type': 'application/json', accept: 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } }),
  });
  const { result, error } = await res.json();
  if (error) throw new Error(error.message);
  if (result.isError) throw new Error(result.content[0].text);
  return result.structuredContent;
}

const quote = await tool('quote_perp', { market: 'ETH2xL', side: 'buy', amount: '100' });
console.log('100 USDC ->', quote.expectedOut.human, 'ETH2xL (min', quote.minimumOut.human + ')');

// An ETH buy needs no approvals. For a USDC buy, send built.approvals first (see below).
const built = await tool('build_perp_swap', { market: 'ETH2xL', side: 'buy', amount: '0.05', asset: 'eth', from: FROM });

// Simulate (read-only). The state override pretends the wallet holds 10 ETH.
const client = createPublicClient({ chain: base, transport: http('https://base-rpc.publicnode.com') });
await client.call({
  account: FROM, to: built.to, data: built.data, value: BigInt(built.value),
  stateOverride: [{ address: FROM, balance: 10n ** 19n }],
});
console.log('simulation ok; approvals needed:', built.approvals.length);

// To execute for real, use a wallet client: send each approval in order and wait for it to mine, then the swap.
//   const wallet = createWalletClient({ chain: base, transport: http(), account });
//   for (const a of built.approvals) await wallet.sendTransaction({ to: a.to, data: a.data, value: BigInt(a.value) });
//   await wallet.sendTransaction({ to: built.to, data: built.data, value: BigInt(built.value) });
```

Gotcha: `eth_call` only succeeds if the wallet really has the funds and approvals (or you override them as above); without them the router reverts with no reason. That is the wallet's state, not a bad transaction.

## Recipe 4: pair a new token against a perp (launchpads)

Call `perp_pairing_info` with the perp symbol. It returns the token, decimals, the perp's own hook pool key, every address below, and these steps.

1. Create a normal Uniswap v4 pool between your token and the perp token. Sort the two addresses ascending so `currency0 < currency1`. The Flash hook pools themselves are registrar-only, so do not try to create or reuse them; create your own pool with your own hook (or no hook, `hooks = 0x000…0`).
2. Choose `fee` and `tickSpacing`. For a hookless pool, fee 3000 / tickSpacing 60 is a safe default; use 10000 / 200 for a volatile meme.
3. Call `PoolManager.initialize(poolKey, sqrtPriceX96)` at `0x498581fF718922c3f8e6A244956aF099B2652b2b`. Work out the price in perp terms: the perp is worth `priceUsd` dollars (about NAV), so your token's price in perps is `tokenUsd / priceUsd`. Remember both tokens are 18 decimals (check yours) and that price is token1 per token0.
4. Add liquidity through the v4 PositionManager (`0x7C5f5A4bBd8fD63184577525326123B519429bDc`) after the Permit2 approvals. A single-sided range works if you only hold your token.
5. Buyers then reach your token from USDC in one transaction: Universal Router `V4_SWAP`, `SWAP_EXACT_IN`, path `USDC -> perp (Flash hook pool) -> your token`. ETH works with a native ETH/USDC hop in front. Get the whole-route quote from the v4 Quoter's `quoteExactInput`; `quote_perp` and `build_perp_swap` cover only the Flash leg.

`perp_pairing_info` returns `poolKeyTemplate` and `multiHopPathExample` objects you can fill in. Sanity check before sending: the perp pool key hooks field must equal `0x4f573870eb41d219eA9aB42559f1207918F9F8c0`.

Warn your users: liquidity paired against a leveraged token carries leveraged exposure, and a liquidation lowers the token's NAV.

## Errors and limits

- Tool failures come back as `isError: true` with a plain-language message (unknown market, bad amount, pool refusal). JSON-RPC protocol errors use standard codes.
- Rate limit: 60 requests per minute per client (a tool call counts as 3). On HTTP 429 wait for `Retry-After`.
- Data is live; quotes are valid for the current block only. Always re-quote before building, and keep `minimumOut`.
- Report problems or ask for a new market at https://tryflash.app.
