git:20261002.e5a5a95 to git:20261019.d793ca7

172 added, 98 removed. Audit A to A.

---
name: gdex-hl-outcomes
- description: HyperLiquid HIP-3 outcome / event markets — list markets, place outcome orders, and manage outcome positions
+ description: Hyperliquid HIP-4 outcome markets on GDEX — discover a market, pick a side, check spot collateral, place a limit order, track resting/fill state, cancel or exit (partial/full), and reconcile
---
- # GDEX: HyperLiquid Outcomes (HIP-3 Event Markets)
+ # GDEX: Hyperliquid HIP-4 Outcome Markets
- Trade discrete-outcome event markets running on HyperLiquid HIP-3 permissioned
- perp DEXes. These are prediction-market-style instruments where each market
- resolves to a discrete outcome rather than a continuous price.
+ GDEX outcome markets are **Hyperliquid HIP-4 outcome markets**: each market has
+ two or more sides; each side is a token that trades at a probability price in
+ (0, 1), and the winning side settles on Hyperliquid. They are traded with spot
+ collateral (the market's `quoteToken`, USDC today) from the managed wallet's
+ **Hyperliquid spot balance**.
+ They are **not** HIP-3 builder perps, not perps, and not spot tokens. **GDEX has
+ no Polymarket or Kalshi integration.**
+
## When to Use
- - Listing the currently open / resolved outcome markets
- - Getting an account state on an outcomes perp dex
- - Opening, cancelling, or closing positions on an outcome market
+ - "What prediction / outcome markets can I trade?" → discover
+ - Buy or sell one side of an outcome market
+ - Track a resting order, cancel it, or exit a position fully or partially
+ - Explain what happens when a market expires or resolves
## Prerequisites
- - `@gemachdao/gdex-skill` installed
- - Authenticated via managed custody — see **gdex-authentication**
- - Trading enabled on HL (one-time `/v1/hl/enable_trading`) — see
- **gdex-perp-trading** for the HIP-3 setup flow
- - For write operations: a pre-built `computedData` payload built by the
- caller using the standard managed-custody encrypted-payload contract
- (ABI schemas for outcome actions live in
- `src/api/services/ServiceHyperLiquid.ts` of the backend)
+ - `@gemachdao/gdex-skill` installed and a managed-custody session — see
+ **gdex-authentication**. You need `apiKey`, the **control** `walletAddress`
+ used at sign-in, and `sessionPrivateKey`. The SDK and the MCP tools build
+ and encrypt the payload for you; you don't need the backend ABI.
+ - The **managed** wallet address (outcome balances and orders live there).
+ - Quote collateral in the managed wallet's **HL spot** balance (step 3).
- ## Backend Endpoints
+ ## Workflow
- | Method | Path | Purpose |
- |--------|------|---------|
- | `GET` | `/v1/hl/outcomes` | List outcome markets |
- | `GET` | `/v1/hl/outcome_account` | Account state on outcomes dex |
- | `POST` | `/v1/hl/outcome/create_order` | Open an outcome order |
- | `POST` | `/v1/hl/outcome/cancel_order` | Cancel an outcome order |
- | `POST` | `/v1/hl/outcome/close_order` | Close an outcome position |
+ ### 1. Discover
- All read endpoints accept an optional `dex` query parameter to target a
- specific HIP-3 perp dex registered via `/v1/hl/perp_dexes`.
+ ```typescript
+ const { markets } = await skill.getHlOutcomeMarkets({ state: 'active', query: 'BTC' });
+ ```
- ## SDK Usage
+ MCP: `hl_outcomes` (`state`, `query`, `excludeFallback`, `limit`, `withVolume`).
+ Each market is normalised:
- The SDK builds the encrypted payload for you — pass structured params plus
- managed-custody credentials (`apiKey`, `walletAddress` = your **control**
- address from sign-in, `sessionPrivateKey`). A pre-built `computedData` is
- still accepted for advanced callers.
+ | Field | Meaning | Source |
+ |---|---|---|
+ | `outcomeId` | Market id | `outcomeMeta.outcomes[].outcome` |
+ | `title` | Readable question built from the market parameters | `name` + `description` (`key:value\|…`), question parameters |
+ | `sides[]` | `{ sideIndex, label, rawName, coin, balanceCoin, assetId, exchangeAssetId, price }` | `sideSpecs[]`. `coin` = `#<token>` or `#<outcomeId*10+sideIndex>`. `balanceCoin` = `+<assetId>`. `price` = `allMids[coin]`. |
+ | `quoteToken` | Collateral currency | `outcomes[].quoteToken` |
+ | `state` | `active` · `pre_launch` (no mids yet) · `expired` (deadline passed, not yet settled) · `settled` | `questions[].settledNamedOutcomes`, the expiry parameter, `allMids` |
+ | `rules` | Price 0.00001–0.99999, tick 0.00001 (5 dp); whole-contract size; min notional 11 quote; full-position sell exempt; market orders ±5%; `reduceOnlyHonoured: false` | GDEX backend + gdex.pro (see Rules) |
+ | `settlement` | `claimViaGdex: false`, resolution source and deadline, and what to do | see "Expiry and settlement" |
+ | `volume24h` | Only with `withVolume`: `{ volume, asOf, source, coverage }`. `coverage` is `complete`, `partial` or `unavailable`. A zero that was actually read stays `0`; `null` means no reading. The wait is bounded (default 2.5 s), so a slow feed never blocks discovery. | Hyperliquid WS `activeAssetCtx.dayNtlVlm` |
- ```typescript
- import { GdexSkill } from '@gemachdao/gdex-skill';
+ `isFallback: true` marks the "none of the listed outcomes" leg of a
+ multi-outcome question.
- const skill = new GdexSkill();
- skill.loginWithApiKey(process.env.GDEX_API_KEY!);
+ ### 2. Pick a side
- // 1. List markets. Each `mids` key is a coin id: "#<outcomeId><sideIndex>",
- // e.g. "#1010" = outcome 101 / side 0 (Yes), "#1011" = side 1 (No).
- const markets = await skill.getHlOutcomes({ status: 'open' });
+ Read `sides[].label`. **Don't assume side 0 is "Yes".**
- // 2. Enable trading once before the first outcome order (see Prerequisites).
- await skill.hlEnableTrading({ apiKey, walletAddress, sessionPrivateKey });
+ - A match market's sides can be the two team names (`LOS` / `T1`).
+ - A question can have one market per candidate, each with its own Yes/No.
- // 3. Account state for one market (outcomeId is REQUIRED).
- const state = await skill.getHlOutcomeAccount({
- userAddress: managedAddress, // the managed wallet holds outcome balances
- outcomeId: 101,
- });
+ Select a side with exactly one of these:
- // 4. Place an order. price is a probability in [0,1]; size is in contracts.
- const order = await skill.createHlOutcomeOrder({
- apiKey, walletAddress, sessionPrivateKey,
- outcomeId: 101,
- coin: '#1010', // Yes side of outcome 101
- isBuy: true,
- price: '0.55', // limit; pass isMarket:true to take the book
- size: '20',
- isMarket: false,
+ - `coin` (e.g. `"#143151"`)
+ - `sideIndex`
+ - `sideLabel` (exact label)
+
+ The SDK rejects a coin that doesn't belong to `outcomeId`.
+
+ ### 3. Check collateral
+
+ ```typescript
+ const c = await skill.checkHlOutcomeCollateral({
+ managedAddress, quoteToken: market.quoteToken, size: '120', price: '0.12',
});
+ // { available, required: 14.4, sufficient: true|false, ... }
+ ```
- // 5. Cancel / close
- await skill.cancelHlOutcomeOrder({ apiKey, walletAddress, sessionPrivateKey, outcomeId: 101, coin: '#1010', orderId });
- await skill.closeHlOutcomeOrder({ apiKey, walletAddress, sessionPrivateKey, outcomeId: 101, coin: '#1010', price: '0', size: '20', isMarket: true });
+ MCP: `hl_outcome_check_collateral`.
+
+ - Buys draw on the managed wallet's HL **spot** balance of `quoteToken`
+ (`total − hold`), not on the perp account.
+ - If it's short, move USDC from perp to spot or fund the account. See
+ **gdex-perp-funding**.
+ - If a market is ever quoted in a token other than USDC, swap into it with
+ `hl_swap_collateral` first.
+
+ ### 4. Place a limit order
+
+ ```typescript
+ const r = await skill.createHlOutcomeOrder({
+ apiKey, walletAddress /* control */, sessionPrivateKey,
+ outcomeId: 14315, sideLabel: 'Yes', isBuy: true,
+ price: '0.12', // probability, at most 5 decimals
+ size: '120', // whole contracts; 120 × 0.12 = 14.4 ≥ 11
+ });
+ // r.status: 'resting' (r.orderId) | 'filled' (r.filledSize, r.avgPrice) | 'rejected' (r.error)
```
- ## Collateral currency (HIP-3 / HIP-4) — swap before trading
+ MCP: `hl_create_outcome_order`.
- Each outcome market is quoted in a specific stablecoin (its `quoteToken` —
- `USDC`, `USDH`, `USDE`, or `USDT0`). HIP-3 and HIP-4 deployments use different
- quote currencies, so **before placing an order you must hold that market's
- currency in your HL spot balance.** If you only hold USDC, swap into the
- market's currency first with `hlSwapCollateral` (a HL spot swap), then swap
- back when done.
+ - `isBuy: false` sells side tokens you hold. It can't short.
+ - `isMarket: true` takes the book at ±5% of the top level. Omit `price`
+ (or pass `"0"`) for market orders.
+ - Pass `managedAddress` on sells. The SDK then checks the real position, and
+ a sell of the whole position is allowed below the minimum.
+ ### 5. Track resting and fill state
+
```typescript
- // Check the market's currency
- const m = (await skill.getHlOutcomes({ status: 'open' })).data.meta.outcomes
- .find((o) => o.outcome === 101);
- console.log(m.quoteToken); // e.g. 'USDH'
+ const s = await skill.getHlOutcomeAccountState({ userAddress: managedAddress, outcomeId: 14315 });
+ ```
- // If it's not USDC, swap USDC -> that currency (amount = NON-USDC token amount).
- // HyperLiquid enforces a $10 minimum per swap order.
- await skill.hlSwapCollateral({
- apiKey, walletAddress, sessionPrivateKey,
- fromToken: 'USDC', toToken: 'USDH', amount: '15', // buy 15 USDH
- });
+ MCP: `hl_outcome_account`. It returns:
- // ...place / manage outcome orders in that currency...
+ - `positions[]`: `size`, `hold`, `available`, `avgEntryPrice`, `markPrice`,
+ `value`, `unrealizedPnl`.
+ - `openOrders[]`: `orderId`, `price`, `size`, `originalSize`, `filledSize`,
+ and `status` (`resting` or `partially_filled`).
+ - `fills[]`.
+ - `historicalOrders[]`, with Hyperliquid's status, e.g. `filled`, `canceled`,
+ `minTradeNtlRejected`.
- // Swap leftover back to USDC when finished
- await skill.hlSwapCollateral({
- apiKey, walletAddress, sessionPrivateKey,
- fromToken: 'USDH', toToken: 'USDC', amount: '15',
- });
+ ### 6. Cancel, or exit fully or partially
+
+ ```typescript
+ await skill.cancelHlOutcomeOrder({ apiKey, walletAddress, sessionPrivateKey,
+ outcomeId: 14315, coin: '#143150', orderId: String(r.orderId) });
+
+ // Full exit: omit size (the backend sells everything available).
+ await skill.closeHlOutcomeOrder({ apiKey, walletAddress, sessionPrivateKey,
+ outcomeId: 14315, sideLabel: 'Yes', price: '0.15' });
+
+ // Partial exit: the sold part AND the remainder must each be worth ≥ 11.
+ await skill.closeHlOutcomeOrder({ apiKey, walletAddress, sessionPrivateKey,
+ outcomeId: 14315, sideLabel: 'Yes', price: '0.15', size: '80', managedAddress });
```
- Supported swap currencies: `USDC` ⇄ `USDH` / `USDE` / `USDT0`. MCP exposes this
- as the `hl_swap_collateral` tool with the same structured params.
+ MCP: `hl_cancel_outcome_order` and `hl_close_outcome_order`.
- ## Notes
+ - `orderId` is the numeric Hyperliquid oid.
+ - A close is always a sell of held tokens.
+ - Any partial fill that happened before a cancel stays in the position.
- - **Coin format:** orders take `coin` as `"#<outcomeId><sideIndex>"` — side 0
- is the first `sideSpecs` entry (usually "Yes"), side 1 the second ("No").
- Current prices are in the `mids` map returned by `getHlOutcomes`.
- - **Price is a probability** in `(0,1)`; **size is contracts**. HyperLiquid
- enforces its own minimum order value (~$10–11 notional).
- - **`walletAddress` is the control address** used at sign-in (the same rule as
- perp trading); outcome balances/positions live under the **managed** address,
- so pass the managed address to `getHlOutcomeAccount`.
- - Outcome markets settle through HyperLiquid **spot** collateral, separate from
- the perp engine. Funding an HL account (via **gdex-perp-funding**) and placing
- an outcome order can move USDC from the perp to the spot/outcomes balance;
- check both `getHlAccountState` (perp) and `getHlSpotState` (spot).
- - MCP: the same flow is available via the `hl_enable_trading`,
- `hl_create_outcome_order`, `hl_cancel_outcome_order`, and
- `hl_close_outcome_order` tools with these structured params.
+ ### 7. Reconcile
+
+ Call `hl_outcome_account` again. Check that:
+
+ - a cancelled order has left `openOrders`;
+ - fills are in `fills`;
+ - `positions[].available` matches what you expect.
+
+ If a write's outcome is unknown, check here before you submit anything again.
+
+ ## Rules (and where they come from)
+
+ - **Price** must be in (0, 1). The backend rounds to 5 decimals
+ (`hyperLiquidOutcomes.ts:85-91`). The SDK rejects anything outside
+ [0.00001, 0.99999] or with more than 5 decimals.
+ - **Size** is whole contracts (`OUTCOME_SIZE_DECIMALS = 0`,
+ `hyperLiquidOutcomes.ts:69,185-188`).
+ - **Minimum order** is size × price ≥ **11** quote units. This is gdex.pro's
+ rule (`HyperliquidOutcomeDetail.tsx:84`). Hyperliquid's own floor is 10. A
+ sell of the full position is exempt. Partial closes must leave both legs at
+ 11 or more.
+ - **`reduceOnly`** is ignored: the backend always sends `r:false`.
+ - **Tick size and minimum order** aren't per-market fields in `outcomeMeta`.
+ The rules above apply to every outcome market.
+
+ ## Parameter modes
+
+ Every write accepts **either** the structured fields **or** a pre-built
+ `computedData` on its own. Never mix them.
+
+ - **Structured fields:** the SDK validates against the live market first.
+ - **Pre-built `computedData`:** posted as is, with no validation.
+
+ A mix of the two is rejected with `VALIDATION_ERROR` before anything is signed
+ or sent, as are missing structured fields. `dex` and `status` are not read by
+ the backend for outcomes.
+
+ ## Expiry and settlement
+
+ - **`expired`:** the market's deadline has passed, but Hyperliquid hasn't
+ marked it settled yet. The SDK refuses new orders. You can still try to
+ exit with `hl_close_outcome_order`, but it may fail if the book has halted.
+ - **`settled`:** the outcome is listed in its question's
+ `settledNamedOutcomes`. There is no book.
+ - **Settlement is done on Hyperliquid** by the market deployer (the
+ `settleOutcome` and `settleQuestion` permissions in `outcomeMeta.deployers`).
+ - **GDEX has no claim, redeem or settle endpoint.** The backend's outcome
+ routes are exactly: `outcomes`, `outcome_account`, `outcome/create_order`,
+ `outcome/cancel_order`, `outcome/close_order`. After resolution there is
+ nothing to claim through GDEX. Read the managed wallet's HL spot balances
+ (`hl_outcome_account`) to see the result.
+
+ ## Backend endpoints
+
+ | Method | Path | Notes |
+ |--------|------|-------|
+ | `GET` | `/v1/hl/outcomes` | `{ meta: outcomeMeta, mids: allMids }`. Ignores query params. `outcomeMeta` is cached for 60 s. |
+ | `GET` | `/v1/hl/outcome_account?address=<managed>&outcomeId=<id>` | positions (spot balances `+<assetId>`), openOrders, fills, historicalOrders |
+ | `POST` | `/v1/hl/outcome/create_order` | `{ computedData }` |
+ | `POST` | `/v1/hl/outcome/cancel_order` | `{ computedData }` |
+ | `POST` | `/v1/hl/outcome/close_order` | `{ computedData }` (always a sell) |