> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/rest-api/outcomes/registeredoutcomessnapshot.md).

# registeredOutcomesSnapshot

Per-side balance snapshots of every active prediction market outcome (zstd+msgpack).

{% hint style="warning" %}

### ⚠️ This is an add-on endpoint - access has to be purchased separately.

{% endhint %}

{% hint style="info" %}
**New: entry values (opt-in).** Pass `"includeEntryValues": true` to receive each balance entry as a `[size, entry_value]` 2-element array instead of a bare size string. The default response shape is unchanged — existing consumers are unaffected. See [Data Format](#data-format) below for both shapes.
{% endhint %}

Returns compressed snapshots for active HIP-4 outcomes — one snapshot per outcome side (YES/NO). Each snapshot lists every holder of that side, their balance, and their cumulative on-chain entry value.

There are two endpoints:

1. **Metadata Endpoint (Fast)** — check whether new snapshots are available.
2. **Snapshot Endpoint (Heavy)** — download compressed snapshot data.

***

## registeredOutcomesSnapshotTimestamp (Fast)

* **Endpoint:** `POST /info`
* **Purpose:** check whether new snapshots have been written
* **Response time:** \~1 ms

### Request

<table><thead><tr><th width="161">Field</th><th width="119">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>string</td><td>Must be <code>"registeredOutcomesSnapshotTimestamp"</code></td></tr></tbody></table>

```bash
curl -X POST https://api.hydromancer.xyz/info \
  -H "Authorization: Bearer $HYDROMANCER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "registeredOutcomesSnapshotTimestamp" }'
```

### Response

```json
{
  "snapshot_id": "20260428_state_123456789",
  "timestamp": 1745846400
}
```

***

## registeredOutcomesSnapshot (Heavy)

* **Endpoint:** `POST /info`
* **Purpose:** download per-side outcome snapshots
* **Response time:** \~100 ms+

### Request

<table><thead><tr><th width="161">Field</th><th width="170">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>string</td><td>Must be <code>"registeredOutcomesSnapshot"</code></td></tr><tr><td><code>outcome_ids</code></td><td>array of integers</td><td>Outcome ids to fetch (optional). Each id expands to two asset_ids: YES = <code>#{id*10}</code>, NO = <code>#{id*10+1}</code>.</td></tr><tr><td><code>asset_ids</code></td><td>array of strings</td><td>Asset ids in <code>#NNNN</code> format (optional). Used as-is.</td></tr><tr><td><code>question_ids</code></td><td>array of integers</td><td>HIP-4 question ids (optional). Each is server-side-expanded to its child outcomes (named + fallback), then each child to its YES + NO asset_ids.</td></tr><tr><td><code>includeEntryValues</code></td><td>boolean</td><td>Optional, default <code>false</code>. When <code>true</code>, each balances entry is a <code>[size, entry_value]</code> 2-element array instead of a bare size string.</td></tr></tbody></table>

If `outcome_ids`, `asset_ids`, and `question_ids` are all omitted, the response contains every active outcome side.

After deduplication, the combined `outcome_ids` (expanded) + `asset_ids` + `question_ids` (expanded to children, then to asset\_ids) list must not exceed **50 entries**; larger requests are rejected with `400 Bad Request`.

```bash
# Specific outcomes (YES + NO)
curl -X POST https://api.hydromancer.xyz/info \
  -H "Authorization: Bearer $HYDROMANCER_API_KEY" \
  -H "Content-Type: application/json" \
  --output outcomes.bin \
  -d '{ "type": "registeredOutcomesSnapshot", "outcome_ids": [3, 7] }'

# All active outcomes
curl -X POST https://api.hydromancer.xyz/info \
  -H "Authorization: Bearer $HYDROMANCER_API_KEY" \
  -H "Content-Type: application/json" \
  --output outcomes.bin \
  -d '{ "type": "registeredOutcomesSnapshot" }'
```

### Response Headers

* **Single asset\_id:** `x-payload-format: msgpack`, `Content-Encoding: zstd`
* **Multiple asset\_ids:** `x-payload-format: multi-zstd`, `x-compression: inner-zstd`

### Data Format

<details>

<summary>Single Snapshot Response</summary>

**Format:** raw zstd-compressed MessagePack.

**Decompressed structure** — a positional 4-element array. rmp-serde encodes Rust structs as msgpack arrays by default, so the field order matters, not the names:

Default (`includeEntryValues` omitted or `false`) — balances are bare size strings:

```json
[
  "20260428_state_123456789",            // [0] snapshot_id
  3,                                      // [1] outcome_id
  "#30",                                  // [2] asset_id
  {                                       // [3] balances — address → size
    "0xa11ce...000a": "100",
    "0xb0b...000b": "5"
  }
]
```

With `"includeEntryValues": true` — each balances entry is a `[size, entry_value]` 2-element array:

```json
[
  "20260428_state_123456789",            // [0] snapshot_id
  3,                                      // [1] outcome_id
  "#30",                                  // [2] asset_id
  {                                       // [3] balances — address → [size, entry_value]
    "0xa11ce...000a": ["100", "30.50"],
    "0xb0b...000b": ["5", "1.25"]
  }
]
```

> **Note:** both strings in either shape:
>
> * **`size`** is formatted via HL's size-decimals rules. Outcome side tokens currently ship with `szDecimals=0` and `weiDecimals=5` on-chain; the producer reads both per-token from spot meta and uses them to descale and format. With `szDecimals=0`, sizes are rounded to whole lots and rendered as integer strings — no decimal point, no trailing zeros (`"100"`, not `"100.00"`). Sub-lot dust uses banker's rounding (round-half-to-even). Parse with `int(s)`.
> * **`entry_value`** (only with `includeEntryValues: true`) is the holder's cumulative on-chain entry value in the outcome's quote token, formatted as a plain decimal string. `"0"` when the holding has no recorded entry (e.g. tokens acquired outside a fill, such as airdrops). Parse with `Decimal(s)` / `decimal.Decimal` / equivalent — don't use `float()` for accounting workflows.

> **Note:** the HL escrow precompile address for each token is excluded from the snapshot. Every spot token has a deterministic escrow at `0x32{:038x}` of its `token_id` (e.g. `0x32000000000000000000000000000000000001ca` for token 458) that holds the unsold/issuer supply — it isn't a real user position. Thus, it is dropped from each side's balances map.

</details>

<details>

<summary>Multiple Snapshots Response</summary>

**Format:** custom binary framing with per-snapshot zstd payloads.

**Binary structure:**

```
[count: 4 bytes LE][len1: 4 bytes LE][zstd_blob1][len2: 4 bytes LE][zstd_blob2]...
```

Each `zstd_blob` decompresses to one positional 4-element msgpack array with the `[snapshot_id, outcome_id, asset_id, balances]` shape shown above. Snapshots are returned in `(outcome_id, side_index)` order.

</details>

### Implementation Example

```python
import requests, struct, zstandard as zstd, msgpack, os
from decimal import Decimal

response = requests.post(
    "https://api.hydromancer.xyz/info",
    json={
        "type": "registeredOutcomesSnapshot",
        "outcome_ids": [3, 7],
        "includeEntryValues": True,  # omit for the legacy bare-size balances
    },
    headers={
        "Authorization": f"Bearer {os.environ['HYDROMANCER_API_KEY']}",
        "Content-Type": "application/json",
    },
)
body = response.content
fmt = response.headers.get("x-payload-format")

if fmt == "msgpack":
    snapshot = msgpack.unpackb(zstd.ZstdDecompressor().decompress(body), raw=False)
    print(snapshot)
else:
    # multi-zstd: count, then [len][blob] pairs
    count = struct.unpack("<I", body[:4])[0]
    offset = 4
    snapshots = []
    for _ in range(count):
        length = struct.unpack("<I", body[offset:offset + 4])[0]
        offset += 4
        blob = body[offset:offset + length]
        offset += length
        snapshots.append(
            msgpack.unpackb(zstd.ZstdDecompressor().decompress(blob), raw=False)
        )
    for snap in snapshots:
        # snap is a 4-element list: [snapshot_id, outcome_id, asset_id, balances_map].
        # With includeEntryValues, each balances value is a [size_str, entry_value_str]
        # 2-element array; without it, a bare size_str.
        # size uses sz_decimals=0 → integer lots; entry_value is a decimal string
        # in the outcome's quote token (parse with Decimal for accounting math).
        asset_id = snap[2]
        holders = {
            addr: {"size": int(pair[0]), "entry_value": Decimal(pair[1])}
            for addr, pair in snap[3].items()
        }
        print(asset_id, len(holders), "holders")
```

### Error Handling

* **400** — `asset_id` missing the `#` prefix, or the request exceeds 50 deduped asset\_ids.
* **500** — no matching snapshots in Redis, or the producer hasn't written any yet. Retry after polling `registeredOutcomesSnapshotTimestamp`.
