Skip to content

Commit 6754be2

Browse files
Hiksangclaude
andcommitted
feat(outcome): MCP read-only tools + skill bundle docs
Wires the v0.13.0 outcome surface into the agent integration layer. MCP server (3 new tools, 18 → 21 total): - get_outcome_markets — list active outcome markets with parsed metadata (class/underlying/expiry/targetPrice/period) and per-side info (encoding, asset id, current mid). Public read. - get_outcome_book — orderbook for one outcome side. Public read. - get_outcome_positions — active account's outcome holdings (size, entry notional, mark, uPnL). Requires configured HL wallet/agent. Per existing MCP design, server is advisor-only — no execution endpoints. Agents pair these with the CLI for placing orders, which gates on dry-run + explicit user confirmation. Skill bundle: - references/commands.md gains an "Outcome Markets (Hyperliquid HIP-4)" section: full command tree, identifier model (5 forms), enforced foot-guns (min notional / encoded-side mismatch / USDH balance / venue rejection), and a 6-step agent workflow. - SKILL.md intro mentions HIP-4 support; reference list includes outcome; MCP tool count updated to 21 with public read-only tools enumerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a855e14 commit 6754be2

3 files changed

Lines changed: 138 additions & 3 deletions

File tree

skills/perp-cli/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ metadata:
1111

1212
# perp-cli
1313

14-
Multi-DEX perpetual futures CLI + MCP server for Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), and Aster (BNB Chain).
14+
Multi-DEX perpetual futures CLI + MCP server for Pacifica (Solana), Hyperliquid (HyperEVM), Lighter (Ethereum), and Aster (BNB Chain). v0.13.0+ also supports Hyperliquid Outcome markets (HIP-4) — fully-collateralized binary/range contracts quoted in USDH.
1515

1616
## Instructions
1717

@@ -25,7 +25,7 @@ Activate whenever the user wants to do anything against perp-cli, including: sca
2525

2626
This skill uses progressive disclosure. Read these on demand:
2727

28-
- `references/commands.md` — full command tree for every group (market, account, trade, arb, funds, wallet, risk, history, strategy, background, alerts, settings, backtest)
28+
- `references/commands.md` — full command tree for every group (market, account, trade, outcome, arb, funds, wallet, risk, history, strategy, background, alerts, settings, backtest)
2929
- `references/agent-operations.md` — non-interactive setup flows, idempotency rules, error codes, common mistakes
3030
- `references/strategies.md` — funding-arb decision framework + risk-management playbook (read before designing arb strategy)
3131

@@ -320,7 +320,7 @@ When the user first sets up a wallet, ask:
320320
321321
## MCP Server
322322

323-
The package also ships a 18-tool MCP server (no API keys required for read-only market data):
323+
The package also ships a 21-tool MCP server (no API keys required for read-only market data`get_markets`, `get_orderbook`, `get_funding_rates`, `get_prices`, `get_outcome_markets`, `get_outcome_book`, plus 15 account/advisor tools):
324324

325325
```json
326326
{

skills/perp-cli/references/commands.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,75 @@ perp --json -e <EX> trade spot-balance
8989
perp --json trade multi <legs...>
9090
```
9191

92+
## Outcome Markets (Hyperliquid HIP-4)
93+
94+
Fully-collateralized binary/range contracts on Hyperliquid. **Quote token: USDH (NOT USDC)**. No leverage / no liquidation. Min order $10 USDH (price × size ≥ 10). Currently 1 live market on mainnet (BTC binary daily settling at 06:00 UTC).
95+
96+
```bash
97+
# List active outcome markets — parses the description string into
98+
# class / underlying / expiry / targetPrice / period and lists each side
99+
# with mid + assetId.
100+
perp --json outcome list
101+
102+
# Orderbook for one side. <side> accepts: 0/1, Yes/No, or #<enc> / +<enc>
103+
# (the encoded form must match the outcome arg — mismatch throws
104+
# INVALID_PARAMS).
105+
perp --json outcome book <outcome> <side> [--depth N=10]
106+
107+
# Holdings + open orders.
108+
perp --json outcome positions
109+
perp --json outcome orders
110+
111+
# Place orders. ALWAYS dry-run first.
112+
perp --json outcome buy <outcome> <side> <usd> --dry-run
113+
perp --json outcome sell <outcome> <side> <usd> --dry-run
114+
115+
# Live execution. --limit makes it GTC; without --limit it's IoC at top-of-book +/-5%.
116+
perp --json outcome buy <outcome> <side> <usd> [--limit <px>] [--tif gtc|ioc|alo]
117+
perp --json outcome sell <outcome> <side> <usd> [--limit <px>] [--tif gtc|ioc|alo]
118+
119+
# Cancel a resting order.
120+
perp --json outcome cancel <outcome> <side> <oid>
121+
```
122+
123+
### Identifier model
124+
125+
Same outcome side can be referenced 5 ways — the CLI normalises:
126+
127+
| Form | Example (BTC daily Yes) |
128+
|---|---|
129+
| `<outcome>, <side>` integer | `1, 0` |
130+
| `<outcome>, <side>` name | `1, yes` |
131+
| `#<enc>` (l2Book / candle / allMids coin) | `#10` |
132+
| `+<enc>` (spotClearinghouseState balance coin) | `+10` |
133+
| asset id (HL `/exchange` payload) | `100000010` |
134+
135+
Encoding formula: `enc = 10 * outcome + side`; asset id = `100,000,000 + enc`.
136+
137+
### Foot-guns enforced (Rule #2)
138+
139+
- **Min notional:** `price * size < 10 USDH``INVALID_PARAMS` with remediation. Enforced in dry-run too.
140+
- **Encoded-side mismatch:** `outcome buy 2 #10` (where `#10` decodes to outcome=1) → `INVALID_PARAMS`, never silently routes to outcome 2.
141+
- **Insufficient USDH:** pre-checked before order send → `INSUFFICIENT_BALANCE` with "bridge USDC→USDH" remediation. Resolves the user address through the OWS-stored agent meta when no master pk is configured.
142+
- **Venue rejection:** HL returns HTTP 200 with `statuses[0].error` even when an order is rejected; the adapter throws `EXCHANGE_ERROR` so a "placed" success message never masks a failure.
143+
144+
### Agent workflow recommendation
145+
146+
```
147+
1. perp --json outcome list # discover markets, parse description
148+
2. perp --json -e hyperliquid market mid <UNDERLYING>
149+
# if class:priceBinary, compare against targetPrice
150+
3. perp --json outcome book <outcome> <side> --depth 10
151+
# check liquidity (single-MM mirror book risk)
152+
4. perp --json outcome buy <outcome> <side> <usd> --dry-run
153+
# validate notional, get user approval
154+
5. perp --json outcome buy <outcome> <side> <usd> [--limit <px>] [--tif ...]
155+
# execute (only after explicit user OK)
156+
6. perp --json outcome positions # confirm fill / monitor
157+
```
158+
159+
Settlement at `expiryMs` is venue-side: winning side → 1 USDH per share, losing → 0. Decide before expiry whether to self-close or let the venue settle.
160+
92161
## Funds (deposit, withdraw, transfer, bridge, rebalance)
93162

94163
All fund movement lives under `perp funds`.

src/mcp-server.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,76 @@ server.tool(
300300
},
301301
);
302302

303+
// ============================================================
304+
// Outcome markets (Hyperliquid HIP-4) — public reads
305+
// ============================================================
306+
307+
server.tool(
308+
"get_outcome_markets",
309+
"List active Hyperliquid Outcome markets (HIP-4). Each market has parsed metadata (underlying asset, target price, expiry, period) and per-side info (Yes/No name, encoding, asset id, current mid). Public read — no API key needed.",
310+
{},
311+
async () => {
312+
try {
313+
const { HyperliquidAdapter } = await import("./exchanges/hyperliquid.js");
314+
const { HyperliquidOutcomeAdapter } = await import("./exchanges/hyperliquid-outcome.js");
315+
const hl = new HyperliquidAdapter(undefined, false);
316+
await hl.init();
317+
const out = new HyperliquidOutcomeAdapter(hl);
318+
const markets = await out.getMarkets();
319+
return { content: [{ type: "text", text: ok(markets, { count: markets.length, source: "outcomeMeta" }) }] };
320+
} catch (e) {
321+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
322+
}
323+
},
324+
);
325+
326+
server.tool(
327+
"get_outcome_book",
328+
"Get the orderbook for one outcome side. Public read — no API key needed.",
329+
{
330+
outcome: z.number().int().nonnegative().describe("Outcome id from outcomeMeta (e.g., 1 for the BTC binary daily)"),
331+
side: z.number().int().min(0).max(9).describe("Side index within the outcome (0 = first sideSpecs entry like 'Yes', 1 = second like 'No')"),
332+
},
333+
async ({ outcome, side }) => {
334+
try {
335+
const { HyperliquidAdapter } = await import("./exchanges/hyperliquid.js");
336+
const { HyperliquidOutcomeAdapter } = await import("./exchanges/hyperliquid-outcome.js");
337+
const hl = new HyperliquidAdapter(undefined, false);
338+
await hl.init();
339+
const out = new HyperliquidOutcomeAdapter(hl);
340+
const book = await out.getOrderbook(outcome, side);
341+
return { content: [{ type: "text", text: ok(book, { outcome, side, assetId: HyperliquidOutcomeAdapter.assetId(outcome, side) }) }] };
342+
} catch (e) {
343+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { outcome, side }) }], isError: true };
344+
}
345+
},
346+
);
347+
303348
// ============================================================
304349
// Account tools (need private key)
305350
// ============================================================
306351

352+
server.tool(
353+
"get_outcome_positions",
354+
"Get the active account's open outcome positions (size, entry notional, mark price, unrealized PnL). Requires a configured Hyperliquid wallet or agent.",
355+
{},
356+
async () => {
357+
try {
358+
const { HyperliquidAdapter } = await import("./exchanges/hyperliquid.js");
359+
const { HyperliquidOutcomeAdapter } = await import("./exchanges/hyperliquid-outcome.js");
360+
const adapter = await getOrCreateAdapter("hyperliquid");
361+
if (!(adapter instanceof HyperliquidAdapter)) {
362+
return { content: [{ type: "text", text: err("Outcome positions are Hyperliquid-only", { exchange: "hyperliquid" }) }], isError: true };
363+
}
364+
const out = new HyperliquidOutcomeAdapter(adapter);
365+
const positions = await out.getPositions();
366+
return { content: [{ type: "text", text: ok(positions, { count: positions.length }) }] };
367+
} catch (e) {
368+
return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e)) }], isError: true };
369+
}
370+
},
371+
);
372+
307373
server.tool(
308374
"get_balance",
309375
"Get account balance (equity, available margin, margin used, unrealized PnL) on an exchange",

0 commit comments

Comments
 (0)