> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outcome.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Discover HIP-4 events, categories, and typed markets

> Reference for PredictionEventAdapter: list and filter prediction events, fetch categories, and discover typed HIP-4 markets with grouping and pagination.

The Events Adapter (`adapter.events`) is your entry point for discovering what is tradeable on HIP-4. It maps raw Hyperliquid outcome metadata into structured `PredictionEvent` objects, with live midpoint prices on each outcome, and into typed `HIP4Market` objects. Results are cached for 30 seconds, so repeated calls within that window return immediately without hitting the API.

***

## `fetchEvents(params?)`

Returns a paginated, optionally filtered list of prediction events. Internally, the adapter fetches `outcomeMeta` and `allMids` in parallel, builds events from the metadata, and enriches outcome prices from the mids response.

```typescript theme={null}
const events = await adapter.events.fetchEvents({ active: true, limit: 20 });
```

### Parameters

<ParamField path="params.category" type="string">
  Filter events by category slug. Pass `"custom"` or `"recurring"`. Passing
  `"all"` is treated as no filter.
</ParamField>

<ParamField path="params.active" type="boolean">
  When `true`, only events with `status === "active"` are returned.
</ParamField>

<ParamField path="params.limit" default="50" type="number">
  Maximum number of events to return.
</ParamField>

<ParamField path="params.offset" default="0" type="number">
  Pagination offset. Applied after category, active, and query filters.
</ParamField>

<ParamField path="params.query" type="string">
  Case-insensitive search string matched against event `title` and
  `description`.
</ParamField>

### Return type

`Promise<PredictionEvent[]>`

<ResponseField name="id" type="string" required>
  Event identifier. Prefix `q` for question-based events (e.g. `"q5"`); prefix
  `o` for standalone outcome events (e.g. `"o1338"`).
</ResponseField>

<ResponseField name="title" type="string" required>
  Event title, as Hyperliquid sends it (e.g. `"template:priceTouch"` for a template event). Recurring events get a label such as `"BTC > $69070 (1d)"`.
</ResponseField>

<ResponseField name="parsedTitle" type="string">
  Readable event title. Template events are rendered from Hyperliquid's template registry, as in `fetchMarkets`; other events repeat `title`. Available from 1.3.0.
</ResponseField>

<ResponseField name="description" type="string" required>
  Event description text.
</ResponseField>

<ResponseField name="category" type="string" required>
  Category slug: `"custom"` or `"recurring"`.
</ResponseField>

<ResponseField name="markets" type="PredictionMarket[]" required>
  Markets belonging to this event. Each market corresponds to one HIP-4 outcome. A market's `question` and its `outcomes[].name` are the names Hyperliquid sends. `parsedQuestion` and `outcomes[].parsedName` carry the readable ones, available from 1.3.0.
</ResponseField>

<ResponseField name="status" type="string" required>
  `"active"` | `"pending_resolution"` | `"resolved"` | `"cancelled"`. The HIP-4
  adapter currently sets only `"active"` and `"resolved"`: a question event is
  `"resolved"` once all its named outcomes have settled, and standalone outcome
  events are always `"active"`.
</ResponseField>

<ResponseField name="endDate" type="string" required>
  Expiry date string. Populated for recurring markets; empty string otherwise.
</ResponseField>

<ResponseField name="totalVolume" type="string" required>
  Cumulative volume across all markets. Always `"0"` in the current
  implementation.
</ResponseField>

### Example

```typescript theme={null}
import { createHIP4Adapter } from "@outcome.xyz/hip4";

const adapter = createHIP4Adapter({ testnet: false });
await adapter.initialize();

// List active recurring markets, first page
const events = await adapter.events.fetchEvents({
  category: "recurring",
  active: true,
  limit: 10,
  offset: 0,
});

for (const event of events) {
  console.log(event.id, event.parsedTitle ?? event.title, event.status);
  // e.g. "o1338", "BTC > $69070 (1d)", "active"

  for (const market of event.markets) {
    for (const outcome of market.outcomes) {
      console.log(outcome.parsedName ?? outcome.name, outcome.tokenId, outcome.price);
      // e.g. "Yes", "#13380", "0.62"
    }
  }
}
```

***

## `fetchEvent(eventId)`

Fetches a single event by its ID. Loads the full event list via the cache and finds the match. Throws if the event is not found.

```typescript theme={null}
const event = await adapter.events.fetchEvent("q5");
```

### Parameters

<ParamField path="eventId" type="string" required>
  The event ID to look up. Use `q{n}` for question-based events or `o{n}` for
  standalone outcome events.
</ParamField>

### Return type

`Promise<PredictionEvent>` - same shape as each element returned by `fetchEvents`.

Throws `"HIP-4 event not found: {eventId}"` if the ID does not match any event.

### Example

```typescript theme={null}
// Fetch a question-based event
const questionEvent = await adapter.events.fetchEvent("q5");
console.log(questionEvent.parsedTitle); // e.g. "Who will win the race?"
console.log(questionEvent.markets.length); // number of competing outcomes

// Fetch a standalone recurring outcome event
const recurringEvent = await adapter.events.fetchEvent("o1338");
console.log(recurringEvent.category); // "recurring"
console.log(recurringEvent.endDate); // "20260311-0300"
```

***

## `fetchCategories()`

Returns the list of available event categories. This call is synchronous under the hood - no API request is made.

```typescript theme={null}
const categories = await adapter.events.fetchCategories();
```

### Return type

`Promise<PredictionCategory[]>`

The response always contains exactly two entries:

| `id` | `name` | `slug` |
| - | - | - |
| `custom` | `Custom` | `custom` |
| `recurring` | `Recurring` | `recurring` |

### Example

```typescript theme={null}
const categories = await adapter.events.fetchCategories();
// [
//   { id: "custom", name: "Custom", slug: "custom" },
//   { id: "recurring", name: "Recurring", slug: "recurring" }
// ]
```

***

## `fetchMarkets(params?)`

Returns typed `HIP4Market` objects for all HIP-4 outcomes. Each market is classified into one of four types based on the outcome's metadata, and carries pre-computed side coin identifiers ready for order placement.

`name`, `sides[].name`, and `questionName` are the names Hyperliquid sends, so a template market reads `"template:priceTouch"`. The readable names, rendered from Hyperliquid's template registry (`outcomeTemplates`), are in `parsedName`, `sides[].parsedName`, and `parsedQuestionName`: `"BTC touches 90000 by Nov 1, 00:00 UTC"`. A template question's fallback outcome has the `parsedName` `"Other"`, and markets that aren't templates repeat their names. If the registry can't be fetched, the parsed fields keep the wire names, except that the `template:` prefix is removed from plain names such as `"template:Yes"`. The parsed fields are available from 1.3.0.

Refreshing the market or event cache, and `initialize()`, make one `outcomeTemplates` request, cached for 30 seconds.

The return type changes depending on whether you pass `groupBy`:

| `groupBy` value | Return type |
| - | - |
| *(none)* | `HIP4Market[]` |
| `"type"` | `MarketsByType` |
| `"question"` | `MarketsByQuestion` |

```typescript theme={null}
import { createHIP4Adapter } from "@outcome.xyz/hip4";
import type { HIP4Market } from "@outcome.xyz/hip4";

const adapter = createHIP4Adapter({ testnet: false });
await adapter.initialize();

const markets = (await adapter.events.fetchMarkets()) as HIP4Market[];
```

Because the return type depends on `groupBy`, TypeScript types the result as a union. Cast it to the shape you asked for, as in the examples below.

### Parameters

<ParamField path="params.type" type="string">
  Filter to a single market type. One of `"defaultBinary"`, `"labelledBinary"`,
  `"multiOutcome"`, `"priceBucket"`.
</ParamField>

<ParamField path="params.sortBy" type="string">
  Sort order. `"newest"` puts the highest outcome ID first. `"expiry"` puts the
  soonest event time first, with markets that have none last. `"volume"` puts
  the highest 24h volume first and costs one extra request. When omitted, results
  stay in catalog order (the order of Hyperliquid's `outcomeMeta`). Takes effect
  from 1.3.0-beta.0; earlier versions ignore it.
</ParamField>

<ParamField path="params.groupBy" type="string">
  Group the results. `"type"` returns a `MarketsByType` object keyed by market
  type. `"question"` returns a `MarketsByQuestion` object keyed by question ID
  for `multiOutcome` markets. Every other market, including `priceBucket`
  markets, goes under the `"standalone"` key.
</ParamField>

<ParamField path="params.limit" type="number">
  Maximum number of markets to return (applied after filtering and sorting). When omitted,
  every matching market is returned. Ignored when `groupBy` is set.
</ParamField>

<ParamField path="params.offset" default="0" type="number">
  Pagination offset. Ignored when `groupBy` is set.
</ParamField>

### Market types

All four types extend a shared `BaseMarket` with these fields:

| Field | Type | Description |
| - | - | - |
| `type` | `MarketType` | Discriminant: `"defaultBinary"` \| `"labelledBinary"` \| `"multiOutcome"` \| `"priceBucket"` |
| `outcomeId` | `number` | Hyperliquid outcome ID |
| `name` | `string` | Name as Hyperliquid sends it (e.g. `"template:priceTouch"`) |
| `parsedName` | `string` | Readable name (e.g. `"BTC touches 90000 by Nov 1, 00:00 UTC"`). Available from 1.3.0 |
| `description` | `string` | Description text |
| `sides` | `[MarketSide, MarketSide]` | Both tradeable sides with pre-computed identifiers |
| `raw` | `HLOutcome` | Raw Hyperliquid API response for escape-hatch access |

Each `MarketSide` exposes:

| Field | Type | Description |
| - | - | - |
| `name` | `string` | Side name as Hyperliquid sends it (e.g. `"Yes"`, `"template:Yes"`) |
| `parsedName` | `string` | Readable side name (`"template:Yes"` reads `"Yes"`). Available from 1.3.0 |
| `coinNum` | `number` | `outcomeId * 10 + sideIndex` |
| `coin` | `string` | Coin string for API calls (e.g. `"#5160"`) |
| `asset` | `number` | Order asset ID (`100_000_000 + coinNum`) |

`multiOutcome` and `priceBucket` markets also carry `questionName`, the parent question's name as Hyperliquid sends it, and `parsedQuestionName`, its readable name (available from 1.3.0).

### Examples

```typescript theme={null}
import type {
  DefaultBinaryMarket,
  HIP4Market,
  LabelledBinaryMarket,
  MarketsByQuestion,
  MarketsByType,
  MultiOutcomeMarket,
  PriceBucketMarket,
} from "@outcome.xyz/hip4";

// --- Flat list of all markets ---
const all = (await adapter.events.fetchMarkets()) as HIP4Market[];

// --- defaultBinary: recurring price binary (Yes/No sides) ---
const recurring = (await adapter.events.fetchMarkets({
  type: "defaultBinary",
})) as DefaultBinaryMarket[];
const m = recurring[0];

console.log(m.underlying); // "BTC"
console.log(m.targetPrice); // 69070
console.log(m.expiry); // Date object (UTC)
console.log(m.period); // "1d"
console.log(m.sides[0].coin); // "#17580"  - coin string for side 0
console.log(m.sides[0].asset); // 100017580 - asset ID for order placement
console.log(m.sides[1].name); // "No"
console.log(m.raw); // original HLOutcome

// --- labelledBinary: standalone with custom side labels ---
const labelled = (await adapter.events.fetchMarkets({
  type: "labelledBinary",
})) as LabelledBinaryMarket[];
const lb = labelled[0];
console.log(lb.sides[0].parsedName); // e.g. "Hypurr"
console.log(lb.sides[1].parsedName); // e.g. "Usain Bolt"

// --- multiOutcome: one of several outcomes under a parent question ---
const multi = (await adapter.events.fetchMarkets({
  type: "multiOutcome",
})) as MultiOutcomeMarket[];
const mo = multi[0];
console.log(mo.questionId); // 5
console.log(mo.parsedQuestionName); // "Who wins?"
console.log(mo.isFallback); // false

// --- priceBucket: multi-bucket price range markets ---
const buckets = (await adapter.events.fetchMarkets({
  type: "priceBucket",
})) as PriceBucketMarket[];
const pb = buckets[0];
console.log(pb.underlying); // "BTC"
console.log(pb.priceThresholds); // [81015.3, 81258.7]
console.log(pb.lowerBound); // null (unbounded) or a price number
console.log(pb.upperBound); // null (unbounded) or a price number
console.log(pb.bucketIndex); // 0, 1, 2, ... or -1 for the fallback

// --- Group all markets by type ---
const byType = (await adapter.events.fetchMarkets({
  groupBy: "type",
})) as MarketsByType;
console.log(byType.defaultBinary?.length); // recurring market count
console.log(byType.labelledBinary?.length); // labelled binary count

// --- Group multi-outcome markets by parent question ---
const byQuestion = (await adapter.events.fetchMarkets({
  type: "multiOutcome",
  groupBy: "question",
})) as MarketsByQuestion;
// byQuestion["5"] holds all MultiOutcomeMarket objects under question 5

// --- Pagination ---
const page2 = await adapter.events.fetchMarkets({ limit: 10, offset: 10 });
```

<Note>
  When you use `groupBy`, `limit` and `offset` are ignored: the grouped object
  contains every market that matches `type`. Pagination applies only to the flat
  list.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.