<!-- Generated by `npm run skills` in apps/web from the MCP tool definitions in apps/web/lib/mcp/. Edit that, not this file. -->

# ftw.trade MCP tools

Every tool the MCP server at `/api/mcp` lists, by area, with what it does and the arguments it takes. Each call is checked against these arguments before it runs; an argument not listed is refused by name.

## Skills

### `list_skills`

**Skills: how to use ftw.trade** (read).

The skills that teach an agent ftw.trade, in the order to read them: each one's name, what it covers, and its files (SKILL.md, then references). Read one with get_skill. New here: get_skill { name: "ftw-platform" } first.

No arguments.

### `get_skill`

**Read a skill** (read).

One skill as Markdown: { name, file? } (file is SKILL.md unless named, or one of its references such as "references/language.md"; list_skills lists them). Skills cover the platform, projects, sources, Flowscript, strategies, workflows, TypeScript scripts (@ftw/sdk), wallets and funds, this MCP, and end-to-end recipes.

Arguments:

- `name` (required): string. A skill name from list_skills, e.g. ftw-platform.
- `file`: string. SKILL.md (the default) or references/<file>.md.

## Finding tools

### `search_tools`

**Find a tool** (read).

Search every ftw.trade tool, including ones this connection does not list: { search?, area? } answers each match's name, area, title and first sentence (all tools when both are left out). Read one with get_tool, then call it with run_tool, or directly when it is listed.

Arguments:

- `search`: string. Words to look for in a tool's name, title or description, e.g. "perps deposit" or "launch".
- `area`: string. Only this area, e.g. "Perps and bridges" (areas are named in every answer).

### `get_tool`

**Read one tool** (read).

One tool's full description, its arguments as a JSON schema, and its hints (readOnlyHint, destructiveHint): { name }. Works for every tool, listed here or not.

Arguments:

- `name` (required): string. A tool name, e.g. run_perps_move (search_tools finds them).

### `run_tool`

**Call any tool** (trades or moves funds).

Call a tool this connection does not list: { name, arguments }. The call is checked and answered exactly as if it were called directly, with the same live-trading and session-key limits. Read its arguments with get_tool first. A listed tool is better called directly.

Arguments:

- `name` (required): string. The tool to call, e.g. run_perps_move.
- `arguments`: object. The tool's arguments, as get_tool gives its schema.

## Projects and launches

### `get_project_guide`

**How a project is written** (read). Also answers to `project_guide`.

How a project document is written (wallets, strategies, launches, tabs, every widget type, shortcut actions) with a working example. Read it before save_project.

No arguments.

### `list_projects`

**List projects** (read).

Your projects: id, name, mode, and counts. wallets, strategies (the strategies written in its document) and tabs come from the document; scripts/scriptsRunning are its TypeScript automations; workflows/workflowsOn (armed or running) and strategyRuns/strategyRunsRunning (list_running_strategies) are what it runs, and automations/automationsOn add those two up.

No arguments.

### `get_project`

**Get a project** (read).

One project's whole document and its version (pass the version back to save_project). spec.mode is "live" when its trades use real funds.

Arguments:

- `id` (required): string. The project id (list_projects).

### `create_project`

**Create a project** (writes).

Make a project, on paper unless its document says mode "live". Give exactly one of: { spec } a whole document (get_project_guide; spec.name is its name, and each wallet starts with its paperEth, 1 ETH when left out), e.g. { "spec": { "name": "Desk", "wallets": [{ "id": "a", "label": "A", "paperEth": 2 }, { "id": "b", "label": "B", "group": "swarm" }], "tabs": [{ "id": "main", "name": "Main", "widgets": [{ "id": "perf", "type": "performance", "wallets": "all", "view": "both" }] }] } }; { template, answers, name? } (list_project_templates; name replaces the template's own); or { name, walletCount } for a starter with that many wallets of 1 paper ETH each and an Overview tab. The answer is the project as saved, which fills in defaults (see save_project).

Arguments:

- `name`: string
- `walletCount`: integer (≥ 1, ≤ 200)
- `spec`: object. A whole project document (get_project_guide).
- `template`: "launch" | "desk" | "pair" | "orders" | "liquidity" | "hedged" | "overnight" | "blank"
- `answers`: object. Every template but launch takes pool: "chain:address" (a workspace pool; its token is read from it) or token: "0x…", and the project trades it: its chart opens on the pool and its buttons trade the token without asking. launch: { chain, launchpad, token: { name, symbol, description?, image?, website?, twitter?, telegram? }, settings?: the launchpad's settings, devBuyEth? }. pair: { pool: "chain:address" in the workspace, wallets: [{ label?, plan?: { kind: limit|breakout|market|momentum, eth, entryPct? (limit: % under the price at start; breakout: % over), entryPrice? (instead, in the pool's price), takeProfitPct?, stop?: { kind: stop|trailing, pct }, momentum?: { interval: 1m|5m|15m|1h, lookback, enterPct } }, paperEth? }] (1–10 wallets, each with its own plan as a manual workflow trading only that wallet; no plan is a wallet traded by hand; opens on a pair desk). orders: { pool: "chain:address" in the workspace, preset?: protect|ladder|breakout, wallets?: 1–10, ethEach? } (opens with a manual workflow that places the orders around the price when started). liquidity: { pool: a V3 or V4 pool, preset?: compounder|tight-wide|ladders|single, capital?: per range in the quote token, ranges?: [position fields without id, wallet or pool] instead of a preset } (a wallet per range, stopped until started). hedged: { pool: an ETH/dollar pool in the workspace, perp?: "venue:SYMBOL" (lighter:ETH by default; hyperliquid:ETH), wallets?: 1–10, usdEach?: 60+ (100 by default; less never opens the $10 least short), marginPct?: 5–80 (25), preset?: adaptive|tight|wide, interval?: 5m|15m|1h } (the Hedged LP strategy and a "Start hedged LP" workflow). overnight: { pool: a stock or ETF token's dollar pool (SPY/USDG on Robinhood), wallets?: 1–10, usdEach?: 10+ (100), preset?: gated|always|bullish (buy when the day's news mood is 40+, always, or 61+), interval?: 1m|5m } (the Overnight hold strategy, bought in the last 15 minutes of the NYSE session and sold at the next open, and a "Start overnight hold" workflow).

### `save_project`

**Save a project** (replaces or deletes).

Replace a project's document (labels, groups, wallets, strategies, launches, tabs, widgets, shortcuts, mode). Pass version from get_project: a save against an older copy is refused so you never undo the person's edit. The answer is the document as saved, with defaults filled in (labels, paperEth, wallet addresses, a shortcut's confirm and each widget's normalized fields); read it rather than assuming what was sent. Setting mode "live" makes its trades use real funds and needs a connection allowed to trade live. The save that sets it live answers nowLive when strategies started on paper are running on it: they keep running, now with real funds, at the sizes they had.

Arguments:

- `id` (required): string. The project id (list_projects).
- `spec` (required): object. The whole document (get_project_guide).
- `version`: integer

### `update_dashboard`

**Arrange a project's dashboards** (writes). Also answers to `edit_dashboard`, `edit_layout`.

Change a project's tabs and widgets in steps, saved together as one version: { id, ops, version? }. Each op is one of: { op: "add", tab, widget: { type, ...its fields, id? }, index? } (a widget as in get_project_guide; id made when left out) | { op: "update", widget: id, set: { title?, size?: s|m|l|full, height?: s|l, ...its own fields; null clears one } } | { op: "remove", widget } | { op: "move", widget, tab?, index? } (within its tab, or onto another; last there unless index) | { op: "duplicate", widget, tab? } | { op: "add_tab", name, template?: blank|overview|trading|monitor|vault, index?, group?, vault? } (overview: performance, what runs, wallets, fills; trading: the project's chart, buy and sell buttons, wallets, fills; monitor: value, P&L, realized and fills as numbers, value by wallet, what runs, memory, fills; vault: the vault named by vault (fd_…): unit value, return, money in it and holders as numbers, return and drawdown over time, money in and out, who holds it, dealings) | { op: "rename_tab", tab, name } | { op: "group_tab", tab, group } (tabs sharing a group show as one entry in the tab bar, the group's name, with their own row beneath, as a strategy's Overview, Positions, Trades, … do; group null takes it out) | { op: "move_tab", tab, index } | { op: "duplicate_tab", tab, name? } | { op: "remove_tab", tab } | { op: "set_look", set: { theme?: default|midnight|graphite|aurora|terminal|neon|paper, accent?: "#rrggbb", background?: none|grid|dots|glow|gradient, cards?: solid|glass|outline|flat|raised, density?: compact|comfortable|spacious, corners?: sharp|soft|round, font?: sans|mono|serif, palette?: ["#rrggbb", …up to 10] (every chart's colors unless the chart sets its own), title?: the header's name (60), tagline? (160), logo?, banner?, backgroundImage?: picture ids from list_dashboard_images, backgroundFit?: cover|tile|contain, css?: rules over named parts only, like "card { border: 2px dashed hotpink; } title { color: #ffd700; }" (parts: header, banner, logo, title, tagline, tabs, tab, tab-active, board, card, card-title, card-head, note, html, image, caption, link, heading, quote, table, table-head, table-cell, stat, stat-label, stat-value, page; :hover allowed; colors, backgrounds and gradients without url(), borders, shadows, fonts, text and padding; anything else is dropped); null clears one } } or { op: "set_look", look: null } (the default look). A tab is named by id or name. The steps run in order and the result is checked as a save is; a refusal names the step (ops[2].widget) and nothing is saved. The answer is the new version and every tab's widgets in order (id, type, title, size, height).

Arguments:

- `id` (required): string. The project id (list_projects).
- `ops` (required): array of object. The steps, in order (up to 100).
- `version`: integer

### `check_dashboard`

**Check how a dashboard will look** (read). Also answers to `review_dashboard`.

A critique of a project's dashboard for an agent that cannot see it, as a person opening it would find it: { id, tabs?, data? }. Reads the layout and (unless data is false) each chart's rows as a shared link draws them, and lists what to fix (a chart with no rows or a column its rows lack, a figure showing a dash, no title, the same chart twice, a % read as a plain number) and what would read better (a first tab that does not lead with the result, a wall of counters to fold into one stats card, a card stretched across a row because nothing fits beside it, mixed heights in a row, generic titles, 3D charts on the first tab, tables showing every column, too many lines or slices, no look). Each finding names its tab and widget. Also each tab's rows as they pack (widget id and its span of 4). Run it after every update_dashboard and fix until it has no fixes; the ftw-dashboards skill says how.

Arguments:

- `id` (required): string. The project id (list_projects).
- `tabs`: array of string. Tab ids or names to check; all when left out.
- `data`: boolean. false: check the layout only, without reading any rows (faster).

### `fork_project`

**Fork a project** (writes). Also answers to `clone_project`.

A new project of yours from another project's design: its dashboard, strategies, launch plans and ranges, with fresh paper wallets in the same slots: { from, ref?, name? }. from is one of your projects (ref: a branch or commit of its history, as get_project_history lists them; now when left out) or a public one (list_public_projects; as it is now). Never copies keys, addresses, balances, fills or run state, and always starts on paper. The fork remembers where it came from (forkedFrom).

Arguments:

- `from` (required): string. The project to fork: yours or a public one.
- `ref`: string. Your own project only: a branch or commit to fork from.
- `name`: string

### `copy_dashboard`

**Copy a dashboard into a project** (writes).

Copy another project's dashboard tabs into one of yours: { from, to, ref?, tabs?, replace?, version? }. from is yours (at ref if given) or public; tabs picks tab ids (all when left out); the copies go after your tabs, or in place of them with replace: true. Each widget is fitted to the wallets your project has (showing all of them when it named wallets you lack); a widget naming a strategy or launch your project lacks is left out and listed in dropped. One saved version.

Arguments:

- `from` (required): string. The project to copy from: yours or a public one.
- `to` (required): string. The project id (list_projects).
- `ref`: string. Your own source only: a branch or commit.
- `tabs`: array of string. Tab ids to copy; all when left out.
- `replace`: boolean. Replace your tabs instead of adding after them.
- `version`: integer

### `update_project_sharing`

**Make a project public to fork, or private** (writes). Also answers to `publish_project`, `share_project`.

Make a project's design public, so anyone signed in can see it in list_public_projects and fork it or copy its dashboard, or private again: { id, public }. What becomes visible is its design (name, description, dashboard, strategies' code, launch plans, ranges); its wallets' keys, addresses, balances, fills and runs never are. Ask the person first: their strategies' code becomes readable by others.

Arguments:

- `id` (required): string. The project id (list_projects).
- `public` (required): boolean

### `create_dashboard_link`

**Share a project's dashboard by link** (writes). Also answers to `share_dashboard`.

A link that opens the project's dashboard read-only, with no sign-in, in its look, with its charts' numbers (D1477): { id, access?: link|public, tabs?: [tab ids], showWallets?: boolean, backtests?: off|show|run, showCode?: boolean }. link: whoever holds it; public: also listed for anyone on ftw.trade/discover, with any vault that follows the project. Charts, notes, pictures, HTML blocks and the dashboard's branding are shown; buttons, switches and desks are not; keys and scripts never are. Wallets' addresses are hidden unless showWallets is true: then each wallet's address shows with links to the chain's explorer and DeBank (ask the person first). backtests (default run) adds a Backtest tab with each strategy's latest backtest; run also lets viewers rerun it over their own window, cash, costs and knobs (paper replays only, never kept with the owner's runs, rate limited). Strategy code is shown only with showCode: true (ask the person first). update_dashboard_link changes these later. Charts show the project's balances, fills and P&L, so ask the person first. The answer has the path to open on ftw.trade (https://ftw.trade/d/…).

Arguments:

- `id` (required): string. The project id (list_projects).
- `access`: "link" | "public"
- `tabs`: array of string
- `showWallets`: boolean
- `backtests`: "off" | "show" | "run"
- `showCode`: boolean

### `list_dashboard_links`

**List a project's dashboard links** (read).

The project's live dashboard links, newest first: { id }. Each with its access, tabs, views and path.

Arguments:

- `id` (required): string. The project id (list_projects).

### `update_dashboard_link`

**Change a dashboard link's wallet addresses or backtests** (writes).

Change a live dashboard link: { id, token, showWallets?, backtests?: off|show|run, showCode? }; what is left out stays. showWallets on lists each wallet's address with links to the chain's explorer and DeBank (off by default). backtests off: no Backtest tab; show: each strategy's latest backtest; run: viewers can also rerun it (paper only, rate limited). showCode: true shows the strategies' code with their backtests. Ask the person before turning on showWallets or showCode.

Arguments:

- `id` (required): string. The project id (list_projects).
- `token` (required): string
- `showWallets`: boolean
- `backtests`: "off" | "show" | "run"
- `showCode`: boolean

### `revoke_dashboard_link`

**Revoke a dashboard link** (replaces or deletes).

Stop a dashboard link working for everyone: { id, token } (the token is the last part of its path).

Arguments:

- `id` (required): string. The project id (list_projects).
- `token` (required): string

### `list_dashboard_images`

**List a dashboard's pictures** (read).

The pictures uploaded to a project's dashboard, newest first: { id }. Each with its id (for set_look's logo, banner and backgroundImage, an image widget's image, or <img src="img:<id>"> in an html widget), name and size. The person uploads pictures in the dashboard's Look panel or a picture widget; pictures from other sites are never shown.

Arguments:

- `id` (required): string. The project id (list_projects).

### `list_public_projects`

**List public projects** (read).

Projects their owners made public, newest first: { search?, limit? }. Each with its owner, tabs, strategy names, wallet count, how many forks it has, and where it was forked from. Fork one with fork_project or copy its dashboard with copy_dashboard.

Arguments:

- `search`: string
- `limit`: integer

### `delete_project`

**Delete a project** (replaces or deletes).

Delete a project and its document for good. Pass its name as confirmName, exactly as list_projects shows it.

Arguments:

- `id` (required): string. The project id (list_projects).
- `confirmName` (required): string. The project's name, as a check that this is the one meant.

### `list_project_templates`

**Project templates** (read). Also answers to `project_templates`.

The templates a project can start from, and for a launch every chain, its launchpads (ready or coming), what each is priced in, what to know about it, and its settings with their ranges and defaults.

No arguments.

### `get_project_balances`

**Project balances** (read). Also answers to `project_state`.

Every wallet of a project: ETH, holdings, value and P&L now, and the project total. With elsewhere: true, a live project also lists what each wallet holds on Ethereum, Base and Arbitrum (ETH for gas and USDC, what is not zero): what a withdrawal, a close-out or a bridge left there, to bring home with run_perps_move { kind: "bridge", from, to: "robinhood", token: "usdc", toToken: "eth", amount }.

Arguments:

- `id` (required): string. The project id (list_projects).
- `elsewhere`: boolean

### `list_fills`

**Project fills** (read). Also answers to `project_activity`.

A project's swap fills, newest first, optionally for some wallets or one token. Perp orders are not here: a strategy's perp sends are its run's executions (list_running_strategies), and get_log and get_project_balances show each wallet's side.

Arguments:

- `id` (required): string. The project id (list_projects).
- `wallets`: array of string
- `token`: string
- `limit`: integer (≥ 1, ≤ 500)

### `get_project_automation`

**What automates a project** (read). Also answers to `project_automation`.

Everything automating a project, with what each has made: the strategies trading its wallets, its workflows (with each step's state and outputs) and its resting orders, each with fills, ETH spent and received, tokens still held at their last price, and P&L in ETH. A workflow counts its own swaps and every order, exit and strategy it started. history: true adds each one's P&L marked every five minutes over the last week.

Arguments:

- `id` (required): string. The project id (list_projects).
- `history`: boolean

### `get_launch`

**A launch as it stands** (read). Also answers to `project_launch`.

One launch of a project as it stands: its plan, the token on its launchpad curve (progress to graduation, price, FDV, trades), which holders are the project's wallets, paper holdings, each supply step with whether its trigger holds, and its program's jobs when it has one.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string. The launch id in the project document.

### `preview_launch`

**Review a launch before it is sent** (writes).

Build and simulate a launch of the project's token, signing and sending nothing, and say where it will land: addresses { token, curve, graduationPool, poolKind, from } (the token's address, its bonding curve, and the pool its curve graduates into, as "chain:address" or "chain:id"), whether the simulation passed, and whether the creator can pay. The prediction is kept: get_launch shows it, and a workflow with trigger { kind: "launch", launch, event: "token" } gets token, pool (its curve), curve and graduationPool as trigger outputs the moment the token lands, to pass on as ${trigger.graduationPool} and so on. pump.fun from a creator wallet ftw.trade made: the coin's key is made here once, so its mint, curve and pool stay the same until it is sent (run_launch sends it). Pons and Flap from a creator wallet ftw.trade holds are sent by run_launch too, or signed by the creator's browser wallet on the launch page; Virtuals only there. Their token address can change between reviews.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string. The launch id in the project document.
- `slippageBps`: integer (≥ 1, ≤ 5000). EVM launches with a dev buy: the slippage bound on it (default 300).

### `run_launch`

**Send a reviewed launch** (trades or moves funds).

Send a launch, live, from the creator wallet ftw.trade holds: preview_launch first and pass its projectVersion. pump.fun, Pons and Flap: it is built again, simulated, refused if it would fail or the creator cannot pay, then signed and sent once (a second call never sends it again); the answer is its state (landed or sent) with token, curve, graduationPool and tx. A landed launch's token becomes the launch's, and its workflows fire on it with those addresses. Virtuals is sent from the launch page. The project must be live, and the connection allowed to trade live. To launch and trade in one go, a workflow's launch step does the same and hands the addresses to the steps after it.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `projectVersion` (required): integer. From preview_launch.
- `slippageBps`: integer (≥ 1, ≤ 5000). Pons with a dev buy: its slippage bound (default 300).

### `get_launch_book`

**Launch book status** (read).

A live Pons launch book: armed or not, runner state, timings, each workflow step’s state, and recent events. Define the workflow (apply_launch_workflow) and set the project to live before arming.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string. Launch id in the project document.

### `run_launch_book`

**Arm, launch, or pause the book** (trades or moves funds).

Control the live launch book on a Pons launch: action arm (sign, send nothing; optional slippageBps 1–5000 and kickoffAt, an epoch-ms time within seven days when the book releases the launch itself — the kickoff trigger; pausing cancels it), launch (release entry and tiers now), or pause. Requires a live project and a connection allowed to trade live.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `action` (required): "arm" | "launch" | "pause"
- `slippageBps`: integer (≥ 1, ≤ 5000)
- `kickoffAt`: integer. With arm: epoch milliseconds when the book launches itself.

### `run_launch_step`

**Run a launch step now** (writes).

Run one supply step of a launch now (or "dev-buy", the creator's dev buy), on paper: at the curve's price while the token is on its curve, at a live route once it has graduated. Launch steps do not run in live projects yet.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `step` (required): string

### `list_launch_pipeline_templates`

**Launch pipeline templates** (read). Also answers to `launch_pipeline_templates`.

Named launch pipeline templates and the canonical schema for AI-authored custom stages. Templates create a new paused post-launch run; they never sign or send the creator launch.

No arguments.

### `apply_launch_workflow`

**Define a whole launch workflow** (replaces or deletes).

Define a launch's whole workflow in one call (D1217b): the entry burst, market-cap tiers and further steps, saved as one paused document the page shows and the live Pons launch book executes end to end — entry at the launch log, then every tier rule and workflow step as its trigger and stage gate hold, next block rather than next polling round. workflow: { id (fresh, ≤20 lowercase/digits/dashes), entry: { wallets, eth, perWallet? }, bidWallets (required with tiers), circulatingSupply, tiers?: the launch-book tiers (see list_launch_pipeline_templates), steps?: [{ id?, label?, side: buy (ETH total) | sell (percent of each holding), amount, wallets, when?: a supply-step trigger (after_launch seconds, after_step step+minutes, market_cap, price_below/above, all/any; defaults to when trading opens) }], nodes?: [launch strategies (list_launch_strategies) as { strategy, id, args, stage? }, applied in order] }. Needs at least one tier, step or node. No funds are sent; arm and release with run_launch_book.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `version` (required): integer
- `workflow` (required): object. The workflow definition (see the tool description).

### `list_launch_strategies`

**Launch strategies** (read). Also answers to `list_launch_workflow_nodes`.

The catalog a launch is built from (D1235): buy, sell, delayed exit, dip buy, staged buys, take-profit ladder, DCA accumulate, counter-trade, limit ladder and launch-book tier. Each has a stable id, typed args ({ name, kind, description, optional?, default? }), the typed outputs its stage reports, and the building blocks it compiles into (supply steps and dca, limit or counter range rules) under one pipeline stage. The pipeline templates are presets made of these.

No arguments.

### `apply_launch_strategy`

**Add a launch strategy** (replaces or deletes). Also answers to `apply_launch_workflow_node`.

Add one launch strategy (list_launch_strategies) to a launch's workflow: its steps and rules under one pipeline stage, saved paused in the same document the page edits and the live Pons book executes. node: { strategy, id (fresh), args, stage? } (or the older { type, id, ...args }). Supply the current project version. No funds are sent.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `version` (required): integer
- `node` (required): object. The node (see list_launch_strategies).

### `apply_launch_pipeline_template`

**Apply a launch pipeline template** (replaces or deletes).

Replace a launch's post-launch actions with a new paused template run. Supply the current project version and a fresh options.id. Edit amounts/criteria in the saved document before resuming. No funds are sent.

Arguments:

- `id` (required): string. The project id (list_projects).
- `launch` (required): string
- `version` (required): integer
- `template` (required): "entry-exit" | "entry-dca" | "entry-limits" | "two-group-buys" | "launch-book"
- `options` (required): object

## Trading and funds

### `place_order`

**Trade now** (trades or moves funds).

Buy or sell a token now across a project's wallets, as the chart's order ticket does: { side, token, amount, unit?, amountIs?, wallets, pick?, split? }. A buy's amount is ETH (unit eth) or a percent of each wallet's ETH (unit percent, 100 = all it holds less gas); a sell's is a percent of each holding (percent) or whole tokens (token). amountIs total shares the amount over the wallets, each gives it to every wallet. One leg per wallet; a leg a wallet cannot pay for is refused without dropping the others. Paper or live as the project is (get_project spec.mode); the answer says which.

Arguments:

- `id` (required): string. The project id (list_projects).
- `side` (required): "buy" | "sell"
- `token` (required): string. The token address (0x…, or a base58 mint).
- `amount` (required): string. Decimal string.
- `unit`: "eth" | "percent" | "token"
- `amountIs`: "total" | "each"
- `wallets` (required): any. "all", or { wallets?: [wallet ids], groups?: [group names] }
- `pick`: integer (≥ 1). Trade from this many of the chosen wallets, picked at random.
- `split`: "even" | "random"

### `press_shortcut`

**Press a shortcut** (trades or moves funds).

Press one of a project's shortcut buttons with its inputs, as the person would: one leg per wallet at live quotes. Returns each leg and the seed of its random draw. Paper or live as the project is (get_project spec.mode); the answer says which.

Arguments:

- `id` (required): string. The project id (list_projects).
- `widget` (required): string. The shortcut widget id.
- `values`: object. The shortcut's inputs by name.

### `place_resting_order`

**Place a resting order** (trades or moves funds). Also answers to `place_limit_order`.

Rest an order on a pool in the workspace over a project's wallets, kept and run by ftw.trade (no browser needed). kind: limit (buy at or below each target, sell at or above; levels make a ladder, buy targets falling, sell targets rising; protected by slippageBps), stop (buy at or above, sell at or below: a stop loss, unprotected) or trailing (trailPct below the highest since it armed for a sell, above the lowest for a buy; optional activation value first). metric: price (the pool's price as shown, quote per token) or mcap_usd (with supply, declared whole circulating tokens). Amounts: a buy's amount is ETH shared across the chosen wallets (split even or random, or pick N of them); a sell is unit token (shared) or percent (of each wallet's holding). maxTotal caps the whole order across fills and maxPerWallet caps each wallet (ETH for buys, tokens for sells). startDelaySeconds and durationMinutes bound when it may fire. exits on a buy ({ takeProfitPct?, stopLossPct?, trailPct?, sellPct? }) open as one-cancels-the-other sells on the wallets that bought, from the entry price, after its first fill. Each exit sells at most what its entry bought in each wallet; a sale from elsewhere (another order, a workflow, a strategy) caps other entries' exits at what the wallet still holds, oldest entries first, and cancels those left nothing. Each level fires once; failures are reported, not retried. A limit whose target is already through the pool's last price (a buy at or above it, a sell at or below) is placed with a warning, since it fills on the next swap. Paper or live as the project is (get_project spec.mode); the answer says which.

Arguments:

- `id` (required): string. The project id (list_projects).
- `pool` (required): string. chain:address of a pool in the workspace (list_pools).
- `name`: string
- `side` (required): "buy" | "sell"
- `kind`: "limit" | "stop" | "trailing"
- `metric`: "price" | "mcap_usd"
- `supply`: number. Whole tokens in circulation; required for mcap_usd.
- `target`: number
- `amount`: string. Decimal string: ETH for a buy, tokens or percent for a sell (see unit).
- `levels`: array of object
- `trailPct`: number
- `activation`: number
- `unit`: "eth" | "token" | "percent"
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }
- `pick`: integer (≥ 1)
- `split`: "even" | "random"
- `maxPerWallet`: string
- `maxTotal`: string
- `slippageBps`: integer (≥ 0)
- `startDelaySeconds`: integer (≥ 0)
- `durationMinutes`: number (> 0)
- `exits`: object

### `list_resting_orders`

**Resting orders** (read). Also answers to `limit_orders`.

A project's resting orders, newest first, optionally on one pool or in one state: each one's levels, what fired, fills per wallet, what it has used of its limits, its status, and its exits (orders whose parent is it).

Arguments:

- `id` (required): string. The project id (list_projects).
- `pool`: string
- `state`: "open" | "done" | "cancelled"

### `update_resting_order`

**Pause, resume or cancel a resting order** (writes). Also answers to `change_limit_order`.

Pause, resume, rename or cancel one resting order ({ id, order, paused?, name?, cancel? }). A changed plan is a new order. Cancelling an entry leaves its open exits in place. Resuming one in a live project needs a connection allowed to trade live.

Arguments:

- `id` (required): string. The project id (list_projects).
- `order` (required): string
- `paused`: boolean
- `name`: string
- `cancel`: boolean

### `list_live_trades`

**Live trades** (read).

A live project's trades on chain, newest first: every send, its transaction and what it moved, and every leg refused before one.

Arguments:

- `id` (required): string. The project id (list_projects).
- `limit`: integer (≥ 1, ≤ 500)

### `list_wallets`

**Account wallets** (read).

The account's wallets: its labeled Robinhood indexer wallets, and its live wallets (keys ftw.trade holds) with each one's ETH on robinhood. A project wallet trades live from the live wallet whose address it carries.

No arguments.

### `get_main_wallet`

**Main wallet** (read). Also answers to `main_wallet`.

The account's main wallets (EVM and Solana), its paper main balance on each chain, its latest transfers to and from project wallets, and `live`: the real ETH the main wallet holds on Robinhood Chain (what a deposit lands in and payments spend).

No arguments.

### `deposit_paper`

**Add paper ETH** (writes).

Add play ETH to the paper main balance on a chain, to fund project wallets from. Paper only.

Arguments:

- `chain` (required): string
- `amount` (required): number (> 0)

### `transfer_funds`

**Move funds** (trades or moves funds).

Fund a project's wallets from the main wallet (direction "fund") or send funds back to it ("return"), by direct transfer or through husher (lands after delayMinutes, 0 to 120), on a chain, with an amount of ETH per wallet. In a live project only "return" moves real funds: by direct transfer each wallet sends ETH with its own key to the account's main wallet, or to another of the account's live wallets named in to (D1261); through husher it is a private send on robinhood, ethereum or base (D1340): each wallet pays a Husher funding plan that delivers the amount from Husher's pool to the main wallet, the account wallet named in to, or a withdrawal address of the account named in to (one added on the website's Withdraw page and past its cooling-off), after delayMinutes, so nothing on chain links the two (the wallet pays the amount plus Husher's fee; list_transfers shows each leg ordered, paid, landed or failed); funding a live wallet is a send to its address from outside, or a return from another live wallet. A project on Solana moves SOL on solana (chain may be left out): on paper to and from the main balance, direct or through husher; live, a return sends SOL from each wallet's Solana address to `to`, which it must name: the Solana address of another of the account's wallets made here (the main wallet has no Solana address yet; a withdrawal sends anywhere else). A call that times out may still have moved the funds: read list_transfers before sending it again.

Arguments:

- `id` (required): string. The project id (list_projects).
- `direction` (required): "fund" | "return"
- `route` (required): "direct" | "husher"
- `chain`: string. Where the funds move: an EVM chain for a project whose wallets hold ETH (robinhood when left out), solana for a project on Solana (the default there).
- `legs` (required): array of object
- `delayMinutes`: number (≥ 0, ≤ 120)
- `to`: string. Live returns only: the address to send to, the main wallet's (the default; on solana, the Solana address of another of the account's wallets, which must be named), another of the account's live wallets, or the billing address (get_rpc_credits payTo) to buy credits, credited automatically within a few minutes.

### `transfer_privately`

**Fund wallets privately** (trades or moves funds).

Fund many of a project's wallets from one of them through Husher, so nothing on chain links the funding wallet to the wallets it funds, nor those to each other: amountEach ETH to every wallet the selector names (all but from by default), landing after delayMinutes (0 to 120, 10 by default). Live, on the project's chain where Husher funds wallets (robinhood, ethereum or base; robinhood otherwise): a Husher plan per wallet, each invoice paid by from (the amount plus Husher's fee), and every wallet funded must be a live account wallet; wallets that could not be ordered come back in refused. Paper: from's paper ETH (a Solana project's SOL) leaves at once and lands in each wallet after the delay. list_transfers shows each leg. A call that times out may still have ordered: read list_transfers before sending it again.

Arguments:

- `id` (required): string. The project id (list_projects).
- `from` (required): string. The wallet that pays.
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }
- `amountEach` (required): number (> 0)
- `delayMinutes`: number (≥ 0, ≤ 120)

### `list_transfers`

**Project transfers** (read). Also answers to `project_transfers`.

A project's transfers with the main wallet, newest first: each one's route, legs, and whether it has landed.

Arguments:

- `id` (required): string. The project id (list_projects).

## Workflows and snipers

### `get_memory`

**Project memory** (read).

A project's memory: named values that last across runs, each with when it last changed and who changed it (you, mcp, or the workflow whose remember step kept it). Workflows read them as "${memory.name}" in any field, strategy and flowscript steps through their args.

Arguments:

- `id` (required): string. The project id (list_projects).

### `update_memory`

**Change project memory** (writes).

Set or delete values in a project's memory: set is { name: value } (a number, text up to 500 characters, or true/false; null deletes it). A name is 1 to 32 lowercase letters, digits or _, starting with a letter; a project keeps up to 100. A workflow reads the new value from the next step it starts.

Arguments:

- `id` (required): string. The project id (list_projects).
- `set` (required): object. name: value, or null to delete it.

### `save_workflow`

**Write a workflow** (trades or moves funds).

Make or change a workflow on a project: a trigger, then up to 20 named steps on its wallets, paper or live as the project is (the answer says which). It runs on the server by itself once armed. trigger: { kind: "manual", pool?, token? } (started with control_workflow), { kind: "pool", pool: "chain:address", metric?: price|mcap_usd, op: above|below, value } (fires once the pool trades across value after it is armed), { kind: "launch", launch: <launch id>, event: token|graduated } (one of the project's launches: its token is on chain, or it graduated and its pool is indexed, which happens by itself; outputs also launchpad, curve and graduationPool, the pool its curve graduates into, known the moment the token lands so a later step can take pool: "${trigger.graduationPool}"; preview_launch says all three before the launch is sent), or a sniper: { kind: "launches", launchpad: pons|flap|pumpfun, filter: { symbols?: tickers matched whole, contains?: text in the name or ticker, excludes?, devBuyEth?: { min?, max? }, deployer?: { only?, skip?: wallets, txs?: { min?, max? }, eth?: { min?, max? }, minAgeDays?, launches?: { min?, max? } earlier launches, minGraduated? } }, budget: { perLaunchEth, totalEth, maxLaunches? } (in SOL on pumpfun, which is for a project on Solana and buys through Jupiter), fees?: { maxPriorityLamports?, jitoTipLamports? } (pumpfun only: a live buy's priority fee cap, or a Jito tip sent to the block engine), breaker?: { maxDrawdownEth?, maxDrawdownPct? of totalEth }, from?, until?, hoursUtc?: [start, end) } (anyone's launch that matches, seen after it is armed and within 10 minutes of its block: each gets a run of the steps on its bonding curve; every buy is cut to what is left of the budget; once P&L falls the breaker's amount from its best it stops buying and says why; its steps buy with swap and may sell with order, wait and watch; outputs also symbol, name, deployer, devBuyEth, launchedAt, launchpad, curve, graduationPool, tx; see list_launches, list_sniper_runs, backtest_sniper), or { kind: "timer", every: "1m".."7d" } (runs every period until stopped: the account's timer source every-<period>, made and deployed the first time, then { kind: "signal", signal: "every-1h", repeat: true }; one run at a time, a tick during a run is skipped, each run is kept in the workflow's history (run.history: when, ok, what it did, which wallets traded) and three failed runs in a row stop it; a timer gives no pool, so its swaps name pool or pools), or { kind: "signal", signal, where?: { field: value or { gt, gte, lt, lte, ne, in, nin } }, pool?: <field>, repeat?: true, whileBusy?: skip|each|latest|batch } (the next signal of that name a source publishes after it is armed whose fields hold where, e.g. { spread: { gt: 20 } } so the threshold lives in the workflow and not in the source, or with repeat every one, as a timer's are, list_signal_types; with repeat, whileBusy says what it does with signals that came while a run was going: skip them (the default), run on each in turn, run once on the newest (trigger.skipped says how many it stood for), or run once on them all as a batch (the newest's fields, trigger.events how many); its fields are trigger outputs, e.g. "${trigger.followers}", and its token and pool fields, or the workspace pool of its token, are the run's; pool names the field that holds the pool to trade, an address or chain:address, e.g. "ret_pool" from a call source: a pool new to the workspace is indexed from its first block before the run starts, so a workflow on a factory's createToken call trades the pool it just made, a block later), or { kind: "metric", strategy: <id or name>, metric, op: above|below, value, series?, repeat?: true } (a metric a running strategy publishes, list_strategies: fires on the first bar that closes after it is armed with the metric on that side of value, on any running deployment of the strategy or only the one on series; with repeat it arms again after each run and fires on the next crossing, once the metric has been back on the other side, so a breaker trips once per spike, not on every bar of it; backtest_strategy's metricStats shows where a metric usually sits; the run trades that deployment's pool, and outputs also value, deployment and series, e.g. a swap sized "${trigger.value * 0.01}"). supply (whole tokens in circulation) gives market caps. steps: each { id, after?, when?, kind, … }; a step starts once every step in after is done (no after: the step before it; after: [] starts with the trigger), so steps after the same one run together. when: { value, gt|gte|lt|lte|eq|ne } runs the step only if it holds when the step would start, e.g. an alert action with when: { value: "${pnl.value}", lt: -20 }; otherwise it and the steps after it are skipped and the run ends ok. A memory key nothing has set reads as unset there: ne holds and the rest do not. Kinds: { kind: "swap", side: buy|sell|either, amount, unit?, wallets, pick?, split?, slippageBps?, exits?, pool?, pools? } (a market trade now; pick: 1 trades one of the wallets at random; side either flips a coin each run, with amount a percent (of ETH to buy, of the token to sell; a sell that finds nothing to sell buys); pools: [chain:address, …] trades one of them, picked at random each run; a buy's exits: { takeProfits?: [{ pct, sellPct }] rising, each selling sellPct of what is left, or takeProfitPct, stopLossPct?, trailPct? } rest as orders on the wallets that bought, and the stops stay until the ladder sells out; outputs eth, tokens, usd (what eth is worth, to size a hedge: usd: "${spot.usd / spot.wallets}"), price (ETH per token), poolPrice, mcapUsd, wallets), { kind: "order", order: <place_resting_order fields without id or pool>, waitFill?: true } (rests an order on the run's pool; done at its first fill unless waitFill is false; outputs id, price, eth, tokens, mcapUsd), { kind: "wait", minutes }, { kind: "watch", metric?: price|mcap_usd, op, value } (until the pool crosses value; outputs price, mcapUsd), { kind: "watch", metric: "graduated" } (after a launch trigger with event token, whose run trades the bonding curve: waits until the launch graduates, then moves the run to its pool, so later orders and strategies trade the pool; outputs pool, price, mcapUsd), { kind: "strategy", strategy: <id>, interval?, args?, ethPerBuy, wallets, until?, outputs?, keep? } (runs a strategy on the pool trading those wallets; outputs id. In place of ethPerBuy, cash (with margin? and leverage?) sizes a strategy over more than one market, which needs no pool or token when it reads none: a lending market, a vault or a Pendle market, e.g. args: { pt: "${trigger.pendle}" } from a source on build's protocols stream (where: { protocol: "pendle", kind: "CreateNewMarket" }), to roll into each new market. With until: <metric> it is a stage: the steps after it wait until the strategy publishes that metric non-zero, then its run stops (unless keep: true) and it outputs id, fills, equity and each metric in outputs at the bar it ended, so a later stage's args or an order's target read "${acc.entry}"), { kind: "flowscript", strategy: <id>, interval?, series?, bars?, args?, outputs?: [metric names] } (runs a saved strategy once over the latest bars of the pool, or of series, and outputs each metric it publishes at the last bar as a number, every one unless outputs names some: a later step reads "${score.signal}"), { kind: "pool", version?: v4|v3, token?: address (the trigger's token unless named), quote?: "eth"|address, price: quote per whole token, fee?: pips (3000; V3 takes 100, 500, 3000 or 10000), tickSpacing?, hooks? (V4), wallets: the one wallet that makes it, use?: true } (makes a Uniswap pool on robinhood at that price, or finds the one already there; outputs pool (chain:address, or chain:id on V4), address (V3), id (the V4 pool id, a hash), token, token0, token1, fee, tickSpacing, tx, created; with use the steps after it trade that pool and its token, in the same round it lands). { kind: "launch", launch: <launch id>, slippageBps?, use?: true } (sends one of the project's launches, live, from its creator wallet, whose key ftw.trade must hold: Pons or Flap, or pump.fun; a launch that already has its token sends nothing; outputs token, pool (its curve), curve, graduationPool, launchpad, tx, already; with use the steps after it trade the new token on its curve, in the same round it lands, so one workflow can launch, buy and rest exits; preview_launch says where it will land first). { kind: "index", chain, address?: a pool address or output ("${trigger.ret_pool}"), predict?: { version: "v2", factory, tokenA, tokenB, initCodeHash } | { version: "v3", factory, tokenA, tokenB, fee, initCodeHash } | { version: "create2", deployer, salt, initCodeHash } (its CREATE2 address before it exists; tokens may be outputs), minutes?: 10, use?: true } (indexes the pool into the workspace the moment it has code, waiting up to minutes for it to be deployed, so a pool that is about to be made is read from its first block; outputs pool, address, chain, token, predicted, waited (seconds); with use the steps after it trade it). Any swap, order, watch, strategy or flowscript step may name its own pool, e.g. pool: "${deploy.pool}" or a watch's "${moon.pool}", to trade a pool an earlier step made or found. Outputs are typed (number, text, boolean, address, chain, pool, hash, url, json): a field that is one output takes only its kind (a pool where a pool goes, an address for a token, a number in an amount), arithmetic reads numbers only, and both are checked when it is saved. Perps and fund moves, which need no pool (D1176): { kind: "perp", venue, network?, market, side: long|short|close|target, usd?, leverage?, wallets } (target: usd is the position each wallet ends at, signed, below 0 short, 0 flat, so a rebalance trades only the difference from what it holds and sends nothing under $10) (outputs size (signed), price, usd, wallets), { kind: "deposit", venue, network?, amount, from?, token?, wallets }, { kind: "withdraw", venue, network?, amount|"all", wallets } and { kind: "bridge", from, to, token, toToken?, amount, wallets } (from or to solana uses the wallet's Solana address, token sol or usdc there) (each outputs amount, wallets; a deposit or bridge through Relay outputs what arrives); fields as run_perps_move takes them. An alert or any outside call: { kind: "action", name: <an action deployed on the project, list_automations>, args?: { field: value or "${step.output}" as text }, onFail?: stop|continue } (calls it and waits for its answer, as run_action does: e.g. a telegram-post action with text "bought at ${buy.price}"; a failed call fails the step, or with onFail continue ends it with ok false for a later step's when; outputs ok). Memory, which lasts across runs: { kind: "remember", key, value, op?: set|add } keeps value under key in the project's memory (set; a number, text or true/false, or an output such as "${buy.price}"), or adds a number to the one there, 0 when unset (add; outputs value, the total). { kind: "emit", stream, fields: { name: value | { kind, value } } } writes an event to one of this workflow's outputs, a source anything in the account subscribes to (a signal trigger, a strategy's sources.<name>() tape); stream is its name, like "my-flow.entry", one writer per stream; each field is a number, text, true/false or an output such as "${swap.price}", typed by kind (number, text, boolean, address, pool, hash, url; by the value when left out), and every event also carries market (the run's pool) and bar (unix seconds); outputs id. Put one before a swap to tell listeners what is about to be sent. { kind: "poll", source } runs one of the account's sources now and waits for it: a Flowscript source polling a URL (from: { poll }, with or without every) or a script source, named by the stream it publishes; the steps after it read what it just published (a signal trigger on it, or a strategy's tape); outputs ran (false when it was polled less than a second ago or a run of it was going) and published. { kind: "husher", op?: fanout|send, from?: wallet (a fanout pays from it), wallets, amount: ETH each, to?: an account wallet address (a send; the main wallet by default), delayMinutes?: 0..120, timeoutMinutes?: 1..10080 (180), onFail?: stop|continue } moves ETH privately through Husher on Robinhood Chain (paper or live as the project is) and waits until it lands; { kind: "buyWallets", count: 1..25, maxEthEach, role: the category they go in, fundEach?: ETH each, minAgeDays?, minTxCount?, tiers?, timeoutMinutes?: (1440), onFail? } buys marketplace wallets into the project (paper: made at once; live: an invoice the account approves on Wallets, the step waiting until then, its order id fixed by the run so a retry never buys twice), and later steps pick them with { groups: [role] }; bought wallets also join their project and role by themselves once the purchase completes, whoever made it. { kind: "group", wallets, add?: role, remove?: role or "*" (every other), pick?: n at random } moves wallets between roles at once, e.g. retire a spent buyer (outputs wallets, labels). Both output status: pending, done, partial, failed, refunded or expired (plus landed, failed, amount, error / wallets, labels, order), never throw; a failure stops the run unless onFail is continue, when the step ends and a later step's when reads it ({ value: "${fund.status}", eq: "done" }). { kind: "cancel", all?: true, names?: [order names], pool? } cancels the project's open resting orders on the run's pool (or the step's; every pool with all), only those named when names is given, e.g. a swarm of buy orders pulled by a metric trigger when a strategy's volatility spikes; outputs cancelled. A breaker: { kind: "portfolio" } reads the account's book the project trades in (paper or live) at its last 15-minute mark and outputs valueUsd, pnlUsd (net of deposits), dayPnlUsd and dayPct (the last 24 hours', net of deposits) and book; { kind: "stop", strategies?: [ids or names, or run ids] } stops the project's running strategies, or only those named, leaving their positions as they are; outputs stopped. E.g. a timer every 15m: a portfolio step pf, then a stop step with when: { value: "${pf.dayPnlUsd}", lt: -50 }. Any field reads it back as "${memory.key}", in this run or any later one, and in any of the project's workflows, e.g. a repeating workflow that adds 1 to runs and stops buying once "${memory.runs}" is high; get_memory and update_memory read and change it, as the memory table on the dashboard does. For example spot hedged on a perp: a swap buy, a bridge of USDC to arbitrum, a deposit to lighter with amount "${move.amount}", then a perp short. The trigger outputs token, pool, price, mcapUsd. Any field may read outputs of the trigger or of steps it comes after as "${entry.mcapUsd * 3}" (numbers, + - * / ( ), min, max, and random(low, high), drawn when the step starts, as amount: "${random(0.001, 0.005)}"): e.g. sell targets at multiples of the entry's market cap, or a strategy argument from a fill price. It fires once, unless its trigger repeats (a timer, or a signal with repeat); arm it again to rerun. A step that fails ends the run and says why. A new workflow is armed unless armed is false; give workflow to change an existing one that is not running: it stays armed or not as it was, and armed: true arms one that was done, failed or stopped. An armed signal trigger that no source of the account publishes yet is saved with a warning that says so.

Arguments:

- `id` (required): string. The project id (list_projects).
- `name` (required): string
- `workflow`: string. An existing workflow to change (list_workflows).
- `supply`: number (> 0)
- `trigger` (required): object
- `steps` (required): array of object
- `armed`: boolean

### `list_workflows`

**Workflows** (read). Also answers to `workflows`.

A project's workflows: each one's trigger, steps, state (armed, running, done, failed, stopped), each step's state and outputs, and its log of what each step did. A workflow armed on a signal also has heard: whether its source exists, when it last had an event (null lastAt: never), and its state when it is halted or stopped, so a trigger that will never fire reads apart from one about to. One armed on a strategy metric has now: the metric's value at the last bar on each running run of the strategy (empty: nothing runs it, so it cannot fire).

Arguments:

- `id` (required): string. The project id (list_projects).

### `control_workflow`

**Arm, start, stop or delete a workflow** (replaces or deletes).

Arm a workflow (wait for its trigger again from now), start it at once, stop it before its next step, or delete it: { id, workflow, action: arm|start|stop|delete }. Orders and strategies it started keep running; change them on their own.

Arguments:

- `id` (required): string. The project id (list_projects).
- `workflow` (required): string
- `action` (required): "arm" | "start" | "stop" | "delete"

### `list_launches`

**Launches on a launchpad** (read). Also answers to `launches`.

Everyone's newest launches on a launchpad, as ftw.trade reads them from the launchpads' own logs: token, name, ticker, deployer, dev buy in ETH, the deployer's transactions and ETH when seen, earlier launches and graduations, when it launched and whether it graduated. With filter (a sniper's filter, as save_workflow takes it), each says whether it matches and why not. status: how far back the feed reaches. Clanker and Zora (Base) and Virtuals (Robinhood) answer from their factories' creation events as the protocols stream keeps them (token, name, ticker, deployer, what it pairs with, its pool, when; Virtuals names no ticker); a sniper's filter and the Launches feed do not cover them yet, and a source on { stream: "protocols", where: { protocol: "clanker", deployment: "base", kind: "TokenCreated" } } follows them live.

Arguments:

- `launchpad`: "pons" | "flap" | "pumpfun" | "letsbonk" | "clanker" | "zora" | "virtuals"
- `limit`: integer (≥ 1, ≤ 200)
- `filter`: object

### `list_sniper_runs`

**A sniper's runs** (read). Also answers to `sniper_runs`.

A sniper's runs, one per launch it bought, newest first: the launch (token, ticker, deployer, dev buy), each step's state and outputs, and its log. The sniper itself (list_workflows) has the launches it looked at and why each was or was not bought, its spend, P&L and drawdown from its best.

Arguments:

- `id` (required): string. The project id (list_projects).
- `workflow` (required): string. the sniper

### `backtest_sniper`

**Backtest a sniper on past launches** (read).

Try a sniper on past launches before arming it: give a saved one as workflow, or { name, trigger, steps } as save_workflow takes them. Every launch the feed holds from the last days (1–14, default 7) is matched by its filter as it stood then, bought at its curve's first minute after entryDelaySec (default 2) at the bar's open plus slippageBps (default 500) and feeBps (default 100) each way, and taken out by its first buy's exits on one-minute bars, selling what is left after maxHoldMinutes (default 1440). The budget and breaker apply in launch order. Returns each trade, totals, win rate, worst drawdown, where the breaker tripped, which filters could not be checked for past launches, and notes on what it could not know.

Arguments:

- `id` (required): string. The project id (list_projects).
- `workflow`: string
- `name`: string
- `supply`: number (> 0)
- `trigger`: object
- `steps`: array of object
- `days`: number
- `entryDelaySec`: number
- `slippageBps`: number
- `feeBps`: number
- `maxHoldMinutes`: number

## Strategies

### `get_strategy_guide`

**How a strategy is written** (read). Also answers to `strategy_guide`.

How to write a strategy that does what it means: its shape, what it reads, how orders are sized, when and while, na, the workflow from save to run, worked examples that each save and backtest, and the pitfalls that make a strategy overspend or never trade. Read it before save_strategy. { part? } reads one part (shape, reads, orders, examples, pitfalls, …); the DeFi protocols' reads and actions are the part "protocols" (or get_protocol_guide for one).

Arguments:

- `part`: "summary" | "shape" | "reads" | "orders" | "triggers" | "language" | "indicators" | "more" | "workflow" | "testing" | "examples" | "pitfalls" | "protocols". One part of the guide; all of it but the protocols when left out.

### `list_strategies`

**Strategies** (read).

The account's strategies (Flowscript with arguments) and the starters to begin from. A strategy reads bars.open/high/low/close/volume(market, interval) and trades with paper.buy({ quote }) and paper.sell({}); market, interval and each argument are constants it can name. On a perps market (perp:<venue>:<symbol>) it also reads funding.rate(market, interval), hourly as a fraction, the order book (D1337, D1495: book.bid/ask(market, interval), book.bidSize/askSize(…), book.mid(…), book.micro(…) the microprice, book.spread(…) in bps, levels 2–5 as book.bid2(…)…book.askSize5(…), book.bidDepth/askDepth(…) USD on the top 20 levels and book.imbalance(…), book.bids50/asks50(…) USD resting within 0.5% of the middle (10, 25 and 100 too) and book.imbalance50(…) from -1 to +1, book.age(…) seconds; recorded every 15 s, na before recording began; uses book: "^1"; a Polymarket outcome, poly:<token id>, reads the same), and trades perp.long({ usd }), perp.short({ usd }) and perp.close({}) on margin, or rests maker orders with perp.bid({ usd, at }), perp.ask({ usd, at }) and perp.cancel({}) (D1497: in a backtest or a paper run filled at at, at the maker fee, once a later bar trades through; a run on a project's wallets keeps them resting on the venue as post-only orders, see run_strategy); write `uses { bars: "^1", funding: "^1", perp: "^1" }` first. Over more than one market (D1205) it reads a pool, a perp and a shared feed side by side, one of each, by key: pool.open/high/low/close/volume("chain:0x…", interval), perp.close("venue:SYMBOL", interval) and perp.funding(…), feed.close("feed-name", interval), and bars.time(market, interval) for hour(), minute() and dayofweek(); it holds a range on the pool with lp.range({ widthPct, usd? }) (±widthPct around the price; opens, or moves it) and lp.close({}), hedges with perp.hedge({ ratio, minUsd? }) (short ratio times what the range holds) or perp.target({ usd, minUsd? }) (a signed notional), and moves funds with funds.toPerp({ usd }) and funds.toWallet({ usd }); it reads its books with lp.inRange(), lp.widthPct(), lp.baseUsd(), lp.value(), lp.fees(), perp.sizeUsd(), perp.equity() and wallet.cash(). It acts on protocols read from their contracts (D1522), aave, spark, compound, morpho, euler, yearn, fluid, gearbox, spark_savings, sky, ethena, maple, uniswap, kyberswap, cow, aerodrome, pendle, marginfi, kamino, kamino_earn, jupiter_lend, jito, pons, pumpfun, pumpswap, launchlab, virtuals, zora, clanker, flap and metric_omm, each named by its id, with its own reads and actions on any market its deployment lists (get_protocol_guide names them): a market read takes its market and an interval (aave.supplyApy("base:usdc", interval)), an account read takes its market or deployment (aave.supplied("base:usdc"), aave.healthFactor("base")), and an action names its market as asset (aave.supply({ asset: "base:usdc", usd: 100 }), with units, pct or all in place of usd). It trades options on derive (D1356): <id>.index("eth", interval), <id>.iv7d(…) and <id>.iv30d(…) read the underlying, <id>.buy/sell({ type: "call" | "put", days, strike?, usd | units }) and <id>.close({}) trade calls and puts, and <id>.value(), <id>.delta() and <id>.positions() read them back. Or it routes by asset with the earn intents (D1343): earn.apy("usdc", interval) is the best rate across those markets, earn.deposit/withdraw({ usd? }) and earn.rebalance({ minGainPct?, costUsd? }) move the money to it, earn.deposited() and earn.heldApy() read it back. It trades tokens on swap venues (D1501): jupiter.price("solana:sol", interval) and jupiter.impact(…) read a token, jupiter.buy/sell({ usd? | pct?, slippageBps?, maxImpactPct?, dexes?, direct? }) swap it, jupiter.limitBuy/limitSell({ usd? | pct?, at, expiresHours? }) rest limit orders, jupiter.dca({ usd?, orders, everyMinutes, minPrice?, maxPrice? }) runs a DCA, jupiter.cancel({ orders? }) takes them back, and jupiter.held(), jupiter.units(), jupiter.avgPrice(), jupiter.locked() and jupiter.openOrders() read the book. It moves funds through Husher with husher.toPerp({ usd, delayMinutes? }) and husher.toWallet({ usd, delayMinutes? }), landing delayMinutes later (husher.inFlight() until then). A move that cannot be done reverts with why; it never stops the run. With account { project, wallets? } on a live project each wallet holds what the books hold on a protocol's live markets, its USDC bridged there from Robinhood Chain through Relay and home again on control_strategy_close_out; Husher moves stay on the paper books. Over one market an order's size (quote, pct, usd) is a number or an argument as written; over more than one, a number an action takes may also be an expression the program publishes with metric(name, expr). It reads the account's typed sources (D1233) as tapes, sources.<name>() with the name's - and . written _, counted or summed per window: count(window(sources.kol_calls(), "15m")). Read get_strategy_guide first. It is stepped bar by bar, so every(), cooldown() and time of day work. The starter 'Hedged range, width by session and volatility' is one: an NVDA/USDG range on Robinhood hedged on Lighter's NVDA perp. Each saved strategy comes with uses: the sources it reads, the metrics it publishes with their latest value, its runs and how many run, and the workflows that stage it.

No arguments.

### `save_strategy`

**Write a strategy** (writes).

Make a strategy ({ name, description?, source, params?: [{ name, label?, kind: number|text|bool, default, min?, max? }], tests? } or { starter }), or change one ({ id, ... }). The source must compile; problems come back by line. Saving runs nothing but its tests (see test_strategy), whose report comes back as testReport; a failing test does not stop a save, but run_strategy refuses a strategy whose tests fail.

Arguments:

- `id`: string. An existing strategy to change; leave out to make one.
- `starter`: "EMA crossover" | "RSI reversion" | "Breakout" | "Momentum" | "ML forecast" | "Bollinger dip" | "Staged buys, then exit" | "Dip buy" | "Buy, then take profits" | "DCA in a band" | "Counter-trade" | "Perp trend, long and short" | "Funding fade" | "VWAP rotation: fade failed pushes to the mean" | "Order book imbalance" | "Microprice lean" | "Maker quotes" | "Depth-capped breakout" | "Options volatility, hedged on the perp book" | "Polymarket book pressure" | "Hedged range, width by session and volatility" | "Overnight hold: buy the close, sell the open" | "Hedged LP: volatility range, perp short"
- `name`: string
- `description`: string
- `source`: string. Flowscript.
- `params`: array of object
- `tests`: array of object. Test cases, as test_strategy takes them.

### `check_strategy`

**Check a strategy compiles** (read).

Compile Flowscript with its params as save_strategy would, saving nothing: { source, params? }. Answers { ok: true }, or { ok: false, problems } by line and column. The same object save_strategy takes checks too: its name, description and tests are passed over here.

Arguments:

- `source` (required): string. Flowscript.
- `params`: array of object
- `name`: string. Not used here.
- `description`: string. Not used here.
- `tests`: array of object. Test cases, as test_strategy takes them.

### `get_protocol_guide`

**Read a protocol's skill** (read).

How a protocol a strategy uses works, how to use it from Flowscript, how to make a strategy on it pay more, and what the paper book leaves out, with its markets and live markets (D1350): { protocol } for one, { strategy } for every protocol a saved strategy imports in its uses line, or nothing for the list. Read it before designing on a protocol; save_strategy and check_strategy name the ones a source imports.

Arguments:

- `protocol`: "aave" | "spark" | "compound" | "morpho" | "euler" | "yearn" | "fluid" | "gearbox" | "spark_savings" | "sky" | "ethena" | "maple" | "uniswap" | "kyberswap" | "cow" | "aerodrome" | "pendle" | "marginfi" | "kamino" | "kamino_earn" | "jupiter_lend" | "jito" | "pons" | "pumpfun" | "pumpswap" | "launchlab" | "virtuals" | "zora" | "clanker" | "flap" | "metric_omm" | "jupiter" | "derive" | "earn"
- `strategy`: string. A saved strategy: the skills of every protocol it uses.

### `get_protocol_health`

**How the protocols' data is followed** (read).

For each deployment of each lending, vault, DEX and yield protocol read on chain (or of { protocol }): when its state was last kept and how far its kept history reaches, how far its events were read and how many came in over the last day, and its feeds that are failing, with what needs a look in words (problems). Check it before trusting a backtest or a strategy that re-plans on a clock; inside a program, <protocol>.stateAge(deployment) is the age of the state each bar runs on.

Arguments:

- `protocol`: string. One protocol, by its id (aave, uniswap, pendle, …).
- `problems`: boolean. Only the deployments with something to look at.

### `list_modules`

**Modules a strategy can import** (read).

Everything a Flowscript strategy can import, shelf by shelf: market data (bars, pool, feed, funding, book, sources), trading and books (paper, perp, lp, wallet, funds, husher, poll), protocols (lending markets, vaults, fixed rates, options), the earn router, std contract modules (std/erc20, std/erc4626) and your own libraries. Each gives its id, the line that imports it (`uses { aave: "^1" }` or `import "std/erc20";`), what it is for, the chains it reaches and whether it trades real funds. Filter with { kind?: data|trading|protocol|intent|std|library, chain?, search? }. get_module { id } gives one module whole: its reads, actions, markets, the series to backtest it on, how deep that history goes, and an example that compiles.

Arguments:

- `kind`: "data" | "trading" | "protocol" | "intent" | "std" | "library"
- `chain`: string. Only modules that reach this chain, e.g. base.
- `search`: string. Words to find in a module's id, name, summary, reads, actions or markets, e.g. "usdc vault".

### `get_module`

**One module, whole** (read).

One module a strategy can import ({ id }: an id from list_modules, such as "aave", "perp", "earn", "std/erc4626" or one of your libraries): the line that imports it, its reads (market series and book state), its actions with their arguments, its markets as a program names them and which take real funds, the series to backtest and run it on with how far back each market's history goes, and an example strategy that compiles (pass it to save_strategy as source). For a protocol it is everything to write a strategy on it in one call: start (the uses and import lines a strategy begins with), skill (the protocol's whole skill), library (the source of its helper functions), sources (every series it records, by market, with the read that gives it and the series to backtest on), examples, and next (check, backtest, save).

Arguments:

- `id` (required): string. A module id from list_modules.

### `test_strategy`

**Test a strategy** (read).

Feed a strategy bars you write and check what it does, saving nothing: { id } runs a saved strategy's own tests; { id, tests } or { source, params?, tests } runs these. Each case: { name, bars?, sources?: { 'bars.close': [100, 101, null, …] or a generator }, ohlc?: <a close series; open, high, low and volume follow>, params?: { arg: value }, paper?: false, runs?: N (run it N times, each walk with the next seed, to hold its expectations over many random markets; at most 100), expect: [...] }. Generators: { constant: x }, { ramp: [from, to] }, { sine: { mid, amp, period } }, { steps: [[bar, value], …] }, { walk: { from, step, seed } }, each with na?: [bar, …]. Expectations, bars counted from 0: { check: "<Flowscript condition over its consts>", at: N|'ever'|'always'|'never'|'last' }, { metric, at, equals, within? } or { metric, at, na: true }, { fires: 'paper.buy', at?, count?, args? } (count 0: never), { fills: 'paper.buy', at?, count? }, { reverts: …, at?, count? }, { paused: 'paper.buy', at?, count?, by?: 'drawdown' } (an entry the strategy's invariants cage refused; count 0: never), { warns: FS0901 or 'none' }. A case trades the backtest's paper broker (1000 cash, 30 bps) when the program imports paper. A protocol's reads and its book are sources by name, and a book does not follow the actions in a test, so step it where they would have moved it: sources: { 'aave.supplyApy': { steps: [[0, 3], [3, 5], [6, 2]] }, 'aave.supplied': { steps: [[0, 0], [4, 1000]] } } with expect [{ fires: 'aave.supply', at: 3 }, { fires: 'aave.withdraw', at: 6 }]. Answers { passed, failed, tests: [{ name, passed, failures: [{ expect, message }] }] }, each failure saying what happened instead; a failed fires … at: N also says what its when condition read at bar N. Save passing tests with save_strategy({ id, tests }) so later edits are checked against them.

Arguments:

- `id`: string. A saved strategy; its own tests unless tests are given.
- `source`: string. Flowscript, to test a draft.
- `params`: array of object
- `tests`: array of object. Test cases, as test_strategy takes them.

### `list_libraries`

**Libraries** (read).

The account's libraries (D1214): Flowscript `fn` definitions a strategy imports with `import "name";` after its `uses` line and then calls as its own. Each lists the strategies that import it and whether each was saved with its current source.

No arguments.

### `save_library`

**Write a library** (writes).

Make or change a library ({ name, description?, source }): Flowscript `fn` definitions only, such as `fn mid(src: Series<number>, len: count): Series<number> { return (sma(src, len) + ema(src, len)) / 2; }`. A strategy that writes `import "name";` calls them. A strategy keeps a library as it was when the strategy was saved: save each strategy listed in importedBy again to take a change.

Arguments:

- `name` (required): string. Lowercase letters, digits, - and _, up to 40.
- `description`: string
- `source` (required): string. Flowscript fn definitions.

### `delete_library`

**Delete a library** (replaces or deletes).

Delete a library. A strategy saved with it keeps what it imported, until it is saved again.

Arguments:

- `name` (required): string

### `get_pine_translation`

**Translate a Pine Script strategy** (read).

Translate a TradingView Pine Script v5/v6 strategy to a strategy here ({ pine }). Returns { kind, ok, source, params, settings, notes, problems? }: the Flowscript, its inputs as params, its commission, capital, pyramiding and fixed stop or take-profit as backtest settings (feeBps, cash, pyramiding, stopPct, takePct), and a note for each Pine line that did not come across (refused) or came across changed. Nothing is saved: finish what the notes list, then save_strategy with source and params, and backtest_strategy with the settings. A var and its :=, += … updates (under ifs too) become one fold; a for loop summing, or taking the max or min of, x[i] over the last len bars becomes a window function, and one over constant bounds is unrolled; := on a name that is not a var, other loops, arrays and request.security of another symbol have no counterpart; rewrite them with window functions or fold. time(tf, session, timezone) and hour(time, zone) come across as Flowscript's sessions and city clocks.

Arguments:

- `pine` (required): string. The Pine Script source.

### `delete_strategy`

**Delete a strategy** (replaces or deletes).

Delete a strategy and its backtests. Stop and remove its running copies first (remove_running_strategy).

Arguments:

- `id` (required): string

### `list_backtest_sources`

**What can be backtested** (read). Also answers to `backtest_sources`.

What a strategy can be backtested and run on: the account's indexed pools (pool:<chain>:<address>), perps markets in Your pools (perp:<hyperliquid|lighter|derive>:<symbol>, Derive's like perp:derive:ETH-PERP) shared feeds (feed:<name>) and your own sources with a number field (source:<name>, its events as bars: OHLC fields give a bar each, else its price field; ?field=value keeps one key's events, ?price=, ?volume=, ?time= name fields), with how far back each goes (a perp also gives oldestBy, each interval's own start: its 1m and 5m candles reach far less far back than its 1h). With nothing asked it lists the account's pools, perps and sources and counts the shared feeds (there are thousands); q (words in the id or label, e.g. "ETH" or "btc usd") and kind narrow it and list the feeds that match, at most `limit` (50) a kind. A perps market need not be listed: backtesting perp:hyperliquid:<symbol> or perp:lighter:<symbol> adds it on first use. For a rate or a price now, search_market_data, not this. A strategy over more than one market names its pool, perp and feed by these keys without the kind in front.

Arguments:

- `q`: string. Words that must all be in the id or label (any case), e.g. "ETH" or "sol usd".
- `kind`: "pool" | "perp" | "feed" | "source"
- `limit`: integer (≥ 1, ≤ 500). The most of each kind listed (50).

### `backtest_strategy`

**Backtest a strategy** (writes).

Backtest a strategy on paper over a series and window: { id, series, interval, from?, to? (unix seconds; the last 30 days by default, or the last 20,000 bars when shorter: about 13 days of 1m), args?, cash?, feeBps?, leverage?, slippageBps?, pyramiding?, stopPct?, takePct?, trailPct? }. On a pool or a feed, slippageBps moves every fill against the order, pyramiding caps the buys one position takes, and stopPct, takePct and trailPct sell the whole position at the bar close that crosses a stop under the average entry, a take-profit over it, or a trail under the highest close since it opened (orders from exit.stop, exit.take, exit.trail). paper.buyPct({ pct }) buys a percent of equity and paper.sellPct({ pct }) sells a percent of the position. On a perp, leverage is 1 to the market's highest, the fee defaults to the venue's taker fee, funding and liquidation are modelled, and stopPct and takePct close the position when a bar's low or high reaches that percent against or past its entry (at the open, if the bar opens beyond it; the stop first when one bar reaches both), sent to the project as a close on a running strategy; slippageBps moves each fill (not a liquidation) that far against the order, and a strategy that reads the book of its own market (book.*) also crosses half the spread recorded at that bar on each taker fill (a maker fill does not); fillAt: "nextOpen" (spot or perp, backtests and sweeps only) fills each order at the next bar's open instead of the close it was decided on; the other order settings are for a pool or a feed. Over more than one market series may be left out (the pool it reads, else its perp, else its feed), cash is split between the wallet and the perp account (margin, a quarter unless named), leverage (3 unless named) caps the perp account, the range earns its share of each swap's fee and pays the pool's fee and gasPerRebalance on each move, and legs gives each book at each bar (wallet, range, perp account, what the range holds against the hedge, the width); stats add lpFees, lpPnl, perpPnl, funding, rebalances, inRangePct and avgNetDeltaPct. Returns return, buy-and-hold, drawdown, trades, win rate, window (its first and last bar as UTC dates, and bars), (reverted says how many orders reverted and why the first did: at 1x a perp whose cash is its size is short of margin after its first loss), every order and the equity curve (at most 200 points); in the same answer (nothing to ask for), analysis has annualReturnPct, volatilityPct, sharpe, sortino, calmar, longestDrawdownBars, exposurePct, roundTrips, profitFactor, expectancy, avgWin, avgLoss, avgTradePct, bestTrade, worstTrade, avgBarsHeld and maxConsecutiveLosses (annualized over 365 days, no risk-free rate), monteCarlo reshuffles the closed round trips 1,000 times (seeded) for the 5th-95th percentile return, the median and 95th percentile drawdown and the share of orders that lose (5 round trips at least), and trades lists the latest 100 round trips (flat to flat: entry and exit time and price, units, cost, pnl, pnlPct, fees, bars). To compare settings, backtest_sweep; to search for the best with a holdout and a trial-count penalty, run_optimization. Saved: list_backtests reads it again. The answer's metricStats gives each metric the strategy publishes its last value and spread over the window (min, p10, p50, p90, p99, max, and the bars it had a value on), to pick a metric trigger's value from; a word metric (metric("state", "rich")) gives its last word, its bars and how many bars held each word. A setting left out comes from the program's own // @backtest line when it has one, such as // @backtest { series: "perp:hyperliquid:BTC", interval: "5m", days: 90, cash: 1000, leverage: 2, slippageBps: 1 }; what is given wins.

Arguments:

- `id` (required): string
- `series`: string. From list_backtest_sources.
- `interval`: "5s" | "15s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d"
- `from`: number
- `to`: number
- `args`: object
- `cash`: number (> 0)
- `feeBps`: number (≥ 0)
- `leverage`: number (≥ 1)
- `slippageBps`: number (≥ 0, ≤ 1000)
- `pyramiding`: integer (≥ 1, ≤ 100). The most buys one open position takes.
- `stopPct`: number (≤ 99, > 0)
- `takePct`: number (> 0)
- `trailPct`: number (≤ 99, > 0)
- `fillAt`: "close" | "nextOpen". Backtests only: fill each order at the close it was decided on (the default) or at the next bar's open.
- `margin`: number (≥ 0). Over more than one market: the USD of cash that starts in the perp account (a quarter unless named).
- `gasPerRebalance`: number (≥ 0). Over more than one market: USD a range move pays in gas (0.05 unless named).
- `transferCost`: number (≥ 0). Over more than one market: USD a move between the wallet and the perp account pays (0.5 unless named).
- `moveCost`: number (≥ 0). Over a protocol: USD each move into or out of its market pays on paper, the bridge and gas a live run would (0 unless named; about 1 on Base or Arbitrum, 5 on Ethereum).
- `minPerpUsd`: number (≥ 0). Over more than one market on wallets: the least perp trade a wallet sends to follow the run, USD (10 unless named).

### `backtest_sweep`

**Sweep a strategy's settings** (read). Also answers to `sweep_strategy`.

Backtest every setting of a strategy's arguments a grid names over one window, the data read once, and rank them (D1496): backtest_strategy's fields (id, series, interval, from?, to?, cash?, feeBps?, leverage?, order settings, settings over more than one market, args? for the arguments the grid holds still) and grid { name: [values] | { from, to, step } (a number) | "both" (true or false) }, objective? (sharpe, sortino, returnPct, calmar, profitFactor, maxDrawdownPct; sharpe unless named; maxDrawdownPct ranks the shallowest first), minTrades? (round trips a setting must close to be ranked, 1 unless named), walkForward? ({ folds?: 2-12 (4), trainPct?: 50-90 (75) }). At most 200 settings and 2,000,000 settings × bars, over a window of at most 20,000 bars as a backtest's. Returns combos, ran, best, rows (best first, at most 50: args, returnPct, maxDrawdownPct, sharpe, sortino, calmar, profitFactor, trades, winRatePct, fees, exposurePct, annualReturnPct, score), spread (the objective's min, median and max over every setting) and notes. With walkForward the window is cut into folds, each a training span and the test span after it: the setting that scored best over each training span is scored over its test span, and walkForward gives each fold's pick and both scores, oosReturnPct (the test spans compounded), bestInSampleOosReturnPct, efficiencyPct (test score over training score: near 100 holds up, near 0 was fitted to noise) and distinctPicks. Nothing is saved: backtest_strategy { args: best.args } keeps the run, and save_strategy can make them the defaults. For a space too big to try in full, a holdout and a trial-count penalty, run_optimization. A setting left out comes from the program's own // @backtest and // @optimize line when it has one, such as // @backtest { series: "perp:hyperliquid:BTC", interval: "5m", days: 90, cash: 1000, leverage: 2, slippageBps: 1 } and // @optimize { objective: "sharpe", method: "genetic", trials: 200, holdout: 30, walkForward: true, minTrades: 10 }; what is given wins.

Arguments:

- `id` (required): string
- `series`: string. From list_backtest_sources.
- `interval`: "5s" | "15s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d"
- `from`: number
- `to`: number
- `args`: object. The arguments the grid does not vary; the rest keep their defaults.
- `grid` (required): object. Each argument to vary: a list of values, or { from, to, step } over a number.
- `objective`: "sharpe" | "sortino" | "returnPct" | "calmar" | "profitFactor" | "maxDrawdownPct"
- `minTrades`: number (≥ 0)
- `walkForward`: object
- `cash`: number (> 0)
- `feeBps`: number (≥ 0)
- `leverage`: number (≥ 1)
- `slippageBps`: number (≥ 0, ≤ 1000)
- `pyramiding`: integer (≥ 1, ≤ 100). The most buys one open position takes.
- `stopPct`: number (≤ 99, > 0)
- `takePct`: number (> 0)
- `trailPct`: number (≤ 99, > 0)
- `fillAt`: "close" | "nextOpen". Backtests only: fill each order at the close it was decided on (the default) or at the next bar's open.
- `margin`: number (≥ 0). Over more than one market: the USD of cash that starts in the perp account (a quarter unless named).
- `gasPerRebalance`: number (≥ 0). Over more than one market: USD a range move pays in gas (0.05 unless named).
- `transferCost`: number (≥ 0). Over more than one market: USD a move between the wallet and the perp account pays (0.5 unless named).
- `moveCost`: number (≥ 0). Over a protocol: USD each move into or out of its market pays on paper, the bridge and gas a live run would (0 unless named; about 1 on Base or Arbitrum, 5 on Ethereum).
- `minPerpUsd`: number (≥ 0). Over more than one market on wallets: the least perp trade a wallet sends to follow the run, USD (10 unless named).

### `run_optimization`

**Optimize a strategy's settings** (writes). Also answers to `optimize_strategy`, `start_optimization`.

Search a strategy's arguments for the best setting, with overfitting checks; it runs on the server for up to 15 minutes and get_optimization reads it. backtest_strategy's fields (id, series, interval, from?, to?, cash?, feeBps?, leverage?, order settings, settings over more than one market, args? for the arguments held still), and space? { name: [values] | { from, to, step } | "both" } (left out: every number from half to double its default in 7 steps and every true/false both ways), method? (auto: every setting when the space holds no more than trials, else genetic; grid, random, genetic), trials? (distinct settings backtested, 100 unless named, at most 500), objective? (sharpe, sortino, returnPct, calmar, profitFactor, maxDrawdownPct), minTrades?, holdoutPct? (10-50, 30 unless named: the end of the window no choice sees), walkForward? ({ folds?, trainPct? } inside the training span), seed?. Settings are ranked by the objective over the training span only. The report gives best and current (the setting in use), each with train and holdout scores; rows (best first); deflated (the deflated Sharpe ratio after every setting tried: dsr is the odds the best's edge is above zero); trainHoldoutCorrelation; walkForward; and verdict { label: promising | not proven | no edge, reasons }. Quote the holdout and the trial count beside any number. apply_optimization saves the best as the defaults or turns a running copy's knobs. One optimization per account at a time. A setting left out comes from the program's own // @backtest and // @optimize line when it has one, such as // @backtest { series: "perp:hyperliquid:BTC", interval: "5m", days: 90, cash: 1000, leverage: 2, slippageBps: 1 } and // @optimize { objective: "sharpe", method: "genetic", trials: 200, holdout: 30, walkForward: true, minTrades: 10 }; what is given wins. The space is the program's own param(default, { min, max, step | options }) ranges when it declares them.

Arguments:

- `id` (required): string
- `series`: string. From list_backtest_sources.
- `interval`: "5s" | "15s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d"
- `from`: number
- `to`: number
- `args`: object. The arguments held still; the rest keep their defaults.
- `space`: object. Each argument to vary: a list of values, { from, to, step } over a number, or "both" for a switch. Left out: every number and switch around its default.
- `method`: "auto" | "grid" | "random" | "genetic" | "anneal" | "tpe"
- `trials`: integer (≥ 2, ≤ 500)
- `objective`: "sharpe" | "sortino" | "returnPct" | "calmar" | "profitFactor" | "maxDrawdownPct"
- `minTrades`: number (≥ 0)
- `holdoutPct`: number (≥ 10, ≤ 50)
- `walkForward`: object
- `seed`: integer (≥ 0)
- `cash`: number (> 0)
- `feeBps`: number (≥ 0)
- `leverage`: number (≥ 1)
- `slippageBps`: number (≥ 0, ≤ 1000)
- `pyramiding`: integer (≥ 1, ≤ 100). The most buys one open position takes.
- `stopPct`: number (≤ 99, > 0)
- `takePct`: number (> 0)
- `trailPct`: number (≤ 99, > 0)
- `fillAt`: "close" | "nextOpen". Backtests only: fill each order at the close it was decided on (the default) or at the next bar's open.
- `margin`: number (≥ 0). Over more than one market: the USD of cash that starts in the perp account (a quarter unless named).
- `gasPerRebalance`: number (≥ 0). Over more than one market: USD a range move pays in gas (0.05 unless named).
- `transferCost`: number (≥ 0). Over more than one market: USD a move between the wallet and the perp account pays (0.5 unless named).
- `moveCost`: number (≥ 0). Over a protocol: USD each move into or out of its market pays on paper, the bridge and gas a live run would (0 unless named; about 1 on Base or Arbitrum, 5 on Ethereum).
- `minPerpUsd`: number (≥ 0). Over more than one market on wallets: the least perp trade a wallet sends to follow the run, USD (10 unless named).

### `get_optimization`

**Read an optimization** (read).

An optimization as it stands: state (running, done, failed), progress (trials so far of the budget, best training score) and, once done, its report (see run_optimization). waitSeconds? (at most 50) waits for it to end first.

Arguments:

- `id` (required): string. The optimization's id (opt_…).
- `waitSeconds`: number (≥ 0, ≤ 50)

### `list_optimizations`

**Past optimizations** (read).

Optimizations, newest first (of one strategy, given id), each with its state, its verdict, trials, the best setting and its holdout return; not their reports (get_optimization reads one).

Arguments:

- `id`: string. A strategy's id, to list only its optimizations.

### `control_optimization`

**Stop an optimization** (writes). Also answers to `cancel_optimization`.

Stop a running optimization ({ id, action: "stop" }); it ends with the report of the settings it got through.

Arguments:

- `id` (required): string
- `action` (required): "stop"

### `apply_optimization`

**Apply an optimization's best settings** (writes).

Apply a finished optimization's best setting (or args?, one of its rows): as the strategy's defaults, which every new backtest and run starts from, or, given deploymentId, as a running copy's knobs (it decides from the next bar). Refused once the strategy was saved again since the optimization ran.

Arguments:

- `id` (required): string. The optimization's id (opt_…).
- `args`: object
- `deploymentId`: string. A running copy of the same strategy (list_running_strategies).

### `export_strategy_run`

**Export a run to run locally** (read).

Everything a backtest would replay for a strategy over a window, without replaying it, so it runs on your own machine (D1453): { strategy (a saved strategy's id) or source (Flowscript, saved or not) with params?, then backtest_strategy's fields: series, interval, from?, to?, args?, cash?, feeBps?, leverage?, order settings and settings over more than one market }. Returns a run bundle { version, kind (spot, perp or legs), program (as compiled, its market, interval and arguments written in), shift, host (reads, capabilities and libraries), series, interval, secs, ts, cash, and history and broker, perp or legs: the data and the book } with NaN written "NaN". `npx ftw-trade local backtest` fetches one and replays it with the same books backtest_strategy uses, making the same trades.

Arguments:

- `strategy`: string
- `source`: string
- `params`: array of object
- `series`: string. From list_backtest_sources.
- `interval` (required): "5s" | "15s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d"
- `from`: number
- `to`: number
- `args`: object
- `cash`: number (> 0)
- `feeBps`: number (≥ 0)
- `leverage`: number (≥ 1)
- `slippageBps`: number (≥ 0, ≤ 1000)
- `pyramiding`: integer (≥ 1, ≤ 100). The most buys one open position takes.
- `stopPct`: number (≤ 99, > 0)
- `takePct`: number (> 0)
- `trailPct`: number (≤ 99, > 0)
- `fillAt`: "close" | "nextOpen". Backtests only: fill each order at the close it was decided on (the default) or at the next bar's open.
- `margin`: number (≥ 0). Over more than one market: the USD of cash that starts in the perp account (a quarter unless named).
- `gasPerRebalance`: number (≥ 0). Over more than one market: USD a range move pays in gas (0.05 unless named).
- `transferCost`: number (≥ 0). Over more than one market: USD a move between the wallet and the perp account pays (0.5 unless named).
- `moveCost`: number (≥ 0). Over a protocol: USD each move into or out of its market pays on paper, the bridge and gas a live run would (0 unless named; about 1 on Base or Arbitrum, 5 on Ethereum).
- `minPerpUsd`: number (≥ 0). Over more than one market on wallets: the least perp trade a wallet sends to follow the run, USD (10 unless named).

### `list_backtests`

**Past backtests** (read).

A strategy's latest backtests, newest first, with their settings and results but not their per-bar series.

Arguments:

- `id` (required): string

### `run_strategy`

**Deploy, stop or restart a strategy** (trades or moves funds).

Run a strategy over a series from the next bar ({ id, series, interval, args?, cash?, feeBps?, leverage?, slippageBps?, pyramiding?, stopPct?, takePct?, trailPct?, account? }, the order settings as backtest_strategy takes them), or stop or restart one that runs ({ deployment, state: running|stopped }), or turn a running one's knobs ({ deployment, args: { name: value } }: new values for some of its parameters, which decide from the next bar; past trades stay as they are), or set its max loss ({ deployment, maxLoss }). maxLoss (D1700), in USD, on a new run or a running one: once the run's P&L (what its card shows: on a live project its money now less all put in, net of withdrawals, as marked every quarter hour; otherwise its own book's equity less its cash, in USD, at each bar) is −maxLoss or below, the run stops, logs why (“Stopped: lost $10.02, past its $10 max loss”) and closes out what it holds on the wallets as control_strategy_close_out does (a pool or Polymarket run's wallets sell its token; a run on its own paper book only stops); 0 takes the cap off; a run the cap stopped stays stopped until restarted. It decides on each closed bar once its indicators have enough bars and keeps running on the server by itself. It runs the code the strategy had when it started: an edit to the strategy marks it outdated and changes nothing it does (a restart keeps that code; start a new run for the new code, with takeOver naming the old run to keep what its wallets hold). Without account it keeps its own paper book. With account { project, wallets?, ethPerBuy, token?, pick? } over a pool series, each order also trades that project's wallets as its bar closes: a buy spends ethPerBuy from each wallet, a sell sells each one's whole holding; with pick: n each buy goes out from n of the wallets drawn at random (a desk's orders spread over its wallets) and a sell still sells every wallet's holding. With account { project, wallets?, sizing? } over a perp series, each wallet sends the order's own USD notional to the venue as a market order at the run's leverage, and a close closes what it holds (sizing fixed, the default); with sizing match, each round's fills go out as one target instead, bringing each wallet to the book's position as a share of its own account equity (a $300 long on a $1,000 book is 30% of each wallet's equity), so a deposit or withdrawal on the wallet's account resizes its next position and nothing is sent under $10; its maker orders (perp.bid, perp.ask, D1497) rest on the venue post-only from each wallet, moved and cancelled as its book's are, and fill there (testnet unless the market is mainnet and the wallet's mainnet account is on; see get_project_perps). The run's perp.sizeUsd() reads its own book, which starts flat: when the wallets already hold the market the answer has a warning naming those positions, since it would trade on top of them. With account, paper or live as the project is; the answer says which. Over more than one market (D1205) series may be left out and margin, gasPerRebalance and transferCost are as backtest_strategy takes them. With account on a paper project, a run over more than one market without a range sends what its books fill to each wallet it names as it fills: spot orders (paper.*) as swaps (each its own size, or account.ethPerBuy), perp orders and funds moves to the wallet's paper perps account (the run's margin goes in first), and Derive orders to its paper subaccount (topped up by the premium). With account { project, wallets? } (D1234) a run that holds a range trades each wallet it names once the project is live: cash is each wallet's USD, and every closed bar each wallet is made to hold what the paper books hold: its range on chain put in as ETH (the pool must pair ETH with a dollar, priced in dollars per ETH), its perp position on its own account at the run's leverage (traded when off by more than minPerpUsd), and margin plus every funds.toPerp deposited to that account in ETH through Relay (a first Lighter deposit makes the account). A step that fails on chain stops the run; control_strategy_close_out unwinds it. add_strategy_dashboard puts its charts, and each wallet as it stands, in a project. A setting left out comes from the program's own // @backtest (its market, interval and fills) and // @deploy line when it has one, such as // @backtest { series: "perp:hyperliquid:BTC", interval: "5m", days: 90, cash: 1000, leverage: 2, slippageBps: 1 } and // @deploy { interval: "5m", cash: 500 } (on paper; with account it trades the wallets as the project is); what is given wins.

Arguments:

- `id`: string. The strategy, to start a new run.
- `series`: string. From list_backtest_sources.
- `interval`: "5s" | "15s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d"
- `args`: object
- `cash`: number (> 0)
- `feeBps`: number (≥ 0)
- `leverage`: number (≥ 1)
- `slippageBps`: number (≥ 0, ≤ 1000)
- `pyramiding`: integer (≥ 1, ≤ 100). The most buys one open position takes.
- `stopPct`: number (≤ 99, > 0)
- `takePct`: number (> 0)
- `trailPct`: number (≤ 99, > 0)
- `fillAt`: "close" | "nextOpen". Backtests only: fill each order at the close it was decided on (the default) or at the next bar's open.
- `margin`: number (≥ 0). Over more than one market: the USD of cash that starts in the perp account (a quarter unless named).
- `gasPerRebalance`: number (≥ 0). Over more than one market: USD a range move pays in gas (0.05 unless named).
- `transferCost`: number (≥ 0). Over more than one market: USD a move between the wallet and the perp account pays (0.5 unless named).
- `moveCost`: number (≥ 0). Over a protocol: USD each move into or out of its market pays on paper, the bridge and gas a live run would (0 unless named; about 1 on Base or Arbitrum, 5 on Ethereum).
- `minPerpUsd`: number (≥ 0). Over more than one market on wallets: the least perp trade a wallet sends to follow the run, USD (10 unless named).
- `lateOrders`: "drop" | "send". On wallets: an order the run made more than two bars late (catching up after being down, or held by a slow send) is dropped and logged (drop, the default) or sent anyway.
- `account`: object
- `takeOver`: string. With id: a run on the same project's wallets, series, interval and cash to take over (sd_…), to move it to new code without unwinding. The old run stops; the new one replays from the old one's first bar and its wallets' live state is the old one's, so nothing is sent until the new code decides differently. A run on several markets (a protocol, a range, lending) only.
- `maxLoss`: number (≥ 0). The most the run may lose, in USD (D1700): at a P&L of −maxLoss or below it stops and closes out. On a new run, or with deployment to set or change it; 0 takes it off.
- `deployment`: string. A running strategy (list_running_strategies), to stop, restart or retune with args or maxLoss.
- `state`: "running" | "stopped"

### `start_preset`

**Start a no-code strategy template** (trades or moves funds).

Start a template (D1695), the same strategies the Strategies page sets up without code: { preset, series, interval?, args?, cash?, maxLoss?, stopPct?, takePct?, trailPct?, account? }. maxLoss, in USD, stops the run and closes it out once its P&L reaches −maxLoss, as run_strategy's does (D1700). preset is oscillator, dca, trigger, swing, volume, response or ripple (volume and response run on Your pools only, as they read each swap; ripple on a pool or a spot feed); series a market (perp:hyperliquid:ETH, perp:lighter:BTC, a pool pool:<chain>:<address> or a feed); args its settings by name (left out, its defaults); cash the paper budget. Without account it keeps its own paper book; with account { project, wallets?, ethPerBuy? } it trades that project's wallets, paper or live as the project is, as run_strategy does. The Oscillator's args: period (RSI candles, default 14), buyBelow (30), sellAbove (70), onExit (false: trade the moment RSI enters a zone; true: when it leaves it), size (USD each buy), sellPct (each sell's share of the holding; on perps a sell closes the long), maxPrice/minPrice (0 = any). Each preset runs as one saved strategy per preset and book kind, so it uses no strategy slot per run; stop, restart or retune it with run_strategy { deployment, … }. Give { preset } alone to read a preset's settings and defaults.

Arguments:

- `preset` (required): "oscillator" | "dca" | "trigger" | "swing" | "volume" | "response" | "ripple"
- `series`: string. The market: perp:<venue>:<symbol>, pool:<chain>:<address> or feed:<name>.
- `interval`: "5s" | "15s" | "1m" | "5m" | "15m" | "1h"
- `args`: object
- `cash`: number (> 0)
- `maxLoss`: number (≥ 0). The most the run may lose, in USD: at a P&L of −maxLoss or below it stops and closes out (D1700).
- `stopPct`: number (> 0)
- `takePct`: number (> 0)
- `trailPct`: number (> 0)
- `account`: object

### `control_strategy_close_out`

**Close out a run on wallets** (trades or moves funds).

Stop a run on a project's wallets and unwind it ({ deployment }). A hedged run: each wallet's range is withdrawn and its dollar side sold back to ETH, and its perp position closed. A perp run: each wallet's position on its market is closed. A run over one pool (a template such as DCA, or a spot strategy): each wallet's holding of its token is sold back. Margin stays in each perp account; withdraw it with the project's perps tools.

Arguments:

- `deployment` (required): string

### `list_running_strategies`

**Running strategies** (read). Also answers to `running_strategies`.

The account's running and stopped strategies: their series, arguments, equity, stats, latest metrics and orders, and for one trading a project, each order sent to it. On a live project equity and stats are the run's paper book (figuresAre says so) and liveMoney is its real money across chains and venues, read every 15 minutes: equityUsd, startUsd, depositsUsd (all put in, net of what came out), pnlUsd (money less deposits), returnPct (time-weighted since its first reading), and where it is (parts). Outside top-ups and withdrawals in ETH, WETH or USDC to or from another person's wallet are found on the chain and set aside by themselves; add_run_flow is for one they miss, such as a send from a smart wallet. id (a run, sd_…) or strategy (st_…) answers only those. stalledSec, when set, is how long a running one has had no new closed bar past what its interval explains: its market went quiet or its index fell behind.

Arguments:

- `id`: string
- `strategy`: string

### `add_run_flow`

**Record money put into or taken out of a live run** (writes).

Money put into a live run's wallets from outside, or taken out, that was not found by itself: ETH, WETH and USDC sent from or to another person's wallet are read from the chain each quarter hour, and bridges, swaps and the app's own sends are never flows, so this is for what that misses (a send from a smart wallet or an exchange contract, another token): { deployment, usd (positive in, negative out), at? (unix ms, now by default), note?, key? (the same key replaces an earlier record) }. Its live P&L (liveMoney in list_running_strategies, and its dashboard) sets it aside, so a top-up is not counted as profit; fundings and returns made through the app are counted by themselves.

Arguments:

- `deployment` (required): string
- `usd` (required): number
- `at`: number
- `note`: string
- `key`: string

### `get_log`

**What ran, did, failed and why** (read). Also answers to `get_activity_log`, `strategy_log`, `get_account_log`, `get_errors`.

The account's log, newest first (D1300, D1330): how things ran and what went wrong. With no deployment, strategy or project: the whole account: every API request and tool call (this one's included) that changed something or was refused or failed, with what was asked (secrets taken out), the status and message that came back and how long it took; its strategies, workflows, orders, scripts, shortcuts, liquidity positions, launches and perps agent, and what its background runs met. problems: true keeps warnings and errors, and q entries whose message or name has a text; a problem met again and again is one entry with data.repeats and data.lastAt; kind keeps one kind (request, tool, strategy, workflow, order, script, shortcut, lp, launch, agent, account); area keeps the requests and tool calls about one thing (send, deposit, withdraw, bridge, perps, trade, wallets, strategy, workflow, script, source, project, account, other). The first page has day: the last 24 hours' errors and warnings by kind. { deployment }: one running strategy's decisions, each with the when it came from (gate, condition, line and actionLine on the strategy's own lines) and what each part of its condition read at that bar (why.terms), the orders it sent and what each wallet did, its errors, starts and stops; with its code, each when on its line, and what each when read at its last bar (now), which says why it has not acted; a removed run's log stays readable (removed: true, its entries only). { strategy }: every run of a strategy, and per line how many decisions came from it. { project }: everything on a project. before (unix ms, the oldest entry's at) reads the page before; limit up to 500. An operator may pass account (an account id or handle) to read that account's log.

Arguments:

- `deployment`: string. A running strategy (list_running_strategies).
- `strategy`: string. A strategy id: every run of it.
- `project`: string. The project id (list_projects).
- `kind`: "strategy" | "workflow" | "order" | "script" | "shortcut" | "request" | "tool" | "lp" | "launch" | "agent" | "account". Only this kind.
- `area`: "send" | "deposit" | "withdraw" | "bridge" | "perps" | "trade" | "wallets" | "strategy" | "workflow" | "script" | "source" | "project" | "account" | "other". Only requests and tool calls about this.
- `problems`: boolean. Only warnings and errors (with deployment, strategy and project too).
- `q`: string. Only entries whose message or name has this text, any case: an error, a tool, a route, a token.
- `account`: string. Operators only: another account, by id or handle.
- `before`: number
- `limit`: integer (≥ 1, ≤ 500)

### `add_strategy_dashboard`

**Chart a running strategy in a project** (writes).

Add a running strategy's dashboard to a project ({ deployment, project }), or refresh it. It is a group of tabs under the strategy's name, each left out when it would be empty: Overview (at most four headline numbers: its money, P&L, return and the one its kind turns on; one big curve of its P&L; the market with its fills or what it decides on; its latest trades; what it holds), Positions (what it holds on chain or on the venue, its wallets), Trades (every fill and order, and what it trades each day), Signals (its knobs and metric() values), Risk (drawdown, liquidation distance, funding, delta, range width) and Performance (its curve, books and figures: where the P&L came from). A project without room for them gets one tab instead. The widgets: its return, and over more than one market its range's fees, the funding it paid, its net delta, each book (range, perp account, wallet) over time, the range against the hedge, the range's width and its figures; over one market a tab shaped to the run: its trades, win rate and worst drawdown, the values its code charts with metric() (and for two of them, e.g. a fast and slow EMA, their gap, which changes sign where it trades), on a perp venue its funding, open P&L, distance to liquidation and position, then its return over time and its fills; and its orders. A new run's tabs go first. Answers its first tab. Each chart is a viz widget on the deployment's report (strategy-stats, strategy-metrics, strategy-legs, strategy-curve, strategy-orders) or the wallets' perps, to change like any other.

Arguments:

- `deployment` (required): string
- `project` (required): string. The project id (list_projects).

### `add_desk_dashboard`

**Chart several wallets of one strategy together** (writes).

Add (or refresh) a desk's dashboard to a project ({ project, name, runs: [{ deployment, role }], about? }): runs on the project's own wallets, one wallet each, each with the job it does (role, e.g. "Breakout", "Pullback ladder", "Downside hedge"). Its tabs, first in the project: Overview (each wallet's P&L, its real money on a live project; every wallet over one price, each in its own colour: its stop while it holds, a trailing stop stepping with the price, its entry, its targets and each fill marked by what it did: buy, take part off, sell, short, cover; a table of each wallet's job, position, entry, stop and how far it is, next target, last action and P&L; a hedge's perp position; a note of how the desk works, about or one written from the roles; what the runs are doing) and Money (each wallet's P&L over time). A run says its levels with metric(): price (its close), stop, entry, target1, target2 and state (0 flat, more than 0 holding); a run that keeps none still shows its fills and money. A run on a perp beside spot runs is drawn on the same scale (its levels times the two closes' ratio), and the price drawn is the first perp run's. Each chart is a viz widget on the strategy-desk source, to change like any other; add_strategy_dashboard adds each run's own tabs beside it.

Arguments:

- `project` (required): string. The project id (list_projects).
- `name` (required): string. The tab group's name, e.g. "PEPE desk".
- `runs` (required): array of object
- `about`: string. The note on how the desk works; one is written from the roles when left out.

### `remove_running_strategy`

**Remove a running strategy** (replaces or deletes).

Stop a running strategy and delete it with its paper book ({ deployment, closeOut? }). Trades it already sent to a project stay in that project: a run on a project's perp leaves each wallet's position open, no longer guarded by its stopPct or takePct, and the answer lists what each wallet still holds (nothing when they are flat). closeOut: true closes those positions first (as control_strategy_close_out).

Arguments:

- `deployment` (required): string
- `closeOut`: boolean. Close each wallet's perp position (or unwind a hedged run) before removing.

## Strategy search across markets

### `get_perp_universe`

**Perps history the platform keeps** (read).

Every Hyperliquid and Lighter perp, delisted ones too, whose daily candles and hourly funding the platform keeps for searches across markets (3 years of candles, 2 of funding, kept current). Returns venues: for each, markets, delisted, series and seriesBackfilled (two a market: candles and funding), oldest and newest (unix seconds of the daily candles kept).

No arguments.

### `backtest_rotation`

**Backtest a cross-sectional book** (read). Also answers to `backtest_cross_sectional`, `backtest_momentum_rotation`.

Backtest one cross-sectional book over Hyperliquid's or Lighter's perps and check it against luck: every rebalanceDays it ranks the universe most traded perps by a score, holds the top longPct long and the bottom shortPct short (equal weight or by 1 / volatility), pays fees and slippage on every change and each market's funding, from the platform's own copy of the venue's history. { venue?, score?, args? (the score's arguments), book? (universe 25, longPct 0.2, shortPct 0.2, rebalanceDays 7, weighting equal unless named), from?, to?, holdoutPct?, feeBps?, slippageBps?, gross?, baselines?, trials? }. trials: how many settings you have tried so far, this one included; the deflated Sharpe ratio marks the result down for each, so the tenth tweak cannot pass as the first. Returns window (holdoutFrom: where the holdout starts), data (markets kept, ranked, delisted among them), best: candidate { score, args, book }, flowscript (the score as a program, the book in its header), program (the pick as a program of its own, its book on a // @rotation line and its arguments bound: give it back as score: { source } to backtest it again, or save it), train, holdout and full (returnPct, annualReturnPct, sharpe, sortino, maxDrawdownPct, volatilityPct, skew, kurtosis, days, turnover, feesPct, fundingPct (paid; negative earned), rebalances, avgHeld), holdings (the last book), baselines (each random ranking and stale score's Sharpe over train, holdout and full, and how many matched the candidate on each), deflated { trials, dsr: the deflated Sharpe ratio, the odds the edge is above zero after the tries; psr: the same for one try; benchmarkSharpe: the annual Sharpe the best of that many tries reaches by luck }, robustness { phase (the book started on each day of its rebalance cycle: returnPct, sharpe), neighbours (each setting one value over: trainSharpe, holdoutSharpe) and kept (how many keep half its training Sharpe), bootstrap (block-resampled annual Sharpe at 5/50/95% and atOrUnderZeroPct), pbo (a search's probability of backtest overfitting, CSCV over 24 of its settings spread through its ranking; null for one backtest) }, verdict { label: promising | not proven | no edge, reasons (any of these checks that failed among them) }, benchmark (BTC held over each span), equity (weekly [ts, equity, BTC held]) and unmodelled. The holdout is reported but is not kept from you here: to search without looking at it, start_strategy_search.

Arguments:

- `score`: any. A score program: a built-in's name (momentum, risk-adjusted momentum, reversal, trend, carry), { builtin, params? }, { strategyId, params } (a saved strategy) or { name?, source, params }: Flowscript that publishes metric("score", …), read on each market's daily bars (bars.close(market, interval), funding.rate(market, interval) hourly); the highest scores go long, the lowest short. params: { name: [values] | { from, to, step } } for each argument the search may vary; left out, the arguments the program declares with a space are searched (const lookback = param(7, { options: [3, 7, 14] }) or { min, max, step }), and a backtest takes their defaults. The program may declare its book too, on a line of its own: // @rotation { universe: 25, long: 0.2, short: 0.2, every: 7, weight: "equal" }, each setting a literal or a declared param's name (every: every, with const every = param(7, { options: [3, 7, 14] })), searched over that param's space; a setting the caller names in book or genes wins.
- `args`: object. The score's arguments: { lookback: 7, skip: 0 }.
- `book`: object
- `trials`: number (≥ 1). Settings tried so far, this one included (1 unless named).
- `venue`: "hyperliquid" | "lighter". The venue whose perps are ranked (hyperliquid unless named); a book trades on one venue.
- `from`: number. Unix seconds; a year before to unless named.
- `to`: number. Unix seconds; today unless named.
- `holdoutPct`: number (≥ 10, ≤ 50). The last share of the window kept out of the fit and scored after (30 unless named).
- `feeBps`: number (≥ 0). Paid on every dollar traded (the venue's base taker fee unless named: 4.5 on Hyperliquid, 0 on Lighter).
- `slippageBps`: number (≥ 0). Also paid on every dollar traded (5 unless named).
- `gross`: number (≥ 0.1, ≤ 3). Positions as a multiple of equity, split between the sides (1 unless named).
- `baselines`: number (≥ 4, ≤ 100). Random and stale versions of the same book to compare against (20 unless named: 4 stale, the rest random).
- `seed`: number

### `start_strategy_search`

**Search for a cross-sectional strategy** (writes). Also answers to `genetic_search`, `evolve_strategy`, `search_strategies`.

Start a genetic search for a cross-sectional strategy over Hyperliquid's or Lighter's perps; it runs on the server (minutes) and get_strategy_search reads it. Each candidate is a score (one of scores, with a value for each argument) and a book (genes: universe [10,15,20,25,30,40,50], longPct and shortPct [0,0.1,0.2,0.3,0.5], rebalanceDays [1,2,3,5,7,10,14], weighting ["equal","inverseVol"]; name any to narrow it, one value to fix it). Generations of population candidates are scored by their annual Sharpe over the window without its holdout; the best two carry over and the rest are bred from tournaments, uniform crossover and mutation. Then the best is scored on the holdout no generation saw, against random and stale versions of its book, and by its deflated Sharpe ratio over every distinct setting the search tried. { venue?, scores? (all the built-ins unless named), genes?, from?, to?, holdoutPct?, population? (40, at most 80), generations? (25, at most 60), seed?, feeBps?, slippageBps?, gross?, baselines?, warmupDays? }. Returns { id, state }. One search at a time per account; it stops after 20 minutes with what it has.

Arguments:

- `scores`: array. At most 8.
- `genes`: object
- `population`: number (≥ 4, ≤ 80)
- `generations`: number (≥ 1, ≤ 60)
- `warmupDays`: number (≥ 30, ≤ 400). Days before from each score reads to warm up (150 unless named).
- `method`: "genetic" | "anneal" | "tpe" | "random". genetic (unless named): generations bred from tournaments. anneal: a few chains each stepping one setting over, taking worse steps less as it cools. tpe: Bayesian, drawing where the best quarter so far are dense. random: distinct settings at random, a baseline. All but genetic try population × generations settings.
- `venue`: "hyperliquid" | "lighter". The venue whose perps are ranked (hyperliquid unless named); a book trades on one venue.
- `from`: number. Unix seconds; a year before to unless named.
- `to`: number. Unix seconds; today unless named.
- `holdoutPct`: number (≥ 10, ≤ 50). The last share of the window kept out of the fit and scored after (30 unless named).
- `feeBps`: number (≥ 0). Paid on every dollar traded (the venue's base taker fee unless named: 4.5 on Hyperliquid, 0 on Lighter).
- `slippageBps`: number (≥ 0). Also paid on every dollar traded (5 unless named).
- `gross`: number (≥ 0.1, ≤ 3). Positions as a multiple of equity, split between the sides (1 unless named).
- `baselines`: number (≥ 4, ≤ 100). Random and stale versions of the same book to compare against (20 unless named: 4 stale, the rest random).
- `seed`: number

### `get_strategy_search`

**Read a strategy search** (read).

A search as it stands: { id, state: running | done | failed, progress { generation, generations, trials, bestTrainSharpe, scoresRead, stage }, error?, report? }. Once done, report holds window, data, costs, settings, trials (distinct settings scored), generations (each one's best and median training Sharpe), best: candidate { score, args, book }, flowscript (the score as a program, the book in its header), program (the pick as a program of its own, its book on a // @rotation line and its arguments bound: give it back as score: { source } to backtest it again, or save it), train, holdout and full (returnPct, annualReturnPct, sharpe, sortino, maxDrawdownPct, volatilityPct, skew, kurtosis, days, turnover, feesPct, fundingPct (paid; negative earned), rebalances, avgHeld), holdings (the last book), baselines (each random ranking and stale score's Sharpe over train, holdout and full, and how many matched the candidate on each), deflated { trials, dsr: the deflated Sharpe ratio, the odds the edge is above zero after the tries; psr: the same for one try; benchmarkSharpe: the annual Sharpe the best of that many tries reaches by luck }, robustness { phase (the book started on each day of its rebalance cycle: returnPct, sharpe), neighbours (each setting one value over: trainSharpe, holdoutSharpe) and kept (how many keep half its training Sharpe), bootstrap (block-resampled annual Sharpe at 5/50/95% and atOrUnderZeroPct), pbo (a search's probability of backtest overfitting, CSCV over 24 of its settings spread through its ranking; null for one backtest) }, verdict { label: promising | not proven | no edge, reasons (any of these checks that failed among them) }, top (the ten best by training Sharpe with their holdout Sharpe: how much the ranking held up), benchmark, equity and unmodelled.

Arguments:

- `id` (required): string

### `list_strategy_searches`

**List strategy searches** (read).

Your latest searches, newest first: id, state, progress, and the best's score, verdict and holdout Sharpe once done.

No arguments.

### `start_rotation`

**Paper-trade a cross-sectional book** (writes). Also answers to `run_rotation`, `paper_trade_rotation`.

Run a cross-sectional book on a project wallet's paper perps account, as backtest_rotation tests it: after each daily close it is due on (every rebalanceDays), it ranks the universe most traded perps by the score on that close and moves each market to its target (only the difference trades; a market it no longer holds is closed). { project, wallet (it owns that wallet's positions on the venue), score (a search's best.program as { source }, { strategyId } or a built-in's name), usd (100 to 1000000: the capital it sizes from, or the account's equity when less; deposit to the account first with run_perps_move), book?, args?, venue?, gross?, name? }. Paper projects only; 3 books at once. The first rebalance is within a minute. Returns the run: id, program (its book on the // @rotation line), args, book, nextAt, last (the latest rebalance: day, equity, capital, book with weights and scores, trades), history (equity at each rebalance), error.

Arguments:

- `project` (required): string
- `wallet` (required): string
- `score` (required): any. A score program: a built-in's name (momentum, risk-adjusted momentum, reversal, trend, carry), { builtin, params? }, { strategyId, params } (a saved strategy) or { name?, source, params }: Flowscript that publishes metric("score", …), read on each market's daily bars (bars.close(market, interval), funding.rate(market, interval) hourly); the highest scores go long, the lowest short. params: { name: [values] | { from, to, step } } for each argument the search may vary; left out, the arguments the program declares with a space are searched (const lookback = param(7, { options: [3, 7, 14] }) or { min, max, step }), and a backtest takes their defaults. The program may declare its book too, on a line of its own: // @rotation { universe: 25, long: 0.2, short: 0.2, every: 7, weight: "equal" }, each setting a literal or a declared param's name (every: every, with const every = param(7, { options: [3, 7, 14] })), searched over that param's space; a setting the caller names in book or genes wins.
- `usd` (required): number (≥ 100, ≤ 1000000)
- `args`: object. The score's arguments; its declared defaults unless named.
- `book`: object
- `venue`: "hyperliquid" | "lighter". The venue whose perps are ranked (hyperliquid unless named); a book trades on one venue.
- `gross`: number (≥ 0.1, ≤ 3). Positions as a multiple of equity, split between the sides (1 unless named).
- `name`: string

### `get_rotation`

**Read a running book** (read).

A cross-sectional book start_rotation runs: state (running, stopped, failed), program, args, book, nextAt, last (the latest rebalance and its trades), history (equity at each rebalance) and error (why the last rebalance could not run or trade fully).

Arguments:

- `id` (required): string

### `list_rotations`

**List running books** (read).

Your cross-sectional books, newest first, running and stopped.

No arguments.

### `control_rotation`

**Stop a running book** (writes). Also answers to `stop_rotation`.

Stop a cross-sectional book: { id, action: "stop", close? }. It trades no more; with close: true every position it holds on its venue is closed first, otherwise they stay as they are.

Arguments:

- `id` (required): string
- `action`: "stop"
- `close`: boolean

## Reading links

### `read_link`

**Read a post, thread or article** (read). Also answers to `read_tweet`, `read_article`, `fetch_link`.

Read what a link says, as text: { url, from? }. A post on X (x.com or twitter.com) comes with its author's whole thread, each post numbered ("[3/12, the linked post]"), what each quotes, its photos and videos under media, and each video's transcript from X's captions (up to 3 videos). Any other https link is read as an article: title, author, published date and body, without the page around it. Returns { kind: x_post | x_thread | article, url, title?, author?, published?, site, posts?, media [{ post, type, url, duration?, transcript: captions | no captions | not read }], links (where it points; read one in turn when the ideas are there), text (up to 15,000 characters), from, chars (the whole text), next (pass as from to read on; absent on the last page), pages (every page's from and, in a transcript, the time it starts at: read the ones you need together, several read_link calls in one step), notes (what is missing: a video without captions, a paywall) }. Photos are not read. A PDF or a page that needs a sign-in is refused; ask for the text. The get_skill { name: "ftw-ideas" } skill turns what it says into a strategy.

Arguments:

- `url` (required): string. The link, starting https:// (http is read as https).
- `from`: number (≥ 0). Character to read on from: the last answer's next (0 unless named).

## Pools and market data

### `list_pools`

**Indexed pools** (read).

The account's workspace: its chains, the chains our hosted RPC serves (hostedRpc), and every pool it indexes, with each one's key, kind (including curve for a launched token still on its bonding curve), venue, base and quote, last price, how far its history is read (progress forward to the head; history.progress for one read back behind its live reads), and whether it tracks liquidity.

No arguments.

### `list_chains`

**What works on each chain** (read).

What ftw.trade does on each chain (the multi-chain registry): live trading and its venues, live liquidity, whether wallets hold funds there (withdraw, sweep, deposit), Relay and Circle CCTP reach, its dollars and their addresses, each protocol's markets there (and which act live), its launchpads, and whether our RPC reads it; plus the perps ledgers and how funds reach them. { chain } for one. Read it before planning anything across chains.

Arguments:

- `chain`: string. A chain name, such as base or solana; every chain when left out.

### `add_chain`

**Add a chain** (writes).

Give a chain your own RPC, or put it back on ours: { chain (lowercase name, e.g. base), rpcUrl } or { chain, hosted: true }. A chain we host (list_pools: hostedRpc.chains) needs no adding: add_pool on it reads through our RPC, metered against a free daily quota. Add a chain we do not host with its rpcUrl before indexing its pools.

Arguments:

- `chain` (required): string
- `rpcUrl`: string
- `hosted`: boolean

### `add_pool`

**Index a pool** (writes).

Start indexing a pool on a chain we host (read through our RPC, no add_chain needed) or one in the workspace with your own RPC, reading back hours of history (the default when left out). An EVM pool tracking swaps is live at the head at once (orders and live runs need only that) and reads its history back behind it: list_pools shows that as history.progress, and its prices reach your Robinhood indexer dataset once it is done. track liquidity also reads its adds and removes (V3/V4 positions), for liquidity history and liquidity by price. invert flips which token is base. A trade, order, range, project market or backtest on a pool not here yet adds it on its own, with a day of history, on a chain we host; add it here first for more history, liquidity tracking or a name.

Arguments:

- `chain` (required): string
- `address` (required): string. The pool contract (0x and 40 hex), a Uniswap V4 pool id (0x and 64), or a Solana pool account.
- `hours`: number (> 0)
- `name`: string
- `invert`: boolean
- `track`: "swaps" | "liquidity"

### `search_pools`

**Find pools by pair** (read).

The Uniswap V2 and V3 pools between two tokens on a chain we trade on (robinhood when left out), deepest first by liquidity at the price now, each with its key and whether it is in the workspace. A token is its address, ETH, or a symbol a pool here already names (USDG, NVDA). Pass a key to any pool tool, order, range or project: a pool not here yet is added on use.

Arguments:

- `chain`: string
- `tokenA` (required): string. A token address (0x and 40 hex), ETH, or a symbol.
- `tokenB` (required): string. The other token, the same way.

### `get_pool`

**A pool now** (read).

One indexed pool: what the workspace says of it, its day's stats (volume, trades, change) and what it holds now.

Arguments:

- `key` (required): string. The pool key, chain:0x… (a V4 pool id too) or solana:<base58>, as list_pools gives it.

### `get_pool_candles`

**Pool candles** (read).

An indexed pool's OHLCV candles, oldest first, ending before `before` (unix seconds; now by default). secs is one of 1, 5, 15, 60, 300, 900, 3600, 14400, 86400.

Arguments:

- `key` (required): string. The pool key, chain:0x… (a V4 pool id too) or solana:<base58>, as list_pools gives it.
- `secs`: 1 | 5 | 15 | 60 | 300 | 900 | 3600 | 14400 | 86400
- `before`: integer
- `limit`: integer (≥ 10, ≤ 2000)

### `list_pool_swaps`

**Pool trades** (read).

An indexed pool's trades, newest first: before a block, optionally by one trader.

Arguments:

- `key` (required): string. The pool key, chain:0x… (a V4 pool id too) or solana:<base58>, as list_pools gives it.
- `before`: integer. A block number.
- `limit`: integer (≥ 1, ≤ 500)
- `trader`: string

### `get_pool_liquidity`

**Pool liquidity** (read).

A pool indexed with track liquidity. view history (the default): each bucket's closing liquidity in base and quote, with the adds and removes, before `before` (unix seconds), secs one of 60, 300, 900, 3600, 14400, 86400. view by_price (V3/V4 only): bins steps from the price now ÷ span to × span, each with what its positions hold there; building is true while positions are still being read back.

Arguments:

- `key` (required): string. The pool key, chain:0x… (a V4 pool id too) or solana:<base58>, as list_pools gives it.
- `view`: "history" | "by_price"
- `secs`: 60 | 300 | 900 | 3600 | 14400 | 86400
- `before`: integer
- `limit`: integer (≥ 10, ≤ 2000)
- `bins`: integer (≥ 1). How many price steps; 60 by default.
- `span`: number (> 1). How far from the price now, as a multiple; 4 by default.

### `search_market_data`

**Shared market data** (read). Also answers to `shared_catalog`.

The market data every project can show and every strategy can read, free: crypto, stocks, commodities and lending rates, with each series name and its value now. { search?, category?, limit? }: search is words that must all match (e.g. "eth usd"), best match first; with no search it lists the categories and a few of each. A price a strategy backtests on is list_backtest_sources' feed:<name>.

Arguments:

- `search`: string. Words to look for in the name, label or source, e.g. "eth usd" or "aave usdc supply".
- `category`: string. Only this category (crypto, stocks, …; each answer names them).
- `limit`: integer (≥ 1, ≤ 500). The most series listed (50).

## Perps and bridges

### `list_perps_markets`

**Perps markets** (read). Also answers to `perps_markets`.

Perps and spot markets: with { venue: hyperliquid|lighter|derive, network?: mainnet|testnet } every market that venue lists now, the most traded first (symbol, kind perp|spot, mark, hourly funding as a fraction with fundingAprPct, the same as % a year, and fundingPays, which side pays it, open interest, highest leverage): Hyperliquid's perps, its builder-deployed perps (HIP-3, symbol dex:NAME like xyz:TSLA, stocks, indices and commodities) and its spot pairs (HYPE/USDC); Lighter's perps and spot pairs (ETH/USDC); Derive's perps (ETH-PERP, mainnet only; live, each wallet trades from its first Derive subaccount, which its first deposit makes). Narrow with kind (perp|builder|spot), q (part of a symbol) and limit (default 200). Spot trades at 1x: buy and sell, a short reverts. Without venue, the ones in the account's Your pools, with their live numbers and how much history is kept.

Arguments:

- `venue`: "hyperliquid" | "lighter" | "derive"
- `network`: "mainnet" | "testnet"
- `kind`: "perp" | "builder" | "spot"
- `q`: string
- `limit`: number

### `add_perps_market`

**Add a perps market** (writes).

Add a perps or spot market to Your pools ({ venue, network?: mainnet|testnet, symbol }: BTC, xyz:TSLA for a Hyperliquid builder perp, HYPE/USDC for spot). Its candles and funding load in the background; strategies then read it as perp:<venue>:<symbol>.

Arguments:

- `venue` (required): "hyperliquid" | "lighter" | "derive"
- `network`: "mainnet" | "testnet"
- `symbol` (required): string

### `remove_perps_market`

**Remove a perps market** (replaces or deletes).

Take a perps market out of Your pools ({ venue, network?, symbol }).

Arguments:

- `venue` (required): "hyperliquid" | "lighter" | "derive"
- `network`: "mainnet" | "testnet"
- `symbol` (required): string

### `get_project_perps`

**A project's perps accounts** (read). Also answers to `project_perps`.

A project's perps accounts and moves: each wallet's account on Hyperliquid and Lighter, testnet and mainnet, whether it may trade (testnet always; mainnet once turned on for that wallet and venue with update_perps_mainnet), with balances: true its equity, withdrawable USDC and positions as each venue says now; on a paper project, `paper`: each wallet's paper perps accounts with equity, margin, withdrawable, positions at the mark, funding, fees and realized P&L; and the perp orders, deposits, withdrawals and bridges its wallets sent.

Arguments:

- `id` (required): string. The project id (list_projects).
- `balances`: boolean

### `update_perps_mainnet`

**Let a wallet trade perps on mainnet** (trades or moves funds).

Turn a project wallet's mainnet account on a venue on or off ({ id, wallet, venue, mainnet: true|false }). Off by default; while off, nothing that wallet sends on that venue's mainnet leaves ftw.trade. Turning it on lets live workflows and strategies move real funds there: only do it when the person asked, and only from a connection allowed to trade live.

Arguments:

- `id` (required): string. The project id (list_projects).
- `wallet` (required): string
- `venue` (required): "hyperliquid" | "lighter" | "derive"
- `mainnet` (required): boolean

### `run_perps_move`

**Send a perp order or fund move** (trades or moves funds). Also answers to `perps_move`.

Send one perp order or fund move from a project's wallets now, for example { id: "ab12cd34", move: { kind: "deposit", venue: "hyperliquid", network: "mainnet", amount: 50, wallets: ["w1"] } } (wallets: "all", an id or a list of ids, inside move or beside it), paper or live as the project is (live trades real funds; the answer says which). On paper each wallet has a paper perps account per venue and network: a deposit pays for it from the wallet's paper ETH (amount in usdc/usdg dollars, or token: eth for ETH) at the ETH price then, orders fill at the mark against its margin (leverage 20x, or the market's highest if lower, unless named; set for the whole position, as on the venues) and pay the venue's taker fee, funding is paid each hour held, a position at maintenance margin is liquidated, and a withdraw takes what is free of margin back as paper ETH. Its equity counts in the wallet's value and P&L; an order before any deposit is refused. move is { kind: "perp", venue: hyperliquid|lighter|derive, network?: testnet|mainnet (testnet unless named), market: "BTC", side: long|short|close|target, usd (notional each wallet opens; not for close; for target the position to end at, signed: 250 is $250 long, -250 short, 0 flat, and the order is the difference from what the wallet holds, none under $10), share? (target only, in place of usd: the position to end at as a multiple of the wallet's own account equity, 0.5 half of it long, -0.5 half short), size? (close only: the units to close, at most what is held; the whole position unless named), leverage?, slippageBps?, maxImpactBps? } (an immediate-or-cancel order priced from the venue's live book as it is sent: the far side of the book moved by the slippage, 100 bps unless named; with maxImpactBps it takes only what rests within that many bps of the middle and cancels the rest), { kind: "deposit", venue, network?, amount (each wallet, in the token paid), from?: chain (arbitrum unless named), token?: usdc|usdg|eth|any symbol Relay bridges there } (Hyperliquid: USDC from Arbitrum through its bridge, anything else on mainnet through Relay, 5 USDC at least; Arbitrum Sepolia USDC on testnet. Lighter mainnet: through Relay to the wallet's account once it has one; its first deposit, which makes the account, is USDC through Lighter's CCTP deposit address; a deposit sent from the wallet itself on a chain, like USDC on Arbitrum, needs ETH there for gas, while one from robinhood goes through Relay and needs none; a deposit, or a bridge into a Lighter account, is refused while that venue refuses ftw.trade's server where it runs, since nothing there could then be traded or withdrawn from here. Derive (mainnet): Robinhood Chain ETH unless from/token say otherwise, through Relay as USDC to Derive's deposit address for the wallet's first subaccount, which the first deposit makes and Derive credits within minutes; its orders go IOC from that subaccount, its margin cross across the subaccount, so leverage is not set), { kind: "withdraw", venue, network?, amount or "all" } (to the wallet itself), or { kind: "bridge", from: chain, to: chain, token: a symbol (usdc, usdg, eth, sol, usdt…), toToken?, amount, prefer?: auto|cheapest|fastest, via?: relay|cctp } (by the best route: every bridge that carries it is quoted and the best sent, Relay on its 50+ chains and Solana, Circle CCTP for USDC between Circle's chains; to hyperliquid lands in the perps balance, to lighter or robinhood-lighter in that Lighter account by its index (Relay only); outputs what the route quoted to arrive; on paper it is quoted only, nothing moves, and it is refused above what the wallet is worth; get_bridge_quote first shows every route); each with wallets. Answers each wallet's leg.

Arguments:

- `id` (required): string. The project id (list_projects).
- `move` (required): object
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

### `get_bridge_quote`

**Quote a bridge** (read). Also answers to `quote_bridge`, `bridge_routes`.

Every route for moving funds between chains from a project's wallets, ranked, and the one ftw.trade would send; nothing is sent. { id, from: chain, to: chain, token (usdc, eth, sol, usdg, …), toToken? (to swap on the way, like usdc to sol), amount (each wallet), prefer?: auto|cheapest|fastest, via?: relay|cctp, wallets? }. Each bridge that carries the move is asked at once (Relay: any token on 50+ chains, Solana and the perps ledgers, seconds; Circle CCTP: USDC to USDC between Circle's chains, fast ~20 s for about a basis point, or standard with no protocol fee in ~15 min from Ethereum and its rollups). auto takes what lands the most within two minutes, waiting longer only to save 5 bps or more. Answers each route's amount to land, the least it may land, its cost in dollars and its time, why the best is best, and which bridges do not carry it. To hyperliquid from another chain, `cheaper` names the two steps through USDC on Arbitrum (then Hyperliquid's own bridge, which needs a little Arbitrum ETH for gas) when they cost at least half less, as a small move straight to the perps balance often does. Send it with run_perps_move { kind: "bridge", … }.

Arguments:

- `id` (required): string. The project id (list_projects).
- `from` (required): string
- `to` (required): string
- `token` (required): string
- `toToken`: string
- `amount` (required): number
- `prefer`: "auto" | "cheapest" | "fastest"
- `via`: "relay" | "cctp"
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

### `get_project_bridges`

**A project's bridge transfers** (read). Also answers to `project_bridges`, `bridge_transfers`.

A project's bridge transfers, newest first, each from leaving to landing: the route it took (relay, or cctp fast/standard), what it should land, and its state as its bridge says now (sent, landed, failed or refunded), with the transactions on each side.

Arguments:

- `id` (required): string. The project id (list_projects).

## Prediction markets

### `list_prediction_markets`

**Prediction markets** (read). Also answers to `polymarket_markets`.

Polymarket's open events, the most traded first ({ q?: search, tag?: politics|crypto|sports|… , sort?: volume|liquidity|ending|new, limit?: 1-100, default 20, offset? }), each with its open markets and every outcome's price (the probability, 0 to 1), its token and its series poly:<token id>, which strategies backtest and run on paper like spot. A live project's wallets trade it with place_prediction_order, and strategies run on it live with an account { project, usdPerBuy }.

Arguments:

- `q`: string
- `tag`: string
- `sort`: "volume" | "liquidity" | "ending" | "new"
- `limit`: number
- `offset`: number

### `get_prediction_market`

**A prediction market** (read). Also answers to `polymarket_market`.

One Polymarket event by its slug ({ slug }, as in polymarket.com/event/<slug>) with all its open markets, or one outcome by its token ({ token }) with its market; with interval (5m|1h|1d) and bars (default 100, at most 500), that outcome's price as bars.

Arguments:

- `slug`: string
- `token`: string
- `interval`: "5m" | "1h" | "1d"
- `bars`: number

### `get_prediction_wallets`

**A project's Polymarket wallets** (read). Also answers to `polymarket_wallets`.

What each of a live project's wallets holds on Polymarket ({ id, wallets?, token? }): pUSD to trade with (USD), and with token the shares of that outcome held. Polymarket trades from the wallet itself, an account on Polygon: run_prediction_setup makes a wallet ready first.

Arguments:

- `id` (required): string. The project id (list_projects).
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }
- `token`: string

### `run_prediction_setup`

**Make wallets ready for Polymarket** (trades or moves funds). Also answers to `polymarket_setup`.

Make a live project's wallets ready to trade on Polymarket ({ id, wallets? }), as polymarket.com's Enable trading does: each wallet's USDC.e on Polygon turned into pUSD (Polymarket's collateral) and pUSD and its shares approved to Polymarket's exchanges, skipping what is done. Fund each wallet on Polygon first with USDC.e (or USDC) and a little POL for gas, for example with run_perps_move's bridge to polygon. Sends real transactions: only when the person asked, from a connection allowed to trade live.

Arguments:

- `id` (required): string. The project id (list_projects).
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

### `place_prediction_order`

**Buy or sell a prediction** (trades or moves funds). Also answers to `polymarket_order`.

Buy or sell one Polymarket outcome from a live project's wallets now ({ id, token, side: buy|sell, usd (each wallet spends, for a buy), pct? (of the shares each holds, for a sell; 100 unless named), wallets? }): a market order, filled whole at the book within 3% of the best price or not at all. With limitPrice (the chance to rest at, on the market's tick, get_order_book shows the book) it is a limit order that rests on the book until it fills, lapses or is cancelled: shares? (else usd ÷ limitPrice for a buy, pct of the shares held for a sell), postOnly? (refused rather than taking: a maker order only), expiresAt? (unix seconds, at least 90 s ahead; good until cancelled when left out). Whatever crosses the book fills at once unless postOnly. get_prediction_orders lists what rests; remove_prediction_orders cancels. token is the outcome's token from list_prediction_markets. Answers each wallet's leg: shares and USD filled, and a resting order's id and status. Real money: only when the person asked, from a connection allowed to trade live.

Arguments:

- `id` (required): string. The project id (list_projects).
- `token` (required): string
- `side` (required): "buy" | "sell"
- `usd`: number
- `pct`: number
- `limitPrice`: number
- `shares`: number
- `postOnly`: boolean
- `expiresAt`: number
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

### `get_prediction_orders`

**Resting prediction orders** (read). Also answers to `polymarket_orders`, `list_prediction_orders`.

The limit orders a live project's wallets have resting on Polymarket ({ id, token?: one outcome, wallets? }): each order's id, outcome token, side, price, shares asked and matched so far, status, type (GTC or GTD) and when it lapses.

Arguments:

- `id` (required): string. The project id (list_projects).
- `token`: string
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

### `remove_prediction_orders`

**Cancel prediction orders** (trades or moves funds). Also answers to `cancel_prediction_order`, `cancel_prediction_orders`.

Cancel resting Polymarket limit orders from a live project's wallets ({ id, orderId? | token?, wallets? }): one order by its id (get_prediction_orders), or every order the wallets have on one outcome. Answers what each wallet cancelled, and what was not and why (already filled, already cancelled).

Arguments:

- `id` (required): string. The project id (list_projects).
- `orderId`: string
- `token`: string
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

## Market changes

### `list_market_changes`

**Markets opened and closed** (read). Also answers to `market_changes`, `new_markets`.

What changed in the markets Hyperliquid, Lighter and Polymarket list, newest first ({ venue?: hyperliquid|lighter|polymarket, change?: listed|delisted|relisted|closed|resolved|reopened, or opened (listed, relisted, reopened) or ended (delisted, closed, resolved), kind?: perp|builder perp|spot|prediction, q?: search, short?: false to leave out markets open under a day (Polymarket lists hundreds of 15-minute and hourly ones a day), sinceHours?, limit?: 1-200, default 50, before?: an id to page on }): a market listed, delisted or listed again, a prediction closed or resolved (with the outcome that won). With no filter it also answers how many markets each venue has open and how many opened and ended in the last day and week. Checked every 5 minutes. Sources and workflows react to the same as they happen with the markets stream.

Arguments:

- `venue`: "hyperliquid" | "lighter" | "polymarket"
- `change`: "listed" | "delisted" | "relisted" | "closed" | "resolved" | "reopened" | "opened" | "ended"
- `kind`: "perp" | "builder perp" | "spot" | "prediction"
- `q`: string
- `short`: boolean
- `sinceHours`: number
- `limit`: number
- `before`: number

### `get_market_status`

**A market's status** (read). Also answers to `market_status`.

Whether a market is open, delisted, closed or resolved, as the market tracker last saw it ({ venue, id }: a perps or spot market's symbol, BTC or xyz:TSLA or HYPE/USDC; a Polymarket market's id), with when it was first seen, closed or resolved and a resolved prediction's winning outcome.

Arguments:

- `venue` (required): "hyperliquid" | "lighter" | "polymarket"
- `id` (required): string

## Order books

### `get_order_book`

**A market's order book** (read). Also answers to `order_book`, `orderbook`.

One market's order book now ({ venue: polymarket|hyperliquid|lighter|derive, market, levels?: 1-20, default 10 }): market is a Polymarket outcome's token id (list_prediction_markets names each), a Hyperliquid or Lighter symbol as the venue spells it (BTC, a builder perp xyz:TSLA, a spot pair HYPE/USDC), or a Derive instrument (ETH-PERP, ETH-20261030-2400-P). Answers the best bid and ask and their sizes, the microprice, the spread, the USD resting on each side of the top 20 levels and within 10, 25, 50 and 100 bps of the middle, and the levels as [price, size], best first (a Polymarket price is the outcome's chance, 0 to 1). Read from the venue's live socket. To react to it as it changes, subscribe: a Flowscript source with from: { stream: "books", where: { venue, market } } (save_flow_source) gets each change at most once a second, and workflows trigger on that source; a page or a bot holds GET /api/workspace/books/stream?venue=&market= (server-sent events).

Arguments:

- `venue` (required): "hyperliquid" | "lighter" | "polymarket" | "derive"
- `market` (required): string
- `levels`: number

### `update_book_recording`

**Record a market's order book** (writes). Also answers to `record_order_book`, `record_book`.

Start (or with on: false, stop) recording one market's order book every 15 s, so a strategy reading book.* can be backtested on it ({ venue: polymarket|hyperliquid|lighter|derive, market, on?: true }). Recording starts within half a minute and builds history from then on: the venues keep none. A perps market in Your pools and the market of a running strategy that reads the book are recorded without asking. Snapshots are kept every 15 s for a week, then one a minute for 90 days. At most 10 books an account. Answers the account's recordings, each with when its newest snapshot was taken.

Arguments:

- `venue` (required): "hyperliquid" | "lighter" | "polymarket" | "derive"
- `market` (required): string
- `on`: boolean

### `get_book_history`

**A market's recorded order book** (read). Also answers to `book_history`, `list_recorded_books`.

The order book build recorded for one market, as a strategy's book.* reads see it ({ venue, market, from?, to? (unix seconds, default the last hour), every?: seconds between rows, default 15 }): each row is a snapshot's bid, ask, mid, micro, spread (bps), bidSize, askSize, bidDepth, askDepth, imbalance, bids10…asks100, imbalance10…imbalance100 and levels 2 to 5 (bid2, bidSize2 … askSize5). The newest 500 rows; more says earlier ones were left out. Without venue and market it lists the books this account asked to record (update_book_recording).

Arguments:

- `venue`: "hyperliquid" | "lighter" | "polymarket" | "derive"
- `market`: string
- `from`: number
- `to`: number
- `every`: number

## Liquidations

### `get_liquidations`

**A perp's liquidation map** (read). Also answers to `liquidation_map`, `liquidation_heatmap`, `liquidations`.

Where open positions on a Hyperliquid perp would be liquidated, read from the accounts themselves (Hyperliquid's are public, each position states its liquidation price), not estimated from volume: { market: BTC|ETH|ZEC|SOL|HYPE (or perp:hyperliquid:<coin>), within?: percent, minUsd?: USD, side?: long|short, leverage?: "high" (10x and up only), clusters?: how many buckets each side, default 12 }. Answers the newest map (5-minute), the newest summary (a minute): USD to be liquidated within 1/2/5/10% below (longs) and above (shorts), the densest 0.5% cluster each side within 3% (nearLong/nearShort) and 10% (long/short) with its price and USD, the imbalance ((shorts − longs) ÷ both within 10%), and coverage: the USD of positions read against the venue's open interest (accounts are found as they trade, so coverage grows; big positions are read again every 10 minutes). With within and minUsd it also answers the nearest cluster of at least minUsd within that percent, the preset alert's test. To be told without watching: a Flowscript source on the "liquidations" stream with where: { symbol: "BTC", nearShortDistPct: { lte: 0.5 }, nearShortUsd: { gte: 5000000 } } (save_flow_source), and a workflow or automation on it; a strategy reads the same numbers bar by bar as liq.longs5(market, interval), liq.nearShortPx(…) and the rest. Research (2 Nov 2026): the usual estimated heatmap held no edge on 12 Hyperliquid perps (as a magnet, a direction or a fade); this real map is recorded from 2 Nov, so test before trusting it.

Arguments:

- `market` (required): string
- `within`: number
- `minUsd`: number
- `side`: "long" | "short"
- `leverage`: "all" | "high"
- `clusters`: number

### `get_liquidation_history`

**A perp's liquidation summaries over time** (read). Also answers to `liquidations_history`.

The liquidation summaries recorded for a Hyperliquid perp, as liq.* reads them ({ market, from?, to? (unix seconds, default the last 6 hours), every?: seconds between rows, default 300 }): each row's mid, longs1…longs10, shorts1…shorts10, imbalance, the clusters (longPx, longUsd … nearShortUsd), covered and openInterest. The newest 500 rows.

Arguments:

- `market` (required): string
- `from`: number
- `to`: number
- `every`: number

## Derive options

### `get_derive_markets`

**Derive's options markets** (read).

Every underlying Derive lists options on ({}), with its spot price in USD and its move over 24 hours in percent. Pass one as currency to get_derive_chain.

No arguments.

### `get_derive_chain`

**Derive's options chain** (read).

Derive's options on an underlying at one expiry ({ currency: ETH, BTC or any get_derive_markets lists, expiry?: unix seconds or YYYYMMDD; the soonest when left out, compare?: 'deribit' }): the live expiries, the index and forward, and a row per strike with its call and put, each with bid, ask and their sizes, mark, IV in percent, delta, gamma, theta, vega and open interest; minAmount, amountStep and tick say the least size, its step and the price tick. Prices are in USD per contract of one underlying. With compare: 'deribit' each row also carries deribit: { call, put }, Deribit's bid, ask, mark and IV at the same expiry and strike, in USD (null where Deribit lists none; deribitError says why when Deribit did not answer).

Arguments:

- `currency`: string. An underlying Derive lists options on: ETH, BTC, or any get_derive_markets lists
- `expiry`: number
- `compare`: "deribit"

### `get_derive_quote`

**A Derive quote** (read).

One Derive instrument's quote ({ instrument }): bid, ask, their sizes, mark and index, and on an option its IV and greeks.

Arguments:

- `instrument` (required): string. A Derive instrument: ETH-20261030-2400-P (option: underlying, expiry day, strike, C or P) or ETH-PERP

### `get_derive_book`

**A Derive order book** (read).

A Derive instrument's order book now ({ instrument, depth?: 1|10|20|100, 10 by default }): bids and asks as [price, amount], best first.

Arguments:

- `instrument` (required): string. A Derive instrument: ETH-20261030-2400-P (option: underlying, expiry day, strike, C or P) or ETH-PERP
- `depth`: 1 | 10 | 20 | 100

### `get_project_derive`

**A project's Derive accounts** (read). Also answers to `project_derive`.

Each project wallet's Derive account ({ id }). Live: its subaccounts as Derive reports them (USDC and other collateral, positions with mark and average price, open orders, value and margin) and deposits Derive has seen but not credited yet. Paper: its paper subaccounts at Derive's marks (equity, cash, what positions lock, what can be withdrawn, fees, realized P&L, each position's value).

Arguments:

- `id` (required): string. The project id (list_projects).

### `run_derive_move`

**Trade on Derive from project wallets** (trades or moves funds). Also answers to `derive_move`.

Send one Derive move from a project's wallets now, paper or live as the project is (live moves real funds; the answer says which): { id, move, wallets? ("all", an id or ids; inside move or beside it) }. move is { kind: "deposit", amount (USD, at most 250), subaccount?: id | "new" } (live: ETH on Robinhood Chain bridged by Relay into USDC at the deposit address Derive keeps for the wallet; the first deposit, or "new", makes a subaccount once Derive's keeper sweeps it, usually minutes; get_project_derive shows it pending, then credited), { kind: "order", instrument (ETH-20261030-2400-P, ETH-PERP), side: buy|sell, amount (contracts), limitPrice? (USD a contract; without one it takes the book now, no worse than 5% past the quote, and what does not fill is cancelled), subaccount? }, { kind: "close", instrument, subaccount? } (the whole position, at the market), { kind: "cancel", orderId?, subaccount? } (one open order, or all of them), or { kind: "withdraw", amount (USD) | "all", subaccount? } (live: USDC to the wallet on Ethereum once Derive's batch is proven, a fee of at most $1). subaccount is needed only once a wallet has more than one. On paper an order fills at once at Derive's quote (buy at the ask, sell at the bid) with its taker fee, or is refused: a short put locks its strike, a short call the index and a perp its notional, so paper never liquidates; options settle at expiry at Derive's settlement price. An order or deposit is at most $250 of premium, notional or USDC. Answers each wallet's leg.

Arguments:

- `id` (required): string. The project id (list_projects).
- `move` (required): object
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }

## Your trading terminal on ftw.trade

### `get_terminal_layout`

**Read your trading terminal** (read). Also answers to `get_my_terminal`.

The person's trading terminal on ftw.trade (Trade → Terminal): each tab with its grid, its charts (pane 1, 2, … with market, interval, studies and a strategy marked on it), its panels (watchlist, alerts, order books, strategy cards, notes), the numbers it watches (metrics, with alert lines), the watchlist and price alerts, and the version to pass to update_terminal_layout. With numbers: true, also each watched market's price, day move, funding, open interest and volume now.

Arguments:

- `numbers`: boolean

### `update_terminal_layout`

**Arrange your trading terminal** (writes). Also answers to `edit_terminal`, `arrange_terminal`.

Change the person's trading terminal in steps, saved together as one version they can undo: { ops, version? }. A tab is named by id or name; a chart by pane: 1, 2, … (1 is the large one in 1+3) or "all". A market is BTC (Hyperliquid), hyperliquid:ETH or lighter:SOL. Intervals: 1m, 5m, 15m, 1h, 4h, 1d. Grids: 1 (one chart), 2 (two side by side), 2x2 (four in a grid), 1+3 (one large on the left and three small on the right). Steps: { op: "add_tab", name, grid?, symbol? | symbols?: [one per chart], interval?, index? } (opens on it) | { op: "rename_tab", tab, name } | { op: "remove_tab", tab } | { op: "move_tab", tab, index } | { op: "duplicate_tab", tab, name? } | { op: "open_tab", tab } | { op: "set_grid", tab, grid } (new charts copy the active one's market) | { op: "set_links", tab, crosshair?, time?, symbol?, interval?, drawings? } (linked symbol or interval: changing one chart changes all) | { op: "set_chart", tab, pane, symbol?, interval?, chartType?: candles|hollow|volume-candles|bars|line|line-markers|step|area|hlc-area|baseline|columns|hilo|heikin-ashi|renko|line-break|footprint, strategy?: a running strategy's id (sd_…) whose buys and sells mark the chart, or null } | { op: "add_study", tab, pane, source: a study call such as rsi(14), ema(50), bb(20, 2), vwap(), tpo() (market profile), vpvr(), svp(), profileblocks() (list_chart_studies lists all), inputs? } | { op: "remove_study", tab, pane, study: its id, call or name } | { op: "clear_studies", tab, pane } | { op: "add_metric", tab, kind, symbol? (market kinds) | strategy? (strategy kinds), label?, above?, below? } (a number watched in the tab's strip; above/below alert when crossed; kinds: price: the mark price; change: the move over the last day, in percent; funding: funding per hour, in percent (positive: longs pay); open_interest: open interest in USD; volume: the last day's volume in USD; strategy_pnl: a running strategy's P&L since it started, in USD; strategy_equity: a running strategy's equity, in USD) | { op: "update_metric", tab, metric: its id, label?, above?, below? (null clears) } | { op: "remove_metric", tab, metric } | { op: "add_panel", tab, kind, place?: side|bottom, title?, symbol?, strategy?, text?, index? } (panels beside or under the charts; kinds: watchlist: the watchlist with each market's price and day move; a click charts it; alerts: the price alerts; book: a market's live order book (symbol); strategy: a running strategy's state, equity, P&L and latest fills (strategy: its sd_… id); note: the person's own text (text), a checklist or plan; a tab starts with panels watchlist and alerts at the side) | { op: "update_panel", tab, panel: its id, place?, title?, symbol?, strategy?, text? } | { op: "move_panel", tab, panel, place?, index? } | { op: "remove_panel", tab, panel } (remove both side panels to give the charts the full width) | { op: "set_watchlist", symbols } | { op: "watch", symbol } | { op: "unwatch", symbol } | { op: "add_alert", symbol, price, note? } (fires when the price crosses it) | { op: "remove_alert", alert: its id }. The steps run in order and the result is checked as a save is; a refusal names the step (ops[2].symbol) and nothing is saved. The page shows the change at once. The answer is the new version and the terminal as get_terminal_layout reads it. When the person says what they care about watching or how they want the screen, build the interface around it: tabs for each job, the charts for those markets with the studies that show it, metrics for the numbers with alert lines where they named a level, and the panels they need (an order book beside a scalping chart, a strategy card under its chart, a note with their plan); drop what they did not ask for.

Arguments:

- `ops` (required): array of object. The steps, in order (up to 100).
- `version`: integer. The version get_terminal_layout read; a save in between refuses.

### `restore_terminal_layout`

**Undo the last terminal change** (writes).

Put the person's trading terminal back as it was before its last change (by the page or by update_terminal_layout); up to 20 changes back.

No arguments.

### `list_chart_studies`

**Chart studies** (read).

The studies a terminal chart can show, by category, each as the call add_study takes (rsi(14), tpo(), vpvr(), …) with what it shows: { search? } narrows by words, e.g. "volume profile" or "trend".

Arguments:

- `search`: string

## Trading terminal

### `get_terminal_status`

**Trading terminal status** (read).

In the person's open trading terminal: Whether the person's perps terminal is open, the network, which venues it may trade on their own accounts, autopilot, and its runs. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

No arguments.

### `search_terminal_funding_pairs`

**Find funding pairs** (read).

In the person's open trading terminal: Rank funding pairs across Hyperliquid and Lighter now: the same asset long on one venue and short on the other, and (kind any or cross) two correlated assets hedged by beta. Answers ideas i1, i2, … with their yearly carry now and over the last month, correlation and the price risk left. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `assets`: array of string. Only pairs that touch these assets (BTC, ETH, SOL…).
- `kind`: "any" | "same" | "cross". same: one asset on both venues; cross: two correlated assets. Default any.
- `min_correlation`: number. Least correlation for a cross pair, 0.5 to 0.99. Default 0.75.
- `limit`: number. Ideas to return, 1 to 10. Default 6.

### `backtest_terminal_pair`

**Backtest a funding pair** (read).

In the person's open trading terminal: Backtest an idea from search_terminal_funding_pairs over the venues' hourly history as a Flowscript strategy (funding, fees, slippage, drift, rebalances, a loss limit). Answers strategy id s1, s2, … with its numbers and the strategy's source. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `idea` (required): string. An idea id (i1…).
- `usd`: number. The long leg in USD. Default 1000.
- `days`: number. Days of history, 7 to 90. Default 30.
- `enter_apr`: number. Open at this average carry, % a year.
- `exit_apr`: number. Close when it falls to this.
- `loss_limit`: number. Close both legs and stop once down this many USD.
- `avg_hours`: number. Hours the carry is averaged over. Default 24.

### `run_terminal_pair`

**Run a funding pair** (trades or moves funds).

In the person's open trading terminal: Start a backtested strategy in the terminal. paper trades its paper accounts (at once with autopilot on, else a Run button). live trades the person's own Hyperliquid and Lighter accounts and is only ever a Run button the person presses in the terminal; this answers that it is waiting. Optionally a watch: tell the person once it is down watch_loss_usd, closing it when watch_close. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `strategy` (required): string. A strategy id (s1…).
- `mode` (required): "paper" | "live"
- `watch_loss_usd`: number
- `watch_close`: boolean

### `create_terminal_strategy`

**Write and backtest a strategy** (read).

In the person's open trading terminal: Write a trading strategy on one perp market as a Flowscript program and backtest it on the venue's bars; the person sees a card with the result, its trades on the chart and the program. Answers its id cs1, cs2, … and the numbers. Either a template, kind trend (fast/slow EMA cross), meanrev (RSI under rsi_low buys, over rsi_high shorts, out at 50), breakout (a close past the last lookback closes) or funding (take the side the funding pays past funding_apr), with its settings; or your own `source`: `uses { perp: "^1" }`, then `// market: hyperliquid:ETH 1h` (venue hyperliquid or lighter, interval 5m 15m 1h 4h 1D), settings as `const name = number;` (with `const usd = …;`). It reads perp.open() high() low() close() volume() funding() and its position perp.side() entry() pnl() pnlPct() bars(); trades with perp.long({ usd: n }), perp.short({ usd: n }) and perp.close({}). `when (cond)` fires as cond turns true, `while (cond)` at each bar it holds; the prelude has ema sma rsi atr macd bb highest lowest crossover crossunder stoch adx supertrend vwap, `x[1]` the bar before; metric("name", x) shows a value and says why a trade fired. A program that does not compile comes back with its problems. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `kind`: "trend" | "meanrev" | "breakout" | "funding"
- `asset`: string
- `venue`: "hyperliquid" | "lighter"
- `interval`: "5m" | "15m" | "1h" | "4h" | "1D"
- `usd`: number. Position size, USD. Default 1000.
- `stop_pct`: number. Close at this loss, % of entry. Default 2; 0 for none.
- `take_pct`: number. Close at this gain, %. Default none.
- `shorts`: boolean. Also take the short side. Default true.
- `fast`: number
- `slow`: number
- `rsi_len`: number
- `rsi_low`: number
- `rsi_high`: number
- `lookback`: number
- `funding_apr`: number
- `days`: number. Days of bars to backtest on. Default 30 (14 under 1h bars, 120 on 4h, 365 on 1D).
- `source`: string. A whole program you wrote, instead of a template.

### `update_terminal_strategy`

**Change a strategy** (writes).

In the person's open trading terminal: Change a strategy (cs…) or a running one (its run id srun_…): settings by name ({"stopPct": 3}), or a whole new source. It is backtested again as a new version; a running strategy switches to it at its next bar close and keeps what it holds. A run on the person's own account is changed only from the terminal. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `strategy` (required): string
- `settings`: object
- `source`: string

### `run_terminal_strategy`

**Run a strategy** (trades or moves funds).

In the person's open trading terminal: Run a strategy (cs…) in the terminal, deciding at each bar close and taking the side it holds now. paper trades the paper account (at once with autopilot on, else a Run button). live trades the person's own account and is only ever a button they press in the terminal. Optionally a watch: tell the person once it is down watch_loss_usd, closing it when watch_close. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `strategy` (required): string
- `mode` (required): "paper" | "live"
- `watch_loss_usd`: number
- `watch_close`: boolean

### `search_terminal_strategies`

**Find strategies that held up** (read).

In the person's open trading terminal: Backtest a spread of strategies (trend, mean reversion, breakout and funding templates at a few settings, on 1h and 4h bars, plus the best funding pairs) on the busiest assets or the ones named, over two back-to-back windows of `days` each, and rank them by the worse window, so a result that only worked in one lucky stretch ranks low. Answers how many were tried, how many made money in both windows, and the best six; the person sees the best one's card. Say plainly when none held up. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `assets`: array of string. Assets to search; default the three busiest.
- `kinds`: array of string. Default all.
- `days`: number. Days in each window, 14 to 60. Default 30.

### `search_terminal_portfolios`

**Find portfolios that held up** (read).

In the person's open trading terminal: Backtest a few ways to mix the busiest markets on one venue, or the assets named: equal weight, risk-balanced (1 / volatility), 60/40, momentum (the strongest of the 30 days before) and market-neutral (long the strongest, short the weakest), mostly rebalanced weekly, and trend-filtered (daily, each leg in cash below its 20-day average), with weights picked from the 30 days before the test. Each runs over two back-to-back windows of `days` and they rank by the worse one. Answers how many held up and the best five; the person sees the best one's card, ready to run. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `assets`: array of string. Mix these; default the six busiest on the venue.
- `venue`: "hyperliquid" | "lighter"
- `days`: number. Each window, days, 14 to 60. Default 30.
- `usd`: number. The stake, USD. Default 1000.
- `mixes`: array of string. Only these kinds of mix (trend: rebalanced daily, each leg in cash below its 20-day average); default all.

### `get_terminal_market_ranking`

**Rank the perp markets** (read).

In the person's open trading terminal: Rank the markets on Hyperliquid and Lighter: by move (each asset's return over the last `days` from daily bars, among the 15 busiest), funding (% a year now; positive is longs paying) or volume (24h). Answers the top and bottom five. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `by` (required): "move" | "funding" | "volume"
- `days`: number. move only: 1 to 90. Default 30.

### `get_terminal_result_explanation`

**Explain a backtest or run** (read).

In the person's open trading terminal: How a backtest or run in the terminal came out and why: its verdict, what made and cost the most, what it is measured against, its worst dip and what it made per dollar of that dip. ref is a strategy (s…, cs…), a portfolio (pf…) or a run; default the newest card. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `ref`: string

### `get_terminal_comparison`

**Compare backtests side by side** (read).

In the person's open trading terminal: Two to four backtests in the terminal side by side: each one's verdict, profit, return on its stake, days, worst dip and what it made per dollar of that dip, and which did best. refs are strategies (s…, cs…) or portfolios (pf…); default the two newest. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `refs`: array of string

### `test_terminal_result_again`

**Re-test a backtest on another window** (read).

In the person's open trading terminal: Backtest a strategy (cs…) or portfolio (pf…) in the terminal again, unchanged, on another window: by default the same number of days just before the one it was tested on (out of sample), or a longer span up to now (days, ago_days 0). Answers both results and whether it held up; ref default the newest result. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `ref`: string
- `days`: number. Length of the window, 7 to 365. Default: the same as the first test.
- `ago_days`: number. The window ends this many days ago; 0 is up to now. Default: where the first test began.

### `create_terminal_portfolio`

**Build and backtest a portfolio** (read).

In the person's open trading terminal: Build a portfolio of perps on one venue and backtest it on the venue's bars and funding: weights by asset (negative is a short), rebalanced back to them every rebalance_hours, once a leg drifts drift_pct points, or both. The backtest sets it beside the same mix bought once and left alone, with each leg's share of the result. Answers its id pf1, pf2, … and the numbers; the person sees a card. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `legs` (required): array of object. One to ten assets: one at 100% with rebalance_hours 0 is holding it, the yardstick to compare against.
- `venue`: "hyperliquid" | "lighter"
- `usd`: number. The stake, USD. Default 1000.
- `rebalance_hours`: number. 24 daily, 168 weekly, 0 never. Default 168.
- `drift_pct`: number. Also rebalance once a leg is this many points off its weight.
- `days`: number. Days to backtest, 7 to 365. Default 30.
- `weighting`: "as_given" | "risk". risk: size each leg by 1 / its volatility over the 30 days before the test, keeping each weight's side.
- `trend_days`: number. A trend filter: hold each leg only while its price is on its side of its average over this many days (a long above, a short below), its weight in cash otherwise. Checked at each rebalance (daily if none is set). 5 to 200.
- `dca_buys`: number. Dollar-cost average: put the stake in over this many equal buys, one every rebalance_hours (default weekly), and never sell. Set beside buying it all at the start. 2 to 100.
- `leverage`: number. Hold this multiple of equity across the legs, 1 to 5. The backtest is liquidated if equity falls under the maintenance margin. Default 1.

### `update_terminal_portfolio`

**Change a portfolio** (writes).

In the person's open trading terminal: Change a portfolio (pf…) or a running one (prun_…): new weights (the whole list), rebalance_hours, drift_pct, trend_days, leverage or usd. It is backtested again as a new version; a running portfolio on paper takes the new weights at once. One on the person's own account is changed only from the terminal. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `portfolio` (required): string
- `legs`: array of object
- `rebalance_hours`: number
- `drift_pct`: number
- `usd`: number
- `weighting`: "as_given" | "risk"
- `trend_days`: number. A trend filter over this many days; 0 removes it.
- `leverage`: number. 1 to 5; 1 removes it.

### `run_terminal_portfolio`

**Run a portfolio** (trades or moves funds).

In the person's open trading terminal: Run a portfolio (pf…) in the terminal: buy its weights now and rebalance it on its schedule while the terminal is open. paper trades the paper account (at once with autopilot on, else a Run button). live trades the person's own account and is only ever a button they press in the terminal. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `portfolio` (required): string
- `mode` (required): "paper" | "live"

### `list_terminal_runs`

**Terminal runs** (read).

In the person's open trading terminal: The runs in the terminal: funding pairs (status, profit, funding, fees, drift, carry, loss limit, watch) and strategies (srun_…: side, entry, mark, open and total profit, the program's values, its last trades and why) and portfolios (prun_…: worth, profit, each leg's target and current weight, drift, next rebalance). Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

No arguments.

### `control_terminal_run`

**Pause, resume or close a run** (writes).

In the person's open trading terminal: Pause a run (it stops deciding; its loss limit holds), resume it, rebalance a portfolio run back to its weights now, or close it (on the person's own accounts that is a button they press). Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `run` (required): string. A run id from list_terminal_runs.
- `action` (required): "pause" | "resume" | "rebalance" | "close"

### `update_terminal_watch`

**Watch a run** (writes).

In the person's open trading terminal: Tell the person once a run is down more than loss_usd, closing it too when close. loss_usd 0 removes the watch. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `run` (required): string. A run id from list_terminal_runs.
- `loss_usd` (required): number
- `close`: boolean

### `add_terminal_monitor`

**Set a monitor in the terminal** (writes).

In the person's open trading terminal: Wake the terminal's assistant when something happens, while the terminal is open (D1274). what: price (an asset's mark, $), funding (an asset's funding, % a year; positive is longs paying), gap (the funding gap between Hyperliquid and Lighter on an asset, % a year) or pair (the best pair a scan finds, % a year, checked hourly). then: tell (a notification and a note in the terminal), find (then scan for pairs) or run (then pick the best pair and run it: on paper by itself only with the terminal's autopilot on, and on the person's own accounts only as a button they press). repeat_hours fires it again after that long while it holds; 0 is once. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `what` (required): "price" | "funding" | "gap" | "pair"
- `asset`: string. BTC, ETH… Needed except for pair.
- `venue`: "hyperliquid" | "lighter". Price and funding only: one venue; default either.
- `above`: number
- `below`: number. Price and funding only.
- `then`: "tell" | "find" | "run"
- `repeat_hours`: number

### `list_terminal_monitors`

**Terminal monitors** (read).

In the person's open trading terminal: The monitors set in the terminal: what each waits for, what it then does, and whether it fired. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

No arguments.

### `remove_terminal_monitor`

**Remove a terminal monitor** (writes).

In the person's open trading terminal: Remove a monitor (m1…) from the terminal, or all of them with "all". Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `monitor` (required): string

### `update_terminal_chart`

**Show a market on the terminal chart** (writes).

In the person's open trading terminal: Switch the terminal's chart to an asset, its venue and a bar interval (1m 5m 15m 30m 1h 4h 1D 1W); any left out stay. Answers what the chart shows, its indicators and the levels marked on it. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `asset`: string. BTC, ETH…
- `venue`: "hyperliquid" | "lighter"
- `interval`: string

### `add_terminal_indicator`

**Add an indicator to the terminal chart** (writes).

In the person's open trading terminal: Add an indicator to the chart by name (rsi, ema, sma, macd, bollinger, vwap, atr, supertrend, …), with an optional length for its first period. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `indicator` (required): string
- `length`: number

### `update_terminal_chart_levels`

**Mark a level on the terminal chart** (writes).

In the person's open trading terminal: Draw a labelled horizontal line on the chart at a price (an entry, a stop, a level to watch); clear true removes the lines the assistant drew instead. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `price`: number
- `label`: string
- `clear`: boolean

### `get_terminal_chart`

**Read the terminal chart** (read).

In the person's open trading terminal: What the bars on the terminal's chart say: price, the day's change and range, the trend against its 20 and 50 bar EMAs, RSI 14 and the average true range, with the market, interval, indicators and marked levels. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

No arguments.

### `update_terminal_ticket`

**Fill the terminal order ticket** (writes).

In the person's open trading terminal: Fill the terminal's order ticket with one order: side buy or sell, a size as usd or qty, market or limit at price, an optional stop_loss and take_profit, on the charted market or the asset and venue given. close true instead fills the order that closes the position held on that asset (fraction 0.5 for half). It never sends: the person checks the ticket and presses Buy or Sell there, on paper or their own account as the ticket is set. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `asset`: string
- `venue`: "hyperliquid" | "lighter"
- `side`: "buy" | "sell"
- `usd`: number
- `qty`: number
- `type`: "market" | "limit"
- `price`: number
- `stop_loss`: number
- `take_profit`: number
- `close`: boolean
- `fraction`: number

### `list_terminal_positions`

**List the terminal positions** (read).

In the person's open trading terminal: The positions and working orders of the account the terminal's order ticket trades from (paper, or the person's own): each position's side, size, entry, mark, profit and exits, and the account's equity and what it has free. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

No arguments.

### `get_terminal_market`

**A perps market on both venues** (read).

In the person's open trading terminal: One asset on Hyperliquid and Lighter now: mark, hourly funding and its yearly rate, open interest, and the gap between them. Runs in that terminal (https://charts.ftw.trade/trade), which must be open in a browser and signed in to ftw.trade; the terminal's chat shows the call.

Arguments:

- `asset` (required): string

## Perps agent

### `get_perps_agent`

**The perps agent** (read).

The account's always-on perps agent on ftw.trade: on, paused or off (off until the person turns it on), its settings and caps, the funding pairs it holds (each with its profit and carry now), its monitors, the day's profit, and its latest events (opens, closes, alerts, refusals). It trades same-asset funding pairs across Hyperliquid and Lighter from one project's wallet, paper or live as that project is, and keeps going with every browser closed.

No arguments.

### `update_perps_agent`

**Set or switch the perps agent** (trades or moves funds).

Change the perps agent (one per account): state on|paused|off (paused keeps its pairs and their loss limits and does nothing else; off with pairs held needs closePairs true to close them or false to leave them unwatched), project and wallet it trades from and network (not while it holds pairs: close them first), autoOpen (open pairs by itself; off it only answers its monitors), usdPerPair (each leg, at least 12: Hyperliquid refuses an order under $10), maxPairs, enterApr and exitApr (% a year), lossPerPairUsd (closes a pair), lossPerDayUsd (closes everything and pauses), minVolumeUsd, assets (["BTC", …] or null), leverage, allowLive (a live project trades only with this; only a connection allowed to trade live may set it). A refused leg pauses it (after closing the other leg again), and on a live project it does not turn on, and pauses, while either venue refuses ftw.trade's server where it runs. Turn it on only when the person asked.

Arguments:

- `state`: "on" | "paused" | "off"
- `closePairs`: boolean
- `project`: string
- `wallet`: string
- `network`: "mainnet" | "testnet"
- `autoOpen`: boolean
- `usdPerPair`: number
- `maxPairs`: number
- `enterApr`: number
- `exitApr`: number
- `lossPerPairUsd`: number
- `lossPerDayUsd`: number
- `minVolumeUsd`: number
- `assets`: array of string
- `leverage`: number
- `allowLive`: boolean

### `add_perps_agent_monitor`

**Add a monitor to the perps agent** (writes).

Wake the perps agent when something happens, checked about once a minute on ftw.trade whether or not a browser is open. what: price ($), funding (% a year, positive is longs paying), gap (the funding gap between the venues on an asset, % a year) or pair (the best same-asset pair, % a year). then: tell (an event), find (an event with the best pairs) or run (open the best pair now within the agent's caps, if it is on). repeatHours fires it again after that long while it holds.

Arguments:

- `what` (required): "price" | "funding" | "gap" | "pair"
- `asset`: string
- `venue`: "hyperliquid" | "lighter"
- `above`: number
- `below`: number
- `then`: "tell" | "find" | "run"
- `repeatHours`: number

### `remove_perps_agent_monitor`

**Remove a perps agent's monitor** (writes).

Remove one of the perps agent's monitors (w1…), or all with "all".

Arguments:

- `monitor` (required): string

### `control_perps_agent_pair`

**Close one of the perps agent's pairs** (trades or moves funds).

Close both legs of one pair the perps agent holds ({ pair: p1…, action: close }), paper or live as its project is.

Arguments:

- `pair` (required): string
- `action` (required): "close"

## Liquidity

### `backtest_lp_ranges`

**Backtest liquidity ranges** (read).

Try liquidity strategies against a Uniswap V2, V3 or V4 pool's indexed swaps, or a Solana pool's (PumpSwap and Raydium CPMM run as V2 pairs; Raydium CLMM, Orca and Meteora DLMM as V3), before running one: { pool: "chain:address", capital: in the quote token, days?: back from now (default every swap the pool keeps), ranges: up to 8 of { widthPct, full?, center?, rebalance?, fees?: compound|collect, compoundEveryHours?, gasPerCompound?, stopLossPct?, gasPerRebalance?, label? } }. full: true (and every V2 pair) is the whole price range and never rebalances. Each opens at the first swap's price and steps through every swap: fees by its share of the liquidity in range (the pool's indexed positions, or the liquidity its swaps imply), rebalances as its rule says (paying the pool's fee on the swap and the gas given), compounds fees back into the position every compoundEveryHours (skipped while the gas would cost more than the fees; a V2 pair compounds on every swap), and returns value, P&L, fees, compounded, fee APR, time in range, the price path's cost against holding (il), vs holding, rebalances and their cost, and a value line. rows flattens the lines as { ts, range, value, fees, compounded, il }.

Arguments:

- `pool` (required): string. chain:address of a V2 pair, a V3 or V4 pool, or solana:<account> of a PumpSwap, Raydium, Orca or Meteora DLMM pool, in the workspace (list_pools).
- `capital`: string. Decimal, in the quote token.
- `days`: number (> 0)
- `ranges` (required): array of object

### `list_lp_positions`

**Liquidity positions** (read). Also answers to `lp_positions`.

A project's liquidity positions as they run: each one's rules, mode (paper, or live with its on-chain position: token id, liquidity, ticks, transactions), status (idle, running, stopped, ended), value, P&L, fees, compounded, collected, uncollected, fee APR (null until the range has run a day, so it is never a year read off a few minutes), time in range, il, vs holding, rebalances and compounds with what each cost, its range now and a value line; wallets: each wallet's earnings (capital, value, P&L, fees, compounded, collected, net fees (fees less costs, which count the swap that opened the range, so below zero until fees pass them), costs, fee APR); total; and events: the ledger of every open, add, remove, collect, compound, rebalance and close. Positions are defined in the project document (positions); a live project runs them on chain with its wallets' keys.

Arguments:

- `id` (required): string. The project id (list_projects).

### `control_lp_position`

**Run a liquidity position** (trades or moves funds).

Act on a liquidity position, on paper or (in a live project) on chain from its wallet: start (opens at the pool's price now; live, it swaps ETH, or SOL on Solana, for the token's share and deposits), stop (its rules and fees wait; resuming carries on from then), close (out of the pool; sell: true also sells the token back for ETH or SOL), reset (forget its run), add { amount: more capital in the quote token }, remove { pct: 1 to 100 of its liquidity; sell? }, collect (take the fees out) or compound (put the fees back in now; a V2 pair or constant-product Solana pool compounds by itself): { id, position, action }. Change its rules with save_project.

Arguments:

- `id` (required): string. The project id (list_projects).
- `position` (required): string
- `action` (required): "start" | "stop" | "close" | "reset" | "add" | "remove" | "collect" | "compound"
- `amount`: string. add: decimal, in the quote token.
- `pct`: number (> 0). remove: percent of the liquidity, up to 100.
- `sell`: boolean. close or remove: sell the token taken out back for ETH.

### `add_dashboard_metric`

**Add a dashboard metric** (writes). Also answers to `add_metric`.

Add a metric to a project's dashboard in one call, as a chart the person can then edit: { id, name, value, source?, agg?, of?, by?, every?, chart?, format?, prefix?, suffix?, filter?, wallets?, tab?, size?, version? }. value is a column of the source or a formula over its columns ("fees", "fees - costs", "if(kind == \"compound\", value, 0)"; + - * / %, comparisons, and/or/not, abs min max round floor ceil sqrt log coalesce if). source defaults to lp (one row per liquidity position: capital, value, pnl, fees, compounded, collected, uncollected, netFees, costs, il, vsHodl, feeApr, inRangePct, rebalances, compounds); lp-wallets is one row per wallet, lp-history a row per position per point in time, lp-events the ledger (ts, kind, value, fees, cost, deposited, withdrawn, compounded, collected); wallets, positions, fills, pnl, history and transfers work too. agg (default sum) folds the rows; of: "totals" folds each column first and then computes the formula (a fee APR over everything is sum(fees) / sum(capital), not the sum of each row's). by splits it by a column (a bar per wallet), every (hour, day, week) draws it over time; one number is a counter. It goes on the tab named (made when missing), else a Metrics tab. Examples: Total revenue = value "fees"; Net earnings = value "fees - costs"; Compounded per day = source lp-events, value "compounded", every day; Earnings by wallet = source lp-wallets, value "fees", by "label".

Arguments:

- `id` (required): string. The project id (list_projects).
- `name` (required): string
- `value` (required): string. A column or a formula over the columns.
- `source`: "lp" | "lp-wallets" | "lp-history" | "lp-events" | "wallets" | "positions" | "fills" | "pnl" | "history" | "transfers"
- `agg`: "sum" | "count" | "avg" | "min" | "max" | "first" | "last" | "distinct"
- `of`: "rows" | "totals"
- `by`: string
- `every`: "hour" | "day" | "week"
- `chart`: "counter" | "bar" | "line" | "area" | "pie" | "table"
- `format`: string. number, eth, usd, percent, compact, ...
- `prefix`: string
- `suffix`: string
- `filter`: array of object. [{ col, op, value }], as a chart's transform filter.
- `wallets`: any. "all", or { wallets?: [wallet ids], groups?: [group names] }
- `tab`: string
- `size`: "s" | "m" | "l" | "full"
- `version`: integer

## Scripts (automations)

### `get_automation_guide`

**How to write an automation** (read).

How to write a hosted TypeScript automation for a project: the definition (triggers, policy), the ctx SDK, limits, versioning, and a starter. Read it before save_automation.

No arguments.

### `list_automations`

**Automations** (read).

A project's automations with their health (runs, errors, latency, last error, next run, ETH spent today, each socket a source listens on: open, received, passed its where, and each of build's streams it reads), and the account's use today against its limits.

Arguments:

- `project` (required): string. The project id (list_projects).

### `save_automation`

**Write an automation** (writes).

Save TypeScript as a new immutable version of an automation (id; its project is the one it is on); without id, create one on the project. The code is loaded in the sandbox and its definition checked first. Pass baseVersion (the version you edited) to refuse a save over a newer one. Saving does not deploy: call run_automation with action "deploy".

Arguments:

- `project`: string. The project id (list_projects).
- `id`: string. The automation id (list_automations).
- `source` (required): string
- `note`: string
- `baseVersion`: integer
- `matchName`: boolean. Without id: save as a new version of the project's automation with the same name as this code's definition, creating one only if there is none (how ftw push saves a file).

### `list_automation_versions`

**An automation's versions** (read).

An automation's versions, newest first; with version, that version's source and definition.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `version`: integer

### `run_automation`

**Deploy, roll back, start, stop or run an automation** (trades or moves funds).

Control an automation. action: deploy (a version, the latest by default, approving its policy; in a live project a policy that trades live needs live: true), rollback (to the version before the one deployed, or version), start, stop, run (once now, returning the run; a paper project can test-run any version). In a live project a connection that may not trade live can only stop it. Results say mode: paper|live.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `action` (required): "deploy" | "rollback" | "start" | "stop" | "run"
- `version`: integer
- `live`: boolean
- `event`: object

### `get_automation_run`

**An automation run** (read).

With run, one run of an automation (state, error, result, CPU and wall time, trades) and its log. Without, the automation's health and its 50 latest runs.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `run`: integer

### `get_automation_logs`

**An automation's log** (read).

An automation's log, newest first: its own lines, every trade and refusal (level action), and deploys, halts and errors (level system/error). Filter by run, levels, search; since (a line id) returns only newer lines, to tail.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `run`: integer
- `since`: integer
- `levels`: array of string
- `search`: string
- `limit`: integer

### `run_action`

**Call an action** (trades or moves funds).

Call a TypeScript action (D1267) deployed on the project, by name, as an automation calls it with ctx.actions: { project, name, args, wait?, timeoutMs? }. The args are checked against its contract first and its return after; a timeout or an error is tried again as many times as the action declares. It never fails the call: the answer is { ok: true, value } or { ok: false, kind: timeout|error|refused|invalid, error }, with the action's run id. wait false starts it and answers { sent } at once. A failure is answered as the failure: the action's stub is only for a caller's test run. An action that trades does so live in a live project, which needs a connection allowed to trade live.

Arguments:

- `project` (required): string. The project id (list_projects).
- `name` (required): string. The action's name, as its definition names it (list_automations shows actions with their contract).
- `args`: object
- `wait`: boolean. true (the default) waits for its answer; false sends it and answers at once.
- `timeoutMs`: integer (≥ 100, ≤ 30000). A shorter limit than the action's own timeout.
- `idempotencyKey`: string. Your own key for this call: calling again with the same key (say after a timeout) does what the first call did not finish and repeats none of its trades, sends or signals; it answers what each already did.

### `get_automation_memory`

**Inspect execution memory** (read).

Read an owned automation or action's private durable execution memory in explicit paper or live mode: revision, JSON values and UTF-8 bytes. ctx.memory writes survive restart and version updates, are separate from buffered ctx.state, and acknowledge only after commit. This tool never runs code or changes memory.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `mode` (required): "paper" | "live"

### `get_automation_rows`

**An automation's data** (read).

Rows an automation emitted with ctx.emit (and ctx.metric, in table "metrics"), newest first, and the tables it has written.

Arguments:

- `id` (required): string. The automation id (list_automations).
- `table`: string
- `since`: integer
- `limit`: integer

### `delete_automation`

**Delete an automation** (replaces or deletes).

Delete an automation with all its versions, runs, logs and rows. Its fills stay in the project.

Arguments:

- `id` (required): string. The automation id (list_automations).

## Flow and monitoring

### `get_flow`

**The flow, and what needs a look** (read).

Everything the account has built as one graph, as the Flow page shows it: nodes (sources and the markets runs are on, strategies with the metrics they publish and each run of them, workflows with their steps, and actions: built-in orders and TypeScript scripts), each with its state (running, armed, stopped, failed…), when it last did something (lastAt, ms) and a number worth showing (events in the last hour, a run's equity, a metric's value at its last bar); edges (reads, triggers, publishes, runs, does, emits, stages); and attention: what needs a look, broken first (a source that cannot read, a run stopped on an error, a failed workflow, a failing or halted script), then quiet sources something running or armed waits on (no event in a day). project limits it to one project's part. attentionOnly answers attention alone, a cheap check after a change or on a schedule.

Arguments:

- `project`: string. A project id (list_projects): its own nodes and what they connect to.
- `attentionOnly`: boolean

## Sources

### `get_source_guide`

**Source contracts and metric recipes** (read).

The available data inputs and how to subscribe, build a derived metric, choose computation and publication cadence, preview, save privately and optionally publish. With template and concrete bindings, returns an editable definition and example replay events. Indicators (RSI, MACD, Bollinger, EMA/SMA cross, stochastic, Supertrend, Donchian, CCI, ADX, VWAP, volume spike, MFI): template pool-indicator, feed-indicator or market-indicator with indicator and params; each publishes signal (+1 buy, -1 sell) and recipe.strategy trades it (save it with save_strategy, then backtest or run it on recipe.strategy.runOn). Generating a recipe creates and subscribes to nothing. save_flow_source saves the draft; run_automation deploys it.

Arguments:

- `template`: "swap-ohlcv" | "price-ema" | "pool-indicator" | "feed-indicator" | "market-indicator" | "source-ema"
- `name`: string
- `pool`: string
- `feed`: string
- `source`: string
- `field`: string
- `market`: string. market-indicator: venue:market, e.g. hyperliquid:BTC, lighter:ETH, derive:ETH-PERP, polymarket:<token id>.
- `indicator`: "rsi" | "macd" | "bollinger" | "ema-cross" | "sma-cross" | "stochastic" | "supertrend" | "donchian" | "cci" | "adx" | "vwap" | "volume-spike" | "mfi". pool-, feed- and market-indicator: which indicator. The guide (no template) lists each one's params and what it publishes.
- `params`: object. The indicator's settings, e.g. { length: 14, low: 30, high: 70, mode: "enter" } for rsi; left out, its defaults.
- `invert`: boolean. pool-indicator: turn the pool's bars over (token0 priced in token1). Left out, the pool's own orientation decides, so a coin paired with WETH as token0 (DEGEN/WETH on Base) is read as the coin in WETH.
- `since`: string. pool-indicator: fill the source's history from this date (YYYY-MM-DD, up to 120 days back) on deploy, so its strategy backtests at once.
- `public`: boolean. False by default. Explicitly publish the output fields on deployment; code disclosure is separate.
- `barEvery`: string. OHLCV and indicator bar interval, 1m by default; 1s to 1d.
- `emitEvery`: string. EMA output publication cadence, 5s by default; computation still follows each input.

### `list_sources`

**Typed sources** (read).

The account's typed sources: each one's name, fields and their kinds (and cases, for a sum type), where it comes from (origin: automation for a hosted TypeScript or FlowScript source, push, contract with its chain, contract, event and the block it has read through, or shared: another account's public source added here, with shared naming whose, the major it reads (shared.major), the latest (shared.latest) and when its major retires (shared.retiresAt; update_source_version moves it on)), server-owned visibility (public or private), subscription (local alias, pinnedMajor, latest and state active|private|retired), flow cadence for a derived metric, and public sharing preferences (including separate showCode/codeUrl), its latest event and how many landed in the last hour and day. Every source's events are signals: a workflow listens with trigger { kind: "signal", signal: <name> }, a script with { signal }, and a strategy reads sources.<name>() (its - and . written _).

No arguments.

### `get_source`

**One typed source in depth** (read).

One typed source, as its row on the Sources page opens: its shape, origin, latest event and counts (as list_sources gives them), its last events newest first (limit, default 20, up to 100) each with what it started (a workflow run, a script run, or why not), and usedBy: every strategy that reads it as a tape, workflow armed on it and script it sets off, with each one's state and how it uses it (reads or triggers). Read this before changing or deleting a source, and to check that a source you just made is landing events and being heard.

Arguments:

- `name` (required): string
- `limit`: integer (≥ 1, ≤ 100)

### `save_flow_source`

**Write a source in Flowscript** (writes).

Save a hosted source written in Flowscript as a new version: a declared input and a .flow program, no JavaScript. Without id, it creates one on the project. Saving does not deploy, so call run_automation with action "deploy". After that its events are signals, as any source's are. from is one of build's streams, { stream: "launches" | "graduations" | "curves" | "swaps" | "prices" | "markets" | "books" | "protocols", where? } (each event's fields as the stream gives them, a yes or no as 1 or 0); the protocols stream is every event of the protocols read on chain, as each protocol decodes it (a supply, a borrow, a swap, liquidity, a new market, a liquidation), and its where names what to read: { stream: "protocols", where: { protocol: "aave", deployment: "base", kind: "LiquidationCall" } } (market names one market; kind an event's name; a Solana deployment's changes come as kind "account"), with e.protocol, e.deployment, e.market, e.kind, e.address, e.block, e.index, e.tx, e.ts and e.event (the decoded event as JSON text); with protocol and deployment pinned its history is stored, read from the chain's past where nothing kept it, so warmup and a replay's history work on it, and since: "2026-09-01" (unix seconds or a date, up to 120 days back) fills the source's history from there when it is deployed, each event stepped at its time and what it emits published then, before the live stream, so a strategy backtests over the source the day it is written (the swaps stream takes since too); the books stream is one market's live order book, at most once a second while it changes, and its where names it: { stream: "books", where: { venue: "polymarket", market: "<token id>" } } (or venue hyperliquid or lighter with market "BTC", "xyz:TSLA", "HYPE/USDC", or venue derive with an instrument, "ETH-PERP"), with e.bid, e.ask, e.mid, e.micro (the microprice), e.spreadBps, e.bidSize, e.askSize, e.bidDepth, e.askDepth, e.imbalance (USD on the top 20 levels, +1 all bids), e.bids10/asks10, 25, 50, 100 (USD within that many bps of the middle) and levels 2 to 5 as e.bid2, e.bidSize2 … e.askSize5; an owned source or subscribed alias, { source: "prices", read: { value: "number close" }, where? }; the projection, producer identity, pinned schema and current read authority are checked before computing and before releasing queued output; merging typed sources is supported, mixing them with external input families is refused; an always-on socket, { socket: "wss://…", send?, where?, read: { usd: "number data.usd", pool: "text data.pool" } }; or a URL polled, { poll: "https://…", every?: "1s".."7d", method?: GET|POST, body? (sent as JSON with a POST, e.g. { type: "metaAndAssetCtxs" } to Hyperliquid info), headers?, read } (a header may send {{secrets.NAME}} when secrets names it); without every it polls only when asked: a strategy's poll.source({ name }) or a workflow's { kind: "poll", source } step (once a second at most); or a contract's event, { event: "Transfer(address indexed from, address indexed to, uint256 value)", chain, address? (any contract when left out, if where holds an indexed parameter), structs?, where? (the event's fields; indexed ones held to a value filter at the RPC), confirmations?, recheck?, since? }: each log steps the program, its fields as the record (numbers, a yes or no as 1 or 0, the rest text) with address, tx, block and log_index, read from the chain's head on deploy and rechecked for logs the RPC left out; since (a block number, unix seconds or "YYYY-MM-DD", up to 5,000,000 blocks back) fills its history from there first, each log stepped at its block's time, so a strategy backtests over it the day it is written; or a timer, { every: "1s".."7d" }: nothing fetched, each tick gives e.at (unix ms) and e.every (seconds). A workflow trigger { kind: "timer", every: "1h" } ("1m".."7d") makes and deploys one by itself. from may also be a list of 2 to 4 streams and sockets merged into one series, each with as?: "name"; e.from says which one an event came from, and a field another input reads is na (or "") on this one's events. where filters before the program runs, as a script's subscribe does. fields are what it publishes, as any source declares them ({ token: "address", usd: "number", "note?": "text" }). by: "pool" keeps one run per value of an input field, each with its own history and state (on the swaps stream, ema(e.price, 20) is each pool's own), up to 1,000 at once. bars: { every: "1s".."1d", price: "<number field>", volume?: "<number field>", time?: "<number field>", timeUnit?: "s"|"ms" } steps nonempty closed event-time bars. Declared clocks default to seconds and never guess units. OHLC follows event time within a bucket; earlier/closed buckets and invalid declared price/volume are refused. Without a configured volume it stays unknown. The bar adds e.open/high/low/close/volume/count/start/end. emit: { every: "5s" } sets an independent output cadence (whole seconds, 1s to 1d). Every observation still updates indicators; the latest actual batch per partition is released at a fixed UTC boundary. Quiet intervals repeat no values. This samples replacement metrics and is not lossless delivery for alerts or trades. Pending output is bounded and checkpointed with indicator and bar state. warmup: "6h" (up to "7d") steps the stream's stored history first when a version starts (the swaps stream's, or the protocols stream's of one deployment), publishing nothing, so indicators are warm on the first live event. key names the fields that key an event (the same key within dedupeFor, 1 day by default, is published once; dedupeFor: 0 publishes every one). flow is a Flowscript program over the events, one step each: `uses { input: "^1", source: "^1" }`, `const e = input.event();` reads the event (e.usd, e.pool), and `while (cond) { source.emit({ … }); }` publishes, its arguments the fields (`when` publishes only as cond turns true). History, indicators and state work across events: e.usd[1] is the event before, ema(e.price, 20), once(cond), cooldown(minutes(5)). A program is checked when saved: a field it does not have, or one of the wrong kind, is refused. Arrays (D1544) gather events: buffer(e, 200) is the last 200 events (oldest first, records with the input's fields), and fold(e, [], (xs, s) => push(xs, s.wallet, 50)) keeps what a fold says. map, filter, reduce, any, all, find, sortBy(xs, x => -x.usd), groupBy(xs, x => x.pool) (each { key, items }), slice(xs, -10), size, xs[i], unique, contains, indexOf, concat and the reducers (sum, mean, median, min, max, percentile(xs, q), count, distinct) work on one; a function given to one reads consts but keeps no state of its own. A field declared an array publishes one, each item checked: fields: { top: [{ pool: "text", usd: "number" }], wallets: "address[]" }, source.emit({ top: slice(sortBy(flows, f => -f.usd), 0, 5) }); a json field takes any array or record (either at most 4 KB as JSON). A strategy reads an array field as an array: buffer(sources.top_pools(), 10)[-1].top. At most 10,000 items an array. A source file (.source.flow) is the program with the rest of its definition in a `// @source { name, fields, from, … }` header, which may run over the comment lines below it until its braces close; give its text as definition, and it is kept as written. backtest_flow_source steps a definition over events you give, or the stored swaps of the last hours, and says what it would have published, before you save or deploy it.

Arguments:

- `project` (required): string
- `id`: string. The source to save a new version of (an automation id, au_…); left out, a new one.
- `definition` (required): any. { name, description?, fields, from (one input, or a list of streams and sockets to merge), by?, bars?, emit?, warmup?, key?, dedupeFor?, public?, version?, secrets?, flow }, or the text of a source file (.source.flow): the program under a `// @source { name, fields, from, … }` header holding the rest, which may run over the comment lines below it. A source file is kept as written.
- `note`: string
- `baseVersion`: integer

### `backtest_flow_source`

**Backtest a Flowscript source over events** (read).

A Flowscript source's backtest: step a definition (not saved yet, or a saved source's latest version by id) from a fresh start over events, and answer what it would have published, without publishing or keeping anything. events: up to 20,000, each the event itself or { data, at? (unix ms; one a second from now back when left out), from? (a merged input's as) }. history: "6h" (up to "7d") replays the swaps stream's stored events of that span first, through the source's where, for a source on swaps. Answers { ok, error?, events, steps, partitions, emitted, emits: [{ at (release/deadline unix ms), observedAt (originating observation or bar-end unix ms), part?, fields, key? }] (the first 1,000), ms }. Bars still open at the end are closed, as the clock would.

Arguments:

- `project` (required): string
- `id`: string. A saved Flowscript source (au_…) to replay its latest version; or give definition.
- `definition`: any. As save_flow_source takes it: an object, or a source file's text.
- `events`: array
- `history`: string. How far back the stored swaps go, "1h" to "7d".

### `save_machine_source`

**Run a source on a machine of your own** (writes).

Save a source whose code runs on the account's own source machine, as a new version, and run it: for what a Flowscript or TypeScript source cannot do (npm packages, a long-lived websocket, heavy compute). Opt-in and paid: the machine costs a monthly price (list_machine_sources says it, $5 a month for the smallest by default) drawn daily from the account's credits, the first save needs a month of it in credits, and the machine stops when credits cannot pay a day (warned three days ahead). All the account's machine sources share the one machine, which moves a size up (at that size's price) when its memory stays near full. { name, description?, fields (as create_source's pushed fields), cases?, code, dependencies? ({ "<npm package>": "<version or range>" }, registry only), note? }. code is a TypeScript or JavaScript module run with Node 22 that calls emit(fields, { key?, dedupeFor? }) for each event, either at the top level or in a default export async ({ emit, log, signal }) => { … } (signal aborts when the version is replaced); a crash restarts it with backoff. Its events are a pushed source's: signals, checked against fields, public and versioned as any source. list_machine_sources shows each one's state, restarts, error and last log lines.

Arguments:

- `name` (required): string. Lowercase letters, digits, _ . and -, up to 48; unique in the account, or an existing machine source to save a new version of.
- `description`: string
- `fields` (required): object. Field name to kind, as create_source takes.
- `cases`: object
- `code` (required): string. The module, TypeScript or JavaScript, up to 256 KB.
- `dependencies`: object. npm package to version or range, up to 50.
- `note`: string

### `list_machine_sources`

**The source machine and its sources** (read).

The account's source machine: state (none, starting, running, paused, stopped when credits ran out, idle with no sources, failed with reason), size (memoryMb), price (usdMonth, usdDay), credits left (balanceUsd, daysLeft), any warning, its last heartbeat and memory use; and each machine source with its running version, how many versions it has, its dependencies and what the machine last said of it (installing, running, crashed or failed, restarts, error, last log lines).

No arguments.

### `get_machine_source`

**A machine source's code** (read).

A machine source's code and dependencies: the version running, or { version }.

Arguments:

- `name` (required): string
- `version`: integer (≥ 1)

### `update_machine_source`

**Roll a machine source to a version, or take it off the machine** (replaces or deletes).

{ name, version } runs that saved version instead (a rollback or forward); { name, unload: true } takes it off the machine, leaving it a pushed source with its events and versions. With no machine source left the machine stops and its charges stop. delete_source deletes it outright.

Arguments:

- `name` (required): string
- `version`: integer (≥ 1)
- `unload`: boolean

### `update_source_machine`

**Pause or start the source machine** (writes).

{ paused: true } stops the source machine and its daily charge; { paused: false } starts it again, which needs a month of its price in credits.

Arguments:

- `paused` (required): boolean

### `create_source`

**Declare a pushed, contract-event, call or deploy source** (writes).

Declare a typed source. Pushed: { kind: "push", name, description?, fields: { field: kind } (kinds text, number, boolean, address, chain, pool, hash, url, json; a name or kind ending in ? is optional), cases? (a sum type: { "<case>": { field: kind } }) }; the answer holds its key (fws_…) once: send events with POST /api/sources/<name>/events and Authorization: Bearer <key>, a body of one event's fields or { events: [{ fields, key?, dedupeFor? }] } (up to 100). Contract event: { kind: "contract", name, description?, chain (an EVM chain in the workspace, add_chain), address? (the contract; left out, any contract on the chain, when where holds an indexed parameter), event: the Solidity signature as written, e.g. "Transfer(address indexed from, address indexed to, uint256 value)", structs? ({ Name: "(…)" }), where? (fields of the event to a value or { gt, gte, lt, lte, ne, in, nin }: a log that fails it is not published, and an indexed one held to a value or in a list is filtered by the RPC too), confirmations? (0-128 blocks under the head before a log is read; 0 by default), recheck? (blocks each read looks at again for logs the RPC left out, 0-1,000, 32 by default; what it finds is counted in late), fromBlock? (up to 100,000 blocks back; the head when left out), preview?: true (only answer the fields it would publish) }: every matching log becomes an event with the decoded fields (integers as numbers plus their exact text in exact), and the raw log (tx, log_index, block, topic1-3, raw), once. Contract call: { kind: "call", name, chain, address (the contract), function: the Solidity function with what it returns, e.g. "createToken(string name, string symbol) returns (address token, address pool)", fromBlock? (up to 5,000 blocks back), preview? }: every successful call to it, at any depth where the chain's RPC traces blocks (else calls sent to it directly; our hosted RPC does not trace, so add the chain with your own rpcUrl for calls made through a router or factory), is an event the block it lands in with its arguments by name, what it returned as ret_<name> (ret for one unnamed), created (the contracts the call deployed), tx, block, from, to, value (native), depth and returns_from (trace, or replay: re-run on the state before the block). Deploy: { kind: "deploy", name, chain, deployer? (an account or factory; every contract created on the chain when left out) }: each contract created, with address, deployer, from (who sent the transaction), tx, block, depth, via. Call and deploy sources are read the moment a block lands, so a workflow armed on one runs a block after the call: trigger { kind: "signal", signal, where?, pool?: "ret_pool" } indexes that pool into the workspace first. A TypeScript source that polls an API is a script instead: save_automation with source() from @ftw/sdk.

Arguments:

- `kind` (required): "push" | "contract" | "call" | "deploy"
- `name` (required): string. Lowercase letters, digits, _ . and -, up to 48; unique in the account.
- `description`: string
- `fields`: object. push: field name to kind.
- `cases`: object. push: a sum type, case name to its own fields.
- `chain`: string. contract: the chain, as the workspace names it.
- `address`: string. contract, call: the contract address.
- `function`: string. call: the function signature, with returns (…).
- `deployer`: string. deploy: the account or factory that deploys.
- `event`: string. contract: the event signature.
- `where`: object. contract: a filter on the event's fields; indexed ones held to a value go to the RPC as topics.
- `confirmations`: number. contract: blocks under the head before a log is read (0-128).
- `recheck`: number. contract: blocks each read looks at again (0-1,000, 32 by default).
- `structs`: object. contract: named structs the signature uses.
- `fromBlock`: integer (≥ 0)
- `preview`: boolean

### `create_source_ingress`

**Bind durable JetStream ingestion to a pushed source** (writes).

Register one immutable ordered input for an existing pushed source. A worker you run connects to your own NATS JetStream account; the hosted app stores no broker credentials and opens no broker socket. Use @ftw/sdk/nats with this returned ingress identity and the source key. Its bounded batches commit source events and a consecutive stream cursor together before acknowledgment. origin pins account, JetStream domain, stream creation identity, decoder SHA-256 and explicit startAfter sequence; broker is your namespace label. Only an unfiltered plain Limits stream with consecutive sequences is supported. Gaps, partial overlaps, replacement sources and changed origin are refused. This does not raise publication quotas or enable hosted source machines. Changing a live input requires uncapped live controller access.

Arguments:

- `name` (required): string. An existing pushed source in this account.
- `origin` (required): object

### `list_source_ingresses`

**Durable input bindings and committed cursors** (read).

This account's immutable JetStream input bindings, exact producer and origin identities, committed sequence cursor, bounded last batch receipt and batch limits. Contains no source key or broker credentials. A covered-prefix receipt proves that its sequences were already committed, not that historical payload bodies were retained.

No arguments.

### `delete_source_ingress`

**Revoke a durable input binding** (replaces or deletes).

Delete an ingress by id, refusing future publications under that identity. Existing source events stay retained and the pushed source stays. A new binding is a deliberate new origin and starting cursor; workers must never adopt it automatically. Requires uncapped live access when the source reaches a same-account live listener.

Arguments:

- `id` (required): string

### `create_source_key`

**Replace a pushed source's key** (replaces or deletes).

A new key for a pushed source ({ name }), shown once in the answer; the old key stops working at once, so whatever pushes with it must switch. Changing a source whose declared dependencies reach a same-account live listener requires live access without spending caps. Only a pushed source has a key: a contract-event source reads the chain itself, and a TypeScript source is a script.

Arguments:

- `name` (required): string

### `delete_source`

**Delete a source** (replaces or deletes).

Delete a pushed, contract-event, call or deploy source, or remove a public source this account added ({ name }); a public one of its own stops being listed and those who added it stop hearing it; its key stops working at once and its events stay for their 30 days. A running strategy that reads it stops with the reason, and a workflow armed on it never fires; get_source lists what uses it, and the answer names them too. A TypeScript or FlowScript source is deleted with delete_automation. Prefer unsubscribe_public_source for explicit subscription removal.

Arguments:

- `name` (required): string

### `list_public_sources`

**Public sources** (read).

Sources other accounts made public, to add and build on, the most reliable first ({ q? } matches name, publisher or description). Each has its id, publisher, fields, origin, how many accounts added it, addedAs when this account has, and uptime over the last week: hours (judged over: the week, or since its first event in it), heard (hours with at least one event), pct (the share of those hours it kept to its own pace: no silence past two of its usual gaps, an hour at least, so a source that calls once a day and does is 100%), paceMinutes (its usual gap), perDay (events a day), longestQuietHours, lastAt and hourly (168 counts, oldest first). version is its latest major, the one an account that adds it now reads; pinned is the major this account reads, when it added it; versions lists each major (newest first) with its stream, minor, notes, readers, releasedAt and retiresAt.

Arguments:

- `q`: string

### `add_public_source`

**Subscribe to a public source** (writes).

Subscribe to another account's public source ({ id, as? }): as is the name to read it by, its own name unless given. From then on this account reads each event its publisher publishes, under that name, the moment it is published: a strategy reads sources.<name>(), a workflow and a script are armed on it. Nothing is copied: each event is written once, in its publisher's account, so a public source costs what a private one does. Only its publisher publishes it. It reads the latest major version and stays on it: a change its publisher makes that it could not read comes out as a new major, which update_source_version moves to. delete_source removes it.

Arguments:

- `id` (required): string. From list_public_sources.
- `as`: string

### `unsubscribe_public_source`

**Unsubscribe from a public source** (replaces or deletes).

Remove this account's subscription to a public source ({ id, as? }). as is its local alias, checked against the source id when supplied. Stops this account reading it, without deleting the publisher's source or its events. Held streams close and dependent metrics stop. Idempotent: unsubscribed is false when this account no longer follows it.

Arguments:

- `id` (required): string. The public source id, src_….
- `as`: string. The local subscription alias.

### `update_source_sharing`

**Make a source public or private** (writes).

Make one of the account's own sources public ({ name, public: true, publisher?, description?, showCode?, codeUrl? }), listed for every account with its uptime and added by any; publisher is the name shown (Anonymous unless given; kept from before when made public again). showCode: true shows its code with it (get_public_source): a TypeScript source's deployed code, a FlowScript source's program body without its input configuration, or a contract source's chain, contract and event; codeUrl (https) says where the code of a pushed source lives. Code is private by default, independently of data publication; ordinary visibility edits preserve disclosure preferences and links unless given; deleting a producer or removing its output clears code disclosure for its replacement. { name, public: false } makes it private again: those who added it keep its name but stop hearing it, and live readers by its id are let go, until it is public again. Every event it publishes reaches those who added it, so share only what you mean others to see.

Arguments:

- `name` (required): string
- `public` (required): boolean
- `publisher`: string
- `description`: string
- `showCode`: boolean
- `codeUrl`: string. https://…, or "" to clear.

### `create_source_version`

**Release a new major version of a public source** (writes).

Release a new major version of one of the account's public sources ({ name, stream?, notes?, retireOldAfterDays? }): for a change its readers could not keep reading (a field removed, renamed, made required or of another kind, a case removed), which a deploy refuses on the version they read. The new major is its own source in this account, published beside the old one: name.v2 for v2 (stream names another), a TypeScript source with source({ name, version: 2 }) in a script of its own (deploying it releases it by itself), or a pushed source of that name. Accounts that add it from now on read the new major; those that added it before stay on theirs, warned, until it retires in retireOldAfterDays (14 to 90, default 30) and goes quiet for them; they move with update_source_version. Keep the old one running until then. A change they can read (a new field, a description, new code with the same type) needs no release: it reaches every reader of that major as a minor version.

Arguments:

- `name` (required): string. The public source, by its own (v1) name.
- `stream`: string. The source that is the new major; name.vN unless given.
- `notes`: string. What changed, for its readers.
- `retireOldAfterDays`: integer (≥ 14, ≤ 90)

### `update_source_version`

**Move an added public source to a newer version** (writes).

Move a public source this account added to another major version ({ name, to?, force? }): name is the name it reads it by, to the major (the latest unless given). It checks first what the new major drops or changes (a field gone, renamed, made required or of another kind, a case gone) against what reads the source here: workflows on it that filter on or read those fields (trigger.<field>), and scripts triggered by it, named to check by hand. If anything would break it refuses and lists it, unless force: true. Answers { from, to, changes, affected }. The old major stops running for this account when it retires (list_sources: shared.retiresAt).

Arguments:

- `name` (required): string
- `to`: integer (≥ 1)
- `force`: boolean

### `get_public_source`

**One public source, with its code** (read).

One public source by its id, as list_public_sources lists it (its uptime and how many added it), with its code when its owner shows it (code: { kind: typescript|flowscript, automation, version, deployedAt, source } as deployed now (FlowScript source is its program body only); { kind: contract, chain, address, event }; { kind: push, url }; null when not shown) and its live numbers (metrics): liveReaders (streams and waits open now), armed (workflows and scripts armed on it in every account) and handoff (bus notification dispatch latency from publication, p50Ms/p95Ms/maxMs; excludes asynchronous durable reads and delivery to clients), counted since the app last started.

Arguments:

- `id` (required): string. From list_public_sources.

### `get_source_events`

**Wait for a source's next events** (read).

A source's events from a cursor on, waiting for the next when there are none yet (a long poll): { name } for one of the account's sources or a public one it added, or { id } for any public source without adding it; after (the cursor from the last answer; left out, it waits for the next event from now), waitSeconds (0 to 25, default 25), limit (1 to 500, default 100). Answers { events: [{ id, name, key, fields, at }], cursor }: call again with after: cursor for what comes next, in a loop, since one wait is shorter than a slow source's gap (an empty answer means none came yet, not that it stopped). With no after and waitSeconds 0 there is nothing to answer; after: 0 reads its oldest kept events. It is handed each event the moment it is written, as a stream is. To hold a stream open instead (a bot, a service, a page), GET /api/sources/<name>/stream or /api/shared-sources/<id>/stream with Authorization: Bearer <API token>: server-sent events, each with its id, resumed with ?after=<id> or Last-Event-ID.

Arguments:

- `name`: string
- `id`: string
- `after`: integer (≥ 0)
- `waitSeconds`: number (≥ 0, ≤ 25)
- `limit`: integer (≥ 1, ≤ 500)

## Signals

### `list_signal_types`

**Signal types** (read).

The signals the account can listen for: each name, its fields and their kinds, and the deployed automation that publishes it. A workflow listens with trigger { kind: "signal", signal, where? }; an automation with the trigger { signal, where? }.

No arguments.

### `list_signals`

**Signals** (read).

Signals published in the account, newest first: name, fields, which automation and run published it, and what each started (workflow runs, automation runs, or why not). Filter by name or automation.

Arguments:

- `name`: string
- `automation`: string
- `before`: integer
- `limit`: integer (≥ 1, ≤ 500)

### `create_signal`

**Publish a signal** (trades or moves funds).

Publish a signal by hand, as its automation would: { name, fields, key?, dedupeFor? (seconds, 24 h by default) }. It is checked against the declared type, dropped if the same key was published within dedupeFor, and starts whatever listens for it, which may trade. When a listener is in a live project, this connection needs live access.

Arguments:

- `name` (required): string
- `fields` (required): object
- `key`: string
- `dedupeFor`: integer (≥ 0)

## Account wallets

### `list_account_wallets`

**Account wallets** (read).

The account's wallets (each one a key ftw.trade holds): state account (in no project), assigned (in one active project; a wallet is in at most one at a time) or archived, the project it is in, and its ETH on `chain` (robinhood unless named). Wallets made inside a project are the account's too. Pass archived: false to leave archived ones out.

Arguments:

- `archived`: boolean
- `chain`: "robinhood" | "ethereum" | "base" | "arbitrum" | "optimism" | "unichain". The EVM chain; robinhood when left out. A wallet has the same address on each.

### `create_account_wallet`

**Make an account wallet** (writes).

Make a wallet at account level, in no project ({ name? }), or import one by its private key ({ name?, privateKey }). Add it to a project with add_wallet_to_project.

Arguments:

- `name`: string
- `privateKey`: string

### `get_wallet_protocol_positions`

**Wallet protocol positions** (read).

Any EVM wallet's positions now on every lending, vault and DEX protocol ftw.trade reads on chain (Aave, Spark, Compound, Morpho, Euler, Fluid, Gearbox, Yearn, Uniswap, Aerodrome, …): for each deployment it holds something on, the protocol's account reads over it (supplied, borrowed, health factor, LTV where the protocol has them). The wallet is tracked from then on: its positions are kept every hour, so a strategy reads its history with account reads keyed "<deployment>@0x…" (aave.healthFactor("base@0x…")) or "<market>@0x…" (aave.supplied("base:usdc@0x…")). Each deployment is read at its state kept in the last half hour (block says which); one not read within a minute is listed under unread with "ask again", and is quicker the next time.

Arguments:

- `address` (required): string. The EVM wallet address, any wallet.

### `get_wallet_history`

**Wallet history** (read).

One account wallet's history across projects: each stay in a project (when it joined and left, what was stopped when it left, its fills' P&L there), its live trades, and its sweeps.

Arguments:

- `address` (required): string. The wallet address (list_account_wallets).

### `add_wallet_to_project`

**Put an account wallet in a project** (writes).

Put an account wallet into a project ({ address, project, group?, label? }). It must be in no other active project; one it was in before gets its old entry back. It joins with no paper ETH: fund it like any project wallet.

Arguments:

- `address` (required): string. The wallet address (list_account_wallets).
- `project` (required): string. The project id (list_projects).
- `group`: string
- `label`: string

### `remove_wallet_from_project`

**Free a wallet from its project** (replaces or deletes).

Free one wallet from a project back to the account ({ id: project, wallet: its id in the project, apply? }). Without apply: true it only says what would happen: running strategies, open orders or exits and liquidity ranges that trade only this wallet stop, the rest go on without it, and workflows with a step left with no wallet stop. Refused while a live wallet has an unconfirmed transaction. The wallet stays listed in the project, released, so its books keep it.

Arguments:

- `id` (required): string. The project id (list_projects).
- `wallet` (required): string
- `apply`: boolean

### `control_project`

**End or reopen a project** (replaces or deletes).

End a project ({ id, action: "end", apply? }): every strategy, order, workflow, liquidity range and queued launch program trade on it stops, and all its wallets go back to the account. Its history stays, and action "reopen" brings it back (its wallets return where still free). Ending without apply: true only says what would stop.

Arguments:

- `id` (required): string. The project id (list_projects).
- `action` (required): "end" | "reopen"
- `apply`: boolean

### `transfer_wallet_funds`

**Sweep a wallet's funds to the account** (trades or moves funds).

Move a wallet's funds back to the account on one chain ({ address, chain?, to?, tokens?, apply? }; chain is robinhood unless named): every token it traded live there (and any tokens named), then its ETH less the transfer's gas, to the account main wallet or `to`, another of the account's wallets. Only for a wallet in no project. Without apply: true it reads what it would move; with it, real funds move, so the connection must be allowed to trade live.

Arguments:

- `address` (required): string. The wallet address (list_account_wallets).
- `chain`: "robinhood" | "ethereum" | "base" | "arbitrum" | "optimism" | "unichain". The EVM chain; robinhood when left out. A wallet has the same address on each.
- `to`: string
- `tokens`: array of string
- `apply`: boolean

### `update_account_wallet`

**Rename, archive or restore a wallet** (writes).

Rename an account wallet ({ address, name }), archive an emptied one in no project ({ address, archived: true }; refused while it holds more than dust, so sweep it first with transfer_wallet_funds), or restore it ({ address, archived: false }). Its key and history are kept.

Arguments:

- `address` (required): string. The wallet address (list_account_wallets).
- `name`: string
- `archived`: boolean

## Session keys

### `list_session_keys`

**Session keys** (read).

This key (its scope, expiry, when it was last rotated, and for a capped key what it has spent in the last 24 hours) and the keys it made, and the keys those made. A key's secret is never shown again after it is made.

No arguments.

### `create_session_key`

**Make a session key for another agent** (writes).

Make a key for an agent this one hands work to ({ label, scope?, expires? }): no wider than this key (access, projects and tools within its own) and lapsing no later (expires: 1h, 1d, 7d, 30d, never; 1d by default when this key lapses, and never later than it). Revoking this key revokes it too. The answer holds its secret, once: give it to the agent and keep it nowhere else.

Arguments:

- `label` (required): string
- `scope`: object. What the new key may do, never more than this key: { access: "read" | "paper" | "live", projects?: [project ids], tools?: [tool names], caps?: { ethPerDay?, signsPerDay? } }. caps bound what its live signatures send (ETH) and how many transactions or orders it signs in any 24 hours; whatever it signs also counts against this key's own caps.
- `expires`: "1h" | "1d" | "7d" | "30d" | "never"

### `rotate_session_key`

**Rotate this key** (writes).

A new secret for this key, keeping its id, scope and expiry ({ graceMinutes? }): the old secret keeps working for graceMinutes (5 by default, at most 60, 0 to end it now) so calls in flight finish. Switch to the new secret at once.

Arguments:

- `graceMinutes`: number

### `revoke_session_key`

**Revoke a session key** (replaces or deletes).

Revoke this key or one it made ({ id }), and every key made from that one. Revoking this key ends this connection.

Arguments:

- `id` (required): string

## Admin (admin keys only)

### `run_withdrawal`

**Withdraw to any address (admin)** (trades or moves funds).

Send funds from one of the account's wallets to any address, as Wallets → Withdraw does: { address (the wallet, its EVM or Solana address), to, chain? (robinhood, ethereum, base, arbitrum, optimism, unichain or solana; robinhood when left out), token? (an ERC-20 or a mint; ETH or SOL when left out), amount (decimal, or "max"), apply? }. Without apply: true it only plans (fee, amount, destination). Real funds leave the account and cannot be called back: confirm the destination with the person first. Kept to the account's withdrawal setting and to this key's spending caps.

Arguments:

- `address` (required): string
- `to` (required): string
- `chain`: "robinhood" | "ethereum" | "base" | "arbitrum" | "optimism" | "unichain" | "solana"
- `token`: string
- `amount` (required): string. A decimal amount, or "max".
- `apply`: boolean

### `list_withdrawals`

**Withdrawals and the withdrawal setting (admin)** (read).

The account's withdrawal setting (mode open, off or locked to an agent account's return address) and its last 50 withdrawals ({ address? } for one wallet's).

Arguments:

- `address`: string

### `update_withdrawal_setting`

**Turn withdrawals on or off (admin)** (writes).

Turn withdrawals to outside addresses (and key exports) on or off for the account ({ enabled }), the switch under Wallets → Withdraw. An agent account's setting comes from its invite and cannot be changed.

Arguments:

- `enabled` (required): boolean

### `list_ai_access`

**Every key and connected app (admin)** (read).

Every session key and connected app (claude.ai, ChatGPT, Claude Code...) of the account: id, label, clientId (set for an app), scope, expiry, last use, and for a capped key what it spent in the last 24 hours. With { id } also that one's recent activity: its tool calls that changed something or were refused.

Arguments:

- `id`: string

### `update_ai_access`

**Change a key's or app's access (admin)** (writes).

Set what one of the account's keys or connected apps may do ({ id, scope }); the scope replaces the old one. A key made by another stays within that one, and keys this one made that no longer fit are revoked.

Arguments:

- `id` (required): string
- `scope` (required): object. { access: "read" | "paper" | "live" | "admin", projects?: [project ids], tools?: [tool names it may use, only these], deny?: [tool names it may never use], caps?: { ethPerDay?, signsPerDay? } }. The whole scope: what is left out is cleared.

### `revoke_ai_access`

**Revoke a key or disconnect an app (admin)** (replaces or deletes).

Revoke one of the account's keys, or disconnect an app ({ id }), and every key made from it; what they started stops.

Arguments:

- `id` (required): string

## Portfolio, level and RPC credits

### `get_gas_limits`

**Gas limits** (read).

The account's gas limits for automated transactions (strategies, automations, workflows and calls with a session key; not what the owner presses on their page), per EVM chain: paused, maxGwei (the highest gas price signed at), maxPerActionEth (the most one transaction may cost in gas) and maxPerDayEth (the most they may cost together in 24 hours), with the defaults, the gas price now in gwei, the gas used in the last 24 hours, and a current hold (held { since, gwei, reason }) when one is. A transaction past a limit is not signed: its step fails with "Held for gas" and goes again on the next step once gas allows.

No arguments.

### `update_gas_limits`

**Tighten gas limits** (writes).

Tighten the gas limits for automated transactions: { chains: { <chain>: { paused?, maxGwei?, maxPerActionEth?, maxPerDayEth? } } } for robinhood, ethereum, base, arbitrum, optimism or unichain. Over MCP they may only be tightened (a lower number, or paused: true); raising one, unpausing or turning them off is done by the owner on the Account page. Answers what get_gas_limits does.

Arguments:

- `chains` (required): object. By chain: { paused?, maxGwei?, maxPerActionEth?, maxPerDayEth? }.

### `get_account_level`

**Level, points and limits** (read).

The account's level and points (D1271): points come from platform fees its live swaps paid (100 per USD), RPC credits bought (100 per USD) and first steps (first project, backtest, running strategy, live trade). The level sets the fee discount (feeScaleBps of the base rate), free hosted RPC requests a day, projects, project runners and free assistant messages. Answers the level, the next one and the points to it, every level's limits, the milestones and the latest points.

No arguments.

### `get_portfolio`

**Portfolio value, P&L and positions** (read).

The account's portfolio across its main balance, project wallets, account wallets and Hyperliquid and Lighter accounts, in USD. books { paper, live }: each { valueUsd, startUsd, flowsUsd (net capital put in since the start), pnlUsd (value change less flows), pnlPct, history [{ ts, valueUsd, pnlUsd }] (a mark every 15 minutes) } or null; paper and live are never added together, and testnet perps count in neither. holdings [{ book, kind (main, project, wallet, perps, fund: units in a strategy vault at NAV now plus subscriptions waiting), label, detail, usd, error }], positions [{ book, kind (perp, token), where, wallet, symbol, size (signed for a perp), entry, valueUsd, pnlUsd, leverage, liquidation }], strategies [{ name, series, state, cash, equity, returnPct, trades, projectId }], since (when the history was last reset, unix ms).

No arguments.

### `delete_portfolio_history`

**Reset portfolio history** (writes).

Start the portfolio's history and P&L again from now, as the Account settings button does: the marks go and the value now is the new start. Funds, positions and strategies are untouched. Answers what get_portfolio does.

No arguments.

### `list_tracked_wallets`

**Tracked wallets and their performance** (read).

Every wallet the account tracks long term (D1660): its own wallets (they join by themselves) and any address it added with add_tracked_wallet. Each is read from the chain every 15 minutes: native and every token on Robinhood Chain, Ethereum, Base, Arbitrum, Optimism and Unichain, its Hyperliquid perps account, or SOL and SPL tokens for a Solana address. wallets [{ address, kind (evm, solana), label, own, valueUsd, pnlUsd and returnPct (time-weighted) since its history began, day/week/month { pnlUsd, returnPct }, flowsUsd (net put in), chains held on, since (first mark, unix s), spark (hourly values, last week), error }], total { valueUsd, dayPnlUsd, pnlUsd }. Deposits and withdrawals (transfers with another person's wallet) are not P&L.

No arguments.

### `add_tracked_wallet`

**Track a wallet** (writes).

Track any address long term, anyone's: a 0x EVM address or a Solana address, with an optional label; its first mark is read now and one every 15 minutes after. Tracking an address another account already tracks shows its whole history. At most 50 besides the account's own wallets. Adding one already tracked sets its label. Answers the wallet as list_tracked_wallets does.

Arguments:

- `address` (required): string. A 0x EVM address or a Solana address.
- `label`: string. A name for it, up to 60 characters.

### `get_wallet_performance`

**A tracked wallet's performance** (read).

One tracked wallet in full: what list_tracked_wallets gives, plus history [{ ts (unix s), valueUsd, pnlUsd, returnPct (time-weighted, since the first mark) }] over its whole tracked life (thinned to about 1,500 points), holdings now [{ chain, token, symbol, units, price, usd (null where no pool with $10k of liquidity prices it) }], unread (chains whose last read failed, held at their last amounts), flows [{ ts, chain, usd (+ in, − out), note, hash }] (the last 100 deposits and withdrawals found on the chain) and stats { maxDrawdownPct, bestDayPct, worstDayPct, days }.

Arguments:

- `address` (required): string. A tracked address.

### `remove_tracked_wallet`

**Stop tracking a wallet** (replaces or deletes).

Stop tracking an address. The account's own wallet is hidden rather than dropped (add_tracked_wallet shows it again, with its history); an address no account tracks loses its history at the next prune.

Arguments:

- `address` (required): string. A tracked address.

### `get_profile`

**Public profile** (read).

The account's name (shown only to itself) and its public profile: username and avatar (a picture URL). With a username, vaults show the account by it and its picture: as manager, and in each vault's members list. Without one it shows there as anonymous.

No arguments.

### `update_profile`

**Set public profile** (writes).

Set the public profile: username (3 to 20 letters, digits and _, unique; "" hides the profile again) and avatar (a data URL of a PNG, JPEG or WebP up to 256 KB, data:image/png;base64,…; "" removes it). Either may be left out. Answers what get_profile does.

Arguments:

- `username`: string. A unique username, @ optional; "" hides the profile.
- `avatar`: string. A data URL of a PNG, JPEG or WebP up to 256 KB; "" removes it.

### `get_rpc_credits`

**Hosted RPC use and credits** (read).

Hosted RPC today and the account's prepaid credits (D1150, D1275): hosted { chains, quota (free requests today at this level), usedToday, resetsAt } and credits { open, payTo (the billing address, ETH on Robinhood Chain), usdPerMillion, ethUsd, packs, balanceUsd, left, drawnToday, rpcUsd, machinesUsd, aiUsd, aiMarkup, spend (by day), purchases }. One USD balance pays for hosted RPC past the free quota, source machines' daily price, and assistant messages past the free allowance (their model cost times aiMarkup). With no credits, each stops at its free allowance until 00:00 UTC. To buy: send ETH to payTo on any of credits.chains from a wallet this account holds (its sign-in wallet or a live key: a project wallet sends with transfer_funds { direction: "return", to: payTo }); it is credited automatically within a few minutes. A payment from its main wallet, or one not credited, is claimed with add_rpc_payment.

No arguments.

### `add_rpc_payment`

**Credit an RPC payment** (writes).

Credit a confirmed payment of ETH to the billing address (get_rpc_credits payTo) on one of credits.chains to the account's credits, once per transaction; payments from a sign-in wallet or live key are usually credited automatically already. It must come from a wallet this account holds: one of its live keys, its main wallet or the wallet it signs in with. Priced at ETH's live USD now. Answers { tx, wallet, ethWei, usd, requests } (requests: what the USD buys of hosted RPC at usdPerMillion).

Arguments:

- `tx` (required): string. The payment's transaction hash, 0x and 64 hex digits.
- `chain`: string. The chain it was sent on (credits.chains ids); robinhood when omitted.

## Strategy vaults

### `list_vaults`

**Strategy vaults** (read).

Every strategy vault taking orders or still dealing (any account's; the strategy behind each stays private, only its market and record show), with the account's orders, the runs and projects it may make a vault from, and the registry's protocolVaults (as list_protocol_vaults). vaults [{ id, name, manager (ftw.trade for the team’s own), managedByYou, managed (follows a project traded by hand), sponsored (the team's), realMoney (its record is a live project's real wallets since the vault was made), state (open, closed: no new money, closing: winds down at the next dealing, ended), terms { period (hourly, daily 00:00 UTC, weekly Monday, monthly the 1st), noticeHours (the cutoff before each dealing), managementFeePct (a year), performanceFeePct (on each holder's own gain), feeModel (investor: in units at each dealing, held feeHoldDays; withdrawal: on profit withdrawn), lockupHours, managerMinPct, style (hyperliquid or dealings), entryFeePct and exitFeePct (off each subscription and redemption), minimum, capacity }, nav (per unit now, fees accrued taken off), returnPct (since it began at 1), perf { d7, d30 (return over 7 and 30 days, null while younger), apr7, apr30 (the same as a simple APR, percent), aprAll (APR since launch, once a week old), aprSince { days, apr } (from a week old while d30 is null: return since launch annualised over its days), days (its age), spark (NAV at each day's end, 30 days) }, image, twitter (the manager's X handle: an X account they signed in with, so verified; null if none), raise, aum, investors, managerProfile { username, avatar } (null without a public profile), members [{ username, avatar (null: anonymous), sharePct, waiting (ETH of a subscription not yet dealt), manager, you }] (largest first, at most 50), nextDealingAt, orderDealingAt and orderCutoffAt (where an order placed now deals), yours { units, value, invested, pnl }, warning, criteria { maxDrawdownPct, maxLeverage, sameMarket, upgradeNoticeDays, timelockHours, scope { protocols, tokens, markets } }, scopeChange { at, scope } (a wider scope waiting out the timelock), safety { held, of, tags } (its safety profile, as the card shows it) }], orders, runs [{ id, name, usable, why }]. Paper: units are paper ETH from the paper main balance on robinhood.

No arguments.

### `get_vault`

**One strategy vault** (read).

A vault as list_vaults gives it, plus upgrade { at, name (manager only) } when a strategy upgrade waits for the next dealing, upgrades (how many so far), history [{ ts (unix s), nav }] since it began, its dealings (newest first, with the fees taken, what came in and went out, and upgraded where the vault switched runs), your orders in it, and for its manager manage { investors [{ handle, units, value }], pending orders, feesEarned, dashboard { setting: auto|none|link, shown (the link token its page shows), project (the one it follows), links (your links you may show) } }. Its page is ftw.trade/v/<id>.

Arguments:

- `id` (required): string. The vault id (list_vaults).

### `create_vault`

**Make a strategy vault** (writes).

Make a public vault that other accounts can invest in, following one of your strategy runs (deployment; run_strategy) or a project you trade by hand across its wallets and chains (project: a managed vault, valued from the project's five-minute snapshots, fundings not counted as gains). A live project gives the vault a real-money record (realMoney): its wallets' real value from the moment the vault is made, so money sent in before it is not counted; move money in or out afterwards only with transfer_funds, as a send from outside reads as a gain. A run on a live project gives a real-money record too, from the run's quarter-hour marks (its money across its wallets and venues, with top-ups and withdrawals set aside), never its paper book. Investors' units stay paper either way. Its NAV starts at 1 and moves by that return. People come in and go out only at dealing dates; an order must be in noticeHours before one to deal there. Fees go to your paper main balance at each dealing. Terms and criteria are fixed once made. criteria are rules ftw.trade enforces so investors need not trust you: maxDrawdownPct winds the vault down at a dealing that far below its high, maxLeverage caps leverage (a run's, now and after upgrades; a managed vault's perp orders), sameMarket keeps upgrades on the same market, timelockHours (or upgradeNoticeDays) delays an upgrade or a wider scope, and scope limits what the vault may trade: a managed vault's swaps, perps and venue moves outside it are refused before they are planned and again where its wallets sign, and its money may only move between its own wallets until it winds down (LP, lending, launches and Derive are refused from them for now). stake puts your own ETH in at the first dealing, locked until the vault winds down. At most 5 vaults a manager. Answers what get_vault does.

Arguments:

- `deployment`: string. The strategy run it follows (list_running_strategies, or runs in list_vaults). A paper run gives a paper record; a run on a live project, a real-money one.
- `project`: string. Instead of deployment: a project whose wallets you trade by hand (projects in list_vaults). A paper project is valued over the paper ETH its wallets were given; a live one by its real wallets from now on (a real-money record).
- `name` (required): string. Up to 60 characters.
- `about`: string. What the vault does, for investors; the strategy itself is never shown.
- `style`: "hyperliquid" | "dealings". hyperliquid: as a Hyperliquid vault, holders deposit and withdraw any time (dealt within a minute), each deposit stays in lockupHours (24 by default), the performance fee is taken only from the profit a holder withdraws (theirs alone, the manager's at once), and you must put in stake and keep at least 5% of the vault while others are in (new money waits until you add more). dealings (default): in and out at period's dealing dates, the fee taken per holder at each dealing and held 30 days.
- `lockupHours`: number (≥ 0, ≤ 168). hyperliquid style: how long a deposit stays in before it may be withdrawn. 24 by default.
- `period`: "hourly" | "daily" | "weekly" | "monthly". A dealings vault: how often it deals. Daily by default.
- `noticeHours`: number (≥ 0, ≤ 168). How long before a dealing orders must be in. 0 by default.
- `managementFeePct`: number (≥ 0, ≤ 5). A year, on NAV, accrued between dealings.
- `performanceFeePct`: number (≥ 0, ≤ 50). A share of each holder's own gain. A dealings vault takes it in units at each dealing from every holder above their own high-water mark and holds it 30 days (a wind-down meanwhile gives it back); a hyperliquid-style vault takes it only from the profit a holder withdraws, the manager's at once.
- `entryFeePct`: number (≥ 0, ≤ 5). Taken off each subscription when it deals. 0 by default.
- `exitFeePct`: number (≥ 0, ≤ 5). Taken off each redemption when it deals (not off a wind-down). 0 by default.
- `minimum`: number (≥ 0). The smallest subscription, ETH. 0.01 by default.
- `capacity`: number (> 0). The most the vault takes in, ETH; leave out for no cap.
- `image`: string. The vault's picture: a data URL (data:image/png;base64,…) of a PNG, JPEG or WebP, at most 256 KB.
- `raise`: object. Raise first, launchpad style: subscriptions (your stake included) are held until the first dealing `days` out; if they reach `target` ETH it launches there at NAV 1, else everyone is refunded and the vault ends.
- `twitter`: boolean. true shows your X (Twitter) account on the vault, so your reputation goes with it. Only an X account linked to this account counts (verified: you signed in with it at Account settings → Ways to sign in); refused if you have none.
- `stake`: number (≥ 0). Your own ETH subscribed at the first dealing, held from your paper main balance now and locked until the vault winds down (a hyperliquid-style vault needs one, not locked: you keep at least 5% of the vault while others are in).
- `criteria`: object. Rules ftw.trade holds the vault to, shown to investors as its safety profile.

### `update_vault`

**Rename, close, wind down or upgrade a vault** (writes).

The manager changes a vault's name, about, image or twitter, its state: open (takes subscriptions), closed (takes none; those waiting are refunded; redemptions go on), closing (at the next dealing every holder is paid out at NAV and it ends; set open again before then to call it off); or upgrades its strategy: deployment names another of your paper runs (start one with the new code by run_strategy), which the vault follows from its next dealing, NAV struck on the old run first so the record carries on; investors see an upgrade is coming (not what) and may redeem before the cutoff; the vault's criteria (maxLeverage, sameMarket, scope, the timelock) apply to it. deployment: null calls a waiting upgrade off. scope narrows the vault's limits at once or widens them after the timelock. dashboard picks what its page (ftw.trade/v/<id>) shows under the vault: one of your dashboard links (its token or /d/ address; create_dashboard_link, any access: a private link shows there too, and only its tabs), none, or auto (the followed project's newest public link); create_vault_dashboard builds one. An operator may instead set sponsored on any vault. Answers what get_vault does.

Arguments:

- `id` (required): string. The vault id (list_vaults).
- `name`: string
- `about`: string
- `image`: string. A new picture as a data URL; empty removes it.
- `twitter`: boolean. true shows your linked (verified) X account on the vault, false hides it.
- `state`: "open" | "closed" | "closing"
- `deployment`: string. Upgrade: the run to follow from the next dealing; empty calls a waiting upgrade off.
- `dashboard`: string. What its page shows under the vault: a link of yours (token or https://ftw.trade/d/… address), none, or auto.
- `scope`: any. A new scope, { protocols?, tokens?, markets? } as in create_vault, or null for none. One that allows nothing more applies at once (naming the current scope calls a waiting change off); a wider one waits out the timelock and applies at a dealing (scopeChange in get_vault).
- `sponsored`: boolean. Operators only, on its own: mark any vault sponsored (made or backed by the ftw.trade team), shown first with a badge.

### `create_vault_dashboard`

**Build a vault its own dashboard** (writes).

The manager gives a vault a dashboard for its investors in one step: a Vault tab on the project the vault follows (its unit value, return since launch, money in it and holders as numbers, its return and drawdown over time, money in and out by dealing, who holds it, its dealings; a tab already charting this vault is kept), shown on the vault's page (ftw.trade/v/<id>) under the vault. The page's link is kept when it already shows every tab; a link of some tabs gets a new one with the Vault tab too; with none, a private link of the Vault tab alone, so nothing else of the project shows. The link is pinned on the vault (update_vault dashboard). Then arrange it like any dashboard: update_dashboard adds notes, pictures, HTML and charts (vault sources vault-nav, vault-dealings, vault-holders, vault-stats take { vault }), set_look themes it. Answers { project, tab, added, link { token, path, access, tabs } }.

Arguments:

- `id` (required): string. The vault id (list_vaults).

### `place_vault_order`

**Subscribe to or redeem from a vault** (writes).

Paper. Subscribe { side: 'subscribe', amount } in ETH, held from your paper main balance on robinhood until it deals (units = amount ÷ NAV then), or redeem { side: 'redeem', units }, { side: 'redeem', all: true } or { side: 'redeem', profit: true } (only the gain above what you put in), paid to the main balance at NAV then. A vault whose terms.feeModel is investor takes each holder's performance fee on their own gains, in units at a dealing. It deals at the vault's next dealing whose cutoff it makes (orderDealingAt in list_vaults). Answers the order { id, state, dealingAt, cutoffAt, ... }.

Arguments:

- `id` (required): string. The vault id (list_vaults).
- `side` (required): "subscribe" | "redeem"
- `amount`: number (> 0). Subscribe: ETH.
- `units`: number (> 0). Redeem: units.
- `all`: boolean. Redeem every unit you hold at the dealing.
- `profit`: boolean. Redeem only your gain above what you put in, worked out at the dealing; what you put in stays invested.

### `update_vault_take_profit`

**Take profit from a vault at each dealing** (writes).

Paper. A standing take-profit on your units in a vault: { minPct } redeems the gain above what you put in at every dealing where it is at least that percent of it (what you put in stays invested); { minPct: null } stops it. Exit fees apply as to any redemption. Answers what get_vault does (yours.takeProfitPct).

Arguments:

- `id` (required): string. The vault id (list_vaults).
- `minPct`: number (≥ 1, ≤ 1000). The gain, as a percent of what you put in, that triggers taking it; leave out or null to stop.

### `delete_vault_order`

**Cancel a vault order** (writes).

Cancel one of your vault orders before its dealing's cutoff; a subscription's ETH goes back to the main balance. Answers the order.

Arguments:

- `order` (required): string. The order id (orders in list_vaults or get_vault).

### `list_protocol_vaults`

**Protocol vaults in the registry** (read).

The vault registry's protocol vaults: yield vaults and lending markets protocols run themselves (Ethena sUSDe, Sky sUSDS, Morpho, Aave, Kamino, Maple…), the team's picks (sponsored) and those accounts listed. Each { id, name, about, protocol { id, name, kind: lend|vault }, market, chain, asset, sponsored, live (real funds can go in through a live project), apyNow (supply APY now, or the share price's growth over the last day, percent), apr7 and apr30 (annualised over 7 and 30 days, percent; null while the feed is younger), aprSince { days, apr } (while apr30 is null: annualised over the days the feed has), spark (daily), use (how a strategy deposits in it), listedBy }. Money goes in through a strategy, not here.

No arguments.

### `add_protocol_vault`

**List a protocol vault in the registry** (writes).

Add a protocol's lending market or yield vault to the registry so others see its APR: protocol (its id, e.g. morpho, yearn, euler, aave, ethena) and market, one of the protocol's listed markets (base:usdc) or, for a protocol whose vaults are named by address (Morpho, Yearn, Euler…), chain:0x…, checked on chain as one of its own. name and about are optional. sponsored is for operators. Answers the listing.

Arguments:

- `protocol` (required): string. The protocol id.
- `market` (required): string. A listed market, or chain:0x… for a vault named by address.
- `name`: string
- `about`: string. Up to 600 characters.
- `sponsored`: boolean. Operators only.

### `update_protocol_vault`

**Edit a protocol vault listing** (writes).

Change a listing you made: name, about; an operator also sets sponsored. Answers the listing.

Arguments:

- `id` (required): string. The listing id (pv_…).
- `name`: string
- `about`: string
- `sponsored`: boolean

### `remove_protocol_vault`

**Remove a protocol vault listing** (writes).

Take a listing you made out of the registry (an operator may remove any). Answers { removed }.

Arguments:

- `id` (required): string. The listing id (pv_…).

## Buying and funding wallets

### `list_wallet_marketplace`

**Wallets for sale** (read).

Aged wallets for sale (each with its price, the ETH it comes with, its source, tier and transaction count), filtered by { count?, minAgeDays?, minTxCount?, tiers?: ["tier0", …], fundingGapSeconds?, sort?, order? }. Buy with create_wallet_purchase.

Arguments:

- `count`: integer (≥ 1)
- `minAgeDays`: number (≥ 0)
- `minTxCount`: integer (≥ 0)
- `tiers`: array of string
- `fundingGapSeconds`: integer (≥ 0)
- `sort`: string
- `order`: "asc" | "desc"

### `create_wallet_purchase`

**Order wallets** (writes).

Order wallets from the marketplace ({ wallet_ids, max_total: wei cap on the invoice, project: the project they are for, topup_per_wallet?: wei, category?, requestId?: a UUID, reused to retry safely }). Makes an invoice, sends nothing: pay it with create_wallet_order_approval then run_wallet_order_payment.

Arguments:

- `wallet_ids` (required): array of string
- `max_total` (required): string. An amount in wei, as a decimal string.
- `topup_per_wallet`: string. An amount in wei, as a decimal string.
- `project` (required): string. The project the wallets are for (letters, digits, space, _ . -; up to 80).
- `category`: string
- `requestId`: string. A lowercase UUID; send the same one to retry without ordering twice.

### `create_wallet_funding`

**Fund wallets through Husher** (writes).

Fund account wallets through Husher ({ wallets: [{ label: its account label, amount: wei }], max_total: wei cap, project: the project they are for, funding_delay?: seconds 0..7200, category?, requestId?: a UUID }). Makes an invoice, sends nothing: pay it with create_wallet_order_approval then run_wallet_order_payment.

Arguments:

- `wallets` (required): array of object
- `max_total` (required): string. An amount in wei, as a decimal string.
- `funding_delay`: integer (≥ 0, ≤ 7200)
- `project` (required): string. The project the wallets are for (letters, digits, space, _ . -; up to 80).
- `category`: string
- `requestId`: string. A lowercase UUID; send the same one to retry without ordering twice.

### `list_wallet_orders`

**Wallet orders** (read).

The account's wallet purchases and Husher fundings, each with its invoice and state ({ id? } for one, { project? } to narrow), and whether fulfillment is open.

Arguments:

- `id`: string. The order id (list_wallet_orders).
- `project`: string

### `delete_wallet_order`

**Cancel a wallet order** (writes).

Cancel an unpaid wallet order ({ id }), freeing what it reserved.

Arguments:

- `id` (required): string. The order id (list_wallet_orders).

### `create_wallet_order_approval`

**Prepare paying an invoice** (writes).

Prepare paying a wallet order from the main wallet's balance ({ id, approver?: the approving wallet's address, the account's first when left out }). Returns the grant and its digest: check its terms, sign the digest with personal_sign using the approver's key, and send the signature to run_wallet_order_payment. Nothing moves yet.

Arguments:

- `id` (required): string. The order id (list_wallet_orders).
- `approver`: string. 0x address of an approving wallet (get_wallet_payment_options).

### `run_wallet_order_payment`

**Pay an invoice** (trades or moves funds).

Pay a prepared wallet order ({ id, signature: the approver's personal_sign of the digest from create_wallet_order_approval }). Real funds move from the main wallet, so the connection must be allowed to trade live. After a purchase is paid, add_purchased_wallets puts the wallets in the account.

Arguments:

- `id` (required): string. The order id (list_wallet_orders).
- `signature` (required): string. The approver's personal_sign of the digest: 0x and 130 hex characters.

### `get_wallet_order_payment`

**Invoice payment status** (read).

Where a wallet order's payment is ({ id }): prepared, sending, sent, confirmed, failed or expired.

Arguments:

- `id` (required): string. The order id (list_wallet_orders).

### `get_wallet_payment_options`

**Approving wallets** (read).

The wallets that may approve this account's payments: an agent's sign-in wallet is one.

No arguments.

### `add_purchased_wallets`

**Take delivery of bought wallets** (writes).

After a purchase is paid and fulfilled, put its wallets into the account (and the project it named) ({ id }). Safe to repeat.

Arguments:

- `id` (required): string. The order id (list_wallet_orders).

## Wallet warming

### `get_warming_guide`

**Wallet warming guide** (read). Also answers to `warming_guide`.

How wallet warming works: workflow, defaults, limits, wallet states, MCP tools, and a create example. Read before create_warming_campaign.

No arguments.

### `list_warming_campaigns`

**List warming campaigns** (read).

Account wallet warming automations with walletStats. Same as Wallets → Warm up wallets.

No arguments.

### `get_warming_campaign`

**Get warming campaign** (read).

One warming automation with every wallet row (state, funding schedule, assigned token, hold timer, trade progress).

Arguments:

- `id` (required): string

### `create_warming_campaign`

**Start wallet warming** (writes).

Start wallet warming. Required: name, count, tokens. Optional tuning fields match get_warming_guide defaults. Pay husher invoices for project "Wallet warming".

Arguments:

- `name` (required): string. 1–60 characters
- `count` (required): integer. Wallet count
- `tokens` (required): string. Newline or comma-separated 0x addresses (or pass an array in the API body)
- `fundingMinEth`: number
- `fundingMaxEth`: number
- `gapMinSec`: integer
- `gapMaxSec`: integer
- `tradesMinDay`: integer
- `tradesMaxDay`: integer
- `tradeMinEth`: number
- `tradeMaxEth`: number
- `holdMinSec`: integer
- `holdMaxSec`: integer
- `warmDays`: integer

### `update_warming_campaign_state`

**Pause or resume warming** (writes). Also answers to `set_warming_campaign_state`.

Pause or resume a warming automation ({ id, state: running|paused }).

Arguments:

- `id` (required): string
- `state` (required): "running" | "paused"

### `get_warming_status`

**Warming dashboard** (read). Also answers to `warming_status`.

All automations, aggregate wallet counts, Hushxima fulfillment readiness, and open husher funding invoices.

No arguments.

### `get_warming_progress`

**Warming progress** (read). Also answers to `warming_progress`.

Monitor one automation: percent ready, counts by wallet state, next scheduled action, full wallet rows.

Arguments:

- `id` (required): string

### `get_warming_funding_status`

**Husher funding status** (read). Also answers to `warming_funding_status`.

Hushxima funding plan status for warmed wallets: payment address/amount while pending, delivery after pay. Optional walletIndex.

Arguments:

- `id` (required): string
- `walletIndex`: integer (≥ 0)

### `run_warming_trade`

**Schedule warming trade** (writes). Also answers to `schedule_warming_trade`.

Schedule a buy or sell on one wallet (automation must be running or paused). Optional token override and at (unix ms).

Arguments:

- `id` (required): string
- `walletIndex` (required): integer (≥ 0)
- `action` (required): "buy" | "sell"
- `token`: string
- `at`: integer

### `remove_wallets_from_project`

**Reclaim wallets from project spec** (writes). Also answers to `detach_wallets_from_project`.

Reclaim account wallets from a project by ftw label (project spec only; account wallets and warming unchanged). Refused if a launch lists the wallet as creator.

Arguments:

- `id` (required): string. The project id (list_projects).
- `accountLabels` (required): array of string
- `version`: integer

### `add_warming_wallets_to_project`

**Attach warmed wallets to project** (writes). Also answers to `attach_warming_wallets_to_project`.

Link warmed account wallets into a project category. Default walletState=ready; use all for in-progress wallets.

Arguments:

- `id` (required): string. The project id (list_projects).
- `campaignId` (required): string
- `category` (required): string
- `walletState`: "ready" | "all"
- `version`: integer

## Templates

### `export_project_template`

**Export a project as a template** (read).

One of your projects as a shareable template file (ftw.project-template version 1): wallet slots without keys, addresses or balances, its strategies, launches (without their tokens) and tabs, its market (the pool and token its buttons and charts default to), the pools its charts and market show, and any saved strategies named. To make questions for whoever imports it, replace a value with { "$param": "<name>" } and list the question under params (kinds: number, count, text, choice, boolean, token); a wallet slot with "count" makes that many wallets; a list item with "$if": { "$param": "<boolean>" } is left out when answered no.

Arguments:

- `id` (required): string. The project id (list_projects).
- `strategies`: array of string. saved strategy ids to bring along

### `preview_template`

**Preview a template** (read).

What importing a template would create: its questions, every wallet, strategy (with its code), launch, tab and pool, its fingerprint and warnings. Checks and compiles everything; writes nothing.

Arguments:

- `template` (required): object. A ftw.project-template file (export_project_template makes one).
- `answers`: object. Answers to the template's params, by name; a missing one takes its default.
- `name`: string

### `import_template`

**Import a template** (writes).

Make a new project from a template and its answers. Always on paper, with launches and range rules paused; saved strategies are saved, not deployed. Preview first and show the person what it brings.

Arguments:

- `template` (required): object. A ftw.project-template file (export_project_template makes one).
- `answers`: object. Answers to the template's params, by name; a missing one takes its default.
- `name`: string

## Project history

### `get_project_history`

**A project’s history** (read).

A project’s version history: every save of its settings, strategies, scripts and workflows is a commit on its current branch. Gives the branches, the commits on one (newest first, each with the files it changed), what is not committed yet, and whether its Git repo on Cloudflare Artifacts is up to date. The files are project.json (the document, its dashboard and strategies’ code left out), dashboard.json (the tabs and their widgets), strategies/<id>.flow, scripts/<name>.ts or .json, workflows/<name>.json, strategies/saved/<name>.flow (saved strategies that run on it, read only) and ftw.json (which file is which).

Arguments:

- `id` (required): string. The project id (list_projects).
- `branch`: string. A branch or ref to list; the current branch by default.
- `limit`: integer (≥ 1, ≤ 200)
- `path`: string. A file or folder in the project, e.g. project.json, dashboard.json, scripts/, workflows/dip-buyer.json, strategies/trend.flow.

### `get_project_diff`

**Compare two versions of a project** (read).

What changed between two versions of a project, file by file as unified diffs. By default from the current branch’s last commit to the project as it is now; from "HEAD~1" to "HEAD" is the last commit’s change; two branches compare an idea with the line it came from.

Arguments:

- `id` (required): string. The project id (list_projects).
- `from`: string. working (the project as it is now), HEAD (the current branch), a branch name, or a commit id (7+ characters); add ~n for n commits before it.
- `to`: string. working (the project as it is now), HEAD (the current branch), a branch name, or a commit id (7+ characters); add ~n for n commits before it.
- `path`: string. A file or folder in the project, e.g. project.json, dashboard.json, scripts/, workflows/dip-buyer.json, strategies/trend.flow.

### `get_project_file`

**Read a file from a project’s history** (read).

One file of a project as it was at a commit or branch (or is now, at working).

Arguments:

- `id` (required): string. The project id (list_projects).
- `ref`: string. working (the project as it is now), HEAD (the current branch), a branch name, or a commit id (7+ characters); add ~n for n commits before it.
- `path` (required): string. The file, e.g. project.json

### `commit_project`

**Commit a project with a message** (writes).

Record the project as it is now as a commit on its current branch, with a message saying what changed and why. Saves commit by themselves too (as “Saved settings” and the like); commit after a change worth naming, before trying something risky.

Arguments:

- `id` (required): string. The project id (list_projects).
- `message` (required): string. What changed, in a line.

### `restore_project`

**Restore a project from its history** (replaces or deletes).

Put a project back as it was at a commit or branch, or only some files of it (paths). The project document comes back with today’s wallets and paper or live mode; a script comes back as a new saved version (deploy it to run it); a running workflow is left alone; nothing the commit lacks is deleted. The restore is a commit of its own, so it can be undone with another. Show the person get_project_diff { from: "working", to: <ref> } first.

Arguments:

- `id` (required): string. The project id (list_projects).
- `ref` (required): string. working (the project as it is now), HEAD (the current branch), a branch name, or a commit id (7+ characters); add ~n for n commits before it.
- `paths`: array of string. Only these files or folders.

### `control_project_branch`

**Create, switch, rename or delete a project branch** (replaces or deletes).

Branches of a project’s history, to try an idea without losing the line it came from. create makes one at from (the current branch as it is now by default), and switch: true moves onto it; switch makes the project the branch’s last commit, after committing what was unsaved on the branch it leaves (wallets and mode stay as they are); rename takes to; delete removes one other than the current.

Arguments:

- `id` (required): string. The project id (list_projects).
- `action` (required): "create" | "switch" | "rename" | "delete"
- `name` (required): string. The branch: letters, digits, ".", "_" and "-".
- `from`: string. working (the project as it is now), HEAD (the current branch), a branch name, or a commit id (7+ characters); add ~n for n commits before it.
- `to`: string. rename: the new name.
- `switch`: boolean. create: move onto it too.

### `get_project_git_access`

**Clone a project with git** (writes).

The project’s Git repo on Cloudflare Artifacts and a token for it, after pushing what it lacks: clone it, read and edit the files with any tool, and with write: true push a new branch back, then pull_project_branch { branch } to bring it into the history and control_project_branch switch (or restore_project) to apply it. The token is a secret: use it in the commands given, never in a file or a message to anyone.

Arguments:

- `id` (required): string. The project id (list_projects).
- `write`: boolean. A token that may push.
- `minutes`: integer (≥ 5, ≤ 1440). How long the token lasts; 60 by default.

### `pull_project_branch`

**Pull a branch pushed with git** (writes).

Bring a branch pushed to the project’s Git repo into its history here: its new commits, each checked (text files only). It does not change the project; switch to the branch or restore from it to do that. as names it something else here.

Arguments:

- `id` (required): string. The project id (list_projects).
- `branch` (required): string
- `as`: string
