# spotTwapSnapshot

{% hint style="info" %}
💧**New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for builder convenience.**
{% endhint %}

### Overview

spotTwapSnapshot endpoint allows you to access all active TWAP (Time-Weighted Average Price) orders on Hyperliquid spot markets. TWAPs are algorithmic orders that execute over time, splitting large orders into smaller pieces.

There are two ways to access the data:

1. **Metadata Endpoint (Fast)**: Check for updates without downloading data
2. **Snapshots Endpoint (Heavy)**: Download actual TWAP snapshot data

<details>

<summary>Efficient polling pattern</summary>

To minimize bandwidth and server load, you should:

1. Poll the metadata endpoint to check for updates
2. Only call the snapshots endpoint when data has changed
3. Cache the snapshot ID locally to avoid unnecessary downloads

</details>

<details>

<summary>Dependencies and best practices</summary>

**Dependencies**

**JavaScript**

* **zstd**: For decompression (e.g., `fzstd`, `@gera2ld/zstd`)
* **msgpack**: For decoding (e.g., `@msgpack/msgpack`)

**Python**

* **zstd**: For decompression (e.g., `zstandard`)
* **msgpack**: For decoding (e.g., `msgpack`)
* **aiohttp**: For async HTTP requests

***

</details>

<details>

<summary>Error handling</summary>

* **404**: No TWAP snapshots are available yet. Wait and retry.
* **500**: A server error occurred. Retry with exponential backoff.
* **Network errors**: Implement retry logic with jitter to avoid overwhelming the server.
* **Parse errors**: Log the issue and continue using cached data if available.

</details>

### Spot TWAP Snapshot Metadata (Fast)

* **Endpoint**: `POST /info`
* **Purpose**: Check if TWAP snapshots have been updated
* **Response Time**: \~1ms

#### Request

```json
{
  "type": "spotTwapSnapshotTimestamp"
}
```

#### Response

```json
{
  "snapshot_id": "20240915_state_123456789",
  "timestamp": 1694764800
}
```

### Spot TWAP Snapshots (Heavy)

* **Endpoint**: `POST /info`
* **Purpose**: Download TWAP snapshot data
* **Response Time**: \~50ms+

#### Request

```json
{
  "type": "spotTwapSnapshots",
  "tokens": ["HYPE", "PURR"]
}
```

## Query Options

**Specific tokens:**

* `["HYPE", "PURR"]` -> Specific spot tokens
* `["@107"]` -> By spot index
* `["HYPE/USDC"]` -> By pair name

**All tokens:**

* `["ALL"]` -> All spot tokens with active TWAPs

#### Response Headers

* **Single Token**: `x-payload-format: msgpack`, `Content-Encoding: zstd`
* **Multiple Tokens**: `x-payload-format: multi-zstd`, `x-compression: inner-zstd`

### Data Format

<details>

<summary>Single Token Response</summary>

**Format**: Raw zstd-compressed MessagePack data

**Decompressed Structure:**

{% code overflow="wrap" %}

```json
[
  "20240915_state_123456789",
  "HYPE",
  [
    ["0x1234567890abcdef...", 1, "HYPE", true, 1000.0, 500.0, 500.0, 5000.0, 50.0, 86400.0, 1694764800000, false, true, "2024-09-15T12:00:00Z", 100, "@107", "HYPE/USDC"],
    ["0xfedcba0987654321...", 2, "HYPE", false, 2000.0, 1000.0, 1000.0, 10000.0, 50.0, 43200.0, 1694764800000, true, false, "2024-09-15T13:00:00Z", 50, "@232", "HYPE/USDH"]
  ]
]
```

{% endcode %}

**Snapshot Array Format:**

```
[snapshot_id, token_name, twaps_array]
```

| Index | Field        | Description                       |
| ----- | ------------ | --------------------------------- |
| 0     | snapshot\_id | Snapshot identifier               |
| 1     | token\_name  | Token name (e.g., "HYPE", "PURR") |
| 2     | twaps        | Array of TWAP tuples              |

**TWAP Tuple Format:**

```
[address, twap_id, asset, is_buy, total_sz, executed_sz, remaining_sz, executed_ntl, progress_pct, duration_secs, start_time_ms, reduce_only, randomize, next_slice_time, slice_number, market_name, market_readable]
```

| Index | Field             | Description                                               |
| ----- | ----------------- | --------------------------------------------------------- |
| 0     | address           | User address (hex string)                                 |
| 1     | twap\_id          | TWAP ID (unique per user)                                 |
| 2     | asset             | Asset symbol (e.g., "HYPE", "PURR")                       |
| 3     | is\_buy           | Direction: true = buy, false = sell                       |
| 4     | total\_sz         | Total size to execute (normalized)                        |
| 5     | executed\_sz      | Size already executed (normalized)                        |
| 6     | remaining\_sz     | Remaining size (total\_sz - executed\_sz)                 |
| 7     | executed\_ntl     | Notional value executed (USD)                             |
| 8     | progress\_pct     | Progress percentage (0-100)                               |
| 9     | duration\_secs    | Total TWAP duration in seconds                            |
| 10    | start\_time\_ms   | TWAP creation timestamp (milliseconds since epoch)        |
| 11    | reduce\_only      | Whether TWAP is reduce-only (boolean)                     |
| 12    | randomize         | Whether execution timing is randomized (boolean)          |
| 13    | next\_slice\_time | Next slice execution time (ISO timestamp string)          |
| 14    | slice\_number     | Current slice number in execution                         |
| 15    | market\_name      | Spot market index (e.g., "@107", "@232")                  |
| 16    | market\_readable  | Human-readable pair name (e.g., "HYPE/USDC", "HYPE/USDH") |

</details>

<details>

<summary>Multiple Tokens Response</summary>

**Format**: Custom binary format with length prefixes

**Binary Structure:**

{% code overflow="wrap" %}

```
[token_count: 4 bytes][length1: 4 bytes][zstd_data1][length2: 4 bytes][zstd_data2]...
```

{% endcode %}

Each `zstd_data` blob, when decompressed, contains a single token's TWAP snapshot in the same array format as the single token response.

</details>

### Implementation Examples

**Note**: Make sure to set **HYDROMANCER\_API\_KEY** in your .env file

{% tabs %}
{% tab title="Python" %}

```python
import struct
import aiohttp
import asyncio
import zstandard as zstd
import msgpack
import os
from datetime import datetime

def parse_twap_snapshot(data: list) -> dict:
    """Parse a single TWAP snapshot from decompressed msgpack data."""
    # Format: [snapshot_id, token_name, twaps_array]
    # TWAP tuple: [address, twap_id, asset, is_buy, total_sz, executed_sz, remaining_sz, executed_ntl, progress_pct, duration_secs, start_time_ms, reduce_only, randomize, next_slice_time, slice_number, market_name, market_readable]
    snapshot_id, token_name, twaps_raw = data[0], data[1], data[2]

    twaps = []
    for t in twaps_raw:
        twaps.append({
            "address": t[0],
            "twap_id": t[1],
            "asset": t[2],
            "is_buy": t[3],
            "total_sz": t[4],
            "executed_sz": t[5],
            "remaining_sz": t[6],
            "executed_ntl": t[7],
            "progress_pct": t[8],
            "duration_secs": t[9],
            "start_time_ms": t[10],
            "reduce_only": t[11],
            "randomize": t[12],
            "next_slice_time": t[13],
            "slice_number": t[14],
            "market_name": t[15],      # e.g., "@232"
            "market_readable": t[16],  # e.g., "HYPE/USDH"
        })

    return {
        "snapshot_id": snapshot_id,
        "token": token_name,
        "twaps": twaps
    }

def parse_multi_zstd_response(data: bytes) -> list:
    """Parse multi-zstd response format."""
    offset = 0
    count = struct.unpack_from('<I', data, offset)[0]
    offset += 4

    dctx = zstd.ZstdDecompressor()
    tokens = []

    for _ in range(count):
        blob_len = struct.unpack_from('<I', data, offset)[0]
        offset += 4
        blob = data[offset:offset + blob_len]
        offset += blob_len

        decompressed = dctx.decompress(blob, max_output_size=blob_len * 20)
        snapshot_data = msgpack.unpackb(decompressed, raw=False)
        tokens.append(parse_twap_snapshot(snapshot_data))

    return tokens

async def fetch_spot_twap_snapshots(tokens=None):
    """Fetch spot TWAP snapshots from API."""
    api_key = os.environ.get('HYDROMANCER_API_KEY')
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {api_key}'
    }

    request = {'type': 'spotTwapSnapshots'}
    if tokens:
        request['tokens'] = tokens
    else:
        request['tokens'] = ['ALL']

    async with aiohttp.ClientSession() as session:
        async with session.post(
            'https://api.hydromancer.xyz/info',
            json=request,
            headers=headers
        ) as resp:
            payload_format = resp.headers.get('x-payload-format')
            data = await resp.read()

            if payload_format == 'multi-zstd':
                return parse_multi_zstd_response(data)
            else:
                # Single token response - aiohttp auto-decompresses based on Content-Encoding
                snapshot_data = msgpack.unpackb(data, raw=False)
                return [parse_twap_snapshot(snapshot_data)]

async def fetch_spot_twap_timestamp():
    """Fetch spot TWAP snapshot metadata."""
    api_key = os.environ.get('HYDROMANCER_API_KEY')
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {api_key}'
    }

    async with aiohttp.ClientSession() as session:
        async with session.post(
            'https://api.hydromancer.xyz/info',
            json={'type': 'spotTwapSnapshotTimestamp'},
            headers=headers
        ) as resp:
            return await resp.json()

if __name__ == "__main__":
    async def main():
        # Check timestamp first
        metadata = await fetch_spot_twap_timestamp()
        print(f"Snapshot ID: {metadata.get('snapshot_id')}")
        print(f"Timestamp: {metadata.get('timestamp')}")

        # Fetch all spot TWAP snapshots
        snapshots = await fetch_spot_twap_snapshots(['ALL'])

        for token_data in snapshots:
            print(f"\nToken: {token_data['token']}")
            for twap in token_data['twaps'][:3]:  # Show first 3 TWAPs
                direction = "BUY" if twap['is_buy'] else "SELL"
                duration_hrs = twap['duration_secs'] / 3600
                start_time = datetime.fromtimestamp(twap['start_time_ms'] / 1000).isoformat()
                print(f"  {twap['address']} | {direction} {twap['market_readable']} ({twap['market_name']}) | "
                      f"Progress: {twap['progress_pct']:.1f}% (slice {twap['slice_number']}) | "
                      f"Remaining: {twap['remaining_sz']:.4f} | "
                      f"Duration: {duration_hrs:.1f}h | Started: {start_time}")

    asyncio.run(main())
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const axios = require('axios');
const { decompress } = require('@mongodb-js/zstd');
const msgpack = require('msgpack-lite');
require('dotenv').config();

function parseTwapSnapshot(data) {
    // Format: [snapshot_id, token_name, twaps_array]
    // TWAP tuple: [address, twap_id, asset, is_buy, total_sz, executed_sz, remaining_sz, executed_ntl, progress_pct, duration_secs, start_time_ms, reduce_only, randomize, next_slice_time, slice_number, market_name, market_readable]
    const [snapshotId, tokenName, twapsRaw] = data;

    const twaps = twapsRaw.map(t => ({
        address: t[0],
        twap_id: t[1],
        asset: t[2],
        is_buy: t[3],
        total_sz: t[4],
        executed_sz: t[5],
        remaining_sz: t[6],
        executed_ntl: t[7],
        progress_pct: t[8],
        duration_secs: t[9],
        start_time_ms: t[10],
        reduce_only: t[11],
        randomize: t[12],
        next_slice_time: t[13],
        slice_number: t[14],
        market_name: t[15],      // e.g., "@232"
        market_readable: t[16]   // e.g., "HYPE/USDH"
    }));

    return {
        snapshot_id: snapshotId,
        token: tokenName,
        twaps: twaps
    };
}

async function parseMultiZstdResponse(buffer) {
    let offset = 0;
    const count = buffer.readUInt32LE(offset);
    offset += 4;

    const tokens = [];

    for (let i = 0; i < count; i++) {
        const blobLen = buffer.readUInt32LE(offset);
        offset += 4;
        const blob = buffer.slice(offset, offset + blobLen);
        offset += blobLen;

        const decompressed = await decompress(blob);
        const snapshotData = msgpack.decode(decompressed);
        tokens.push(parseTwapSnapshot(snapshotData));
    }

    return tokens;
}

async function fetchSpotTwapSnapshots(tokens = ['ALL']) {
    const apiKey = process.env.HYDROMANCER_API_KEY;
    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
    };

    const response = await axios.post(
        'https://api.hydromancer.xyz/info',
        { type: 'spotTwapSnapshots', tokens: tokens },
        { headers, responseType: 'arraybuffer' }
    );

    const payloadFormat = response.headers['x-payload-format'];
    const buffer = Buffer.from(response.data);

    if (payloadFormat === 'multi-zstd') {
        return await parseMultiZstdResponse(buffer);
    } else {
        // Single token response - HTTP client auto-decompresses based on Content-Encoding
        const snapshotData = msgpack.decode(buffer);
        return [parseTwapSnapshot(snapshotData)];
    }
}

async function fetchSpotTwapTimestamp() {
    const apiKey = process.env.HYDROMANCER_API_KEY;
    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
    };

    const response = await axios.post(
        'https://api.hydromancer.xyz/info',
        { type: 'spotTwapSnapshotTimestamp' },
        { headers }
    );

    return response.data;
}

async function main() {
    // Check timestamp first
    const metadata = await fetchSpotTwapTimestamp();
    console.log(`Snapshot ID: ${metadata.snapshot_id}`);
    console.log(`Timestamp: ${metadata.timestamp}`);

    // Fetch all spot TWAP snapshots
    const snapshots = await fetchSpotTwapSnapshots(['ALL']);

    for (const tokenData of snapshots) {
        console.log(`\nToken: ${tokenData.token}`);
        for (const twap of tokenData.twaps.slice(0, 3)) {  // Show first 3 TWAPs
            const direction = twap.is_buy ? 'BUY' : 'SELL';
            const durationHrs = (twap.duration_secs / 3600).toFixed(1);
            const startTime = new Date(twap.start_time_ms).toISOString();
            console.log(`  ${twap.address.slice(0, 10)}... | ${direction} ${twap.market_readable} (${twap.market_name}) | ` +
                `Progress: ${twap.progress_pct.toFixed(1)}% (slice ${twap.slice_number}) | ` +
                `Remaining: ${twap.remaining_sz.toFixed(4)} | ` +
                `Duration: ${durationHrs}h | Started: ${startTime}`);
        }
    }
}

if (require.main === module) {
    main().catch(console.error);
}

module.exports = { fetchSpotTwapSnapshots, fetchSpotTwapTimestamp };
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hydromancer.xyz/readme/rest-api/market-data/spottwapsnapshot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
