> 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/market-data/spottwapsnapshot.md).

# 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 %}
