# perpTwapSnapshot

{% 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

perpTwapSnapshot endpoint allows you to access all active TWAP (Time-Weighted Average Price) orders on Hyperliquid perpetual 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>

### Perp TWAP Snapshot Metadata (Fast)

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

#### Request

```json
{
  "type": "perpTwapSnapshotTimestamp"
}
```

#### Response

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

### Perp TWAP Snapshots (Heavy)

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

#### Request

```json
{
  "type": "perpTwapSnapshots",
  "market_names": ["BTC", "ETH"]
}
```

## Query Options (HIP-3 Multi-Dex)

**Specific markets:**

* `["BTC", "ETH"]` -> Defaults to the main dex
* `["xyz:BTC", "vntl:ETH"]` -> Specify DEX with `dex:market` format

**All markets patterns:**

* `["ALL"]` -> All TWAP markets from the main dex
* `["ALL:xyz"]` -> All TWAP markets from xyz DEX
* `["ALL:ALL_DEXES"]` -> All TWAP markets from all available DEXes

**Mixed queries:**

* `["ALL", "ALL:xyz", "BTC"]` -> All from main dex + all from xyz
* `["ALL:xyz", "vntl:ETH"]` -> All from xyz + specific ETH from vntls

**Rules:**

1. `ALL:ALL_DEXES` takes precedence - if present, returns everything from all dexes
2. Multiple `ALL` patterns allowed - one per dex
3. Specific markets are filtered - if a dex is covered by an ALL pattern, specific markets from that dex are ignored
4. No duplicates - each dex's ALL pattern is deduplicated

#### Response Headers

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

### Data Format

<details>

<summary>Single Market Response</summary>

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

**Decompressed Structure:**

{% code overflow="wrap" %}

```json
[
  "20240915_state_123456789",
  "BTC",
  [
    ["0x1234567890abcdef...", 1, "BTC", true, 1.5, 0.75, 0.75, 75000.0, 50.0, 86400.0, 1694764800000, false, true, "2024-09-15T12:00:00Z", 100],
    ["0xfedcba0987654321...", 2, "BTC", false, 2.0, 1.0, 1.0, 100000.0, 50.0, 43200.0, 1694764800000, true, false, "2024-09-15T13:00:00Z", 50]
  ]
]
```

{% endcode %}

**Snapshot Array Format:**

```
[snapshot_id, market_name, twaps_array]
```

| Index | Field        | Description                                                 |
| ----- | ------------ | ----------------------------------------------------------- |
| 0     | snapshot\_id | Snapshot identifier                                         |
| 1     | market\_name | Market name ("BTC" for main dex, "xyz:BTC" for HIP-3 dexes) |
| 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]
```

| Index | Field             | Description                                        |
| ----- | ----------------- | -------------------------------------------------- |
| 0     | address           | User address (hex string)                          |
| 1     | twap\_id          | TWAP ID (unique per user)                          |
| 2     | asset             | Asset symbol (e.g., "BTC", "ETH")                  |
| 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                  |

</details>

<details>

<summary>Multiple Markets Response</summary>

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

**Binary Structure:**

{% code overflow="wrap" %}

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

{% endcode %}

Each `zstd_data` blob, when decompressed, contains a single market's TWAP snapshot in the same array format as the single market 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, market_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]
    snapshot_id, market_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]
        })

    return {
        "snapshot_id": snapshot_id,
        "market": market_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()
    markets = []

    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)
        markets.append(parse_twap_snapshot(snapshot_data))

    return markets

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

    request = {'type': 'perpTwapSnapshots'}
    if market_names:
        request['market_names'] = market_names
    else:
        request['market_names'] = ['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 market response - aiohttp auto-decompresses based on Content-Encoding
                snapshot_data = msgpack.unpackb(data, raw=False)
                return [parse_twap_snapshot(snapshot_data)]

async def fetch_perp_twap_timestamp():
    """Fetch perp 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': 'perpTwapSnapshotTimestamp'},
            headers=headers
        ) as resp:
            return await resp.json()

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

        # Fetch all TWAP snapshots
        snapshots = await fetch_perp_twap_snapshots(['ALL:ALL_DEXES'])

        for market in snapshots:
            print(f"\nMarket: {market['market']}")
            for twap in market['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'][:10]}... | {direction} {twap['asset']} | "
                      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, market_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]
    const [snapshotId, marketName, 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]
    }));

    return {
        snapshot_id: snapshotId,
        market: marketName,
        twaps: twaps
    };
}

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

    const markets = [];

    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);
        markets.push(parseTwapSnapshot(snapshotData));
    }

    return markets;
}

async function fetchPerpTwapSnapshots(marketNames = ['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: 'perpTwapSnapshots', market_names: marketNames },
        { 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 market response - HTTP client auto-decompresses based on Content-Encoding
        const snapshotData = msgpack.decode(buffer);
        return [parseTwapSnapshot(snapshotData)];
    }
}

async function fetchPerpTwapTimestamp() {
    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: 'perpTwapSnapshotTimestamp' },
        { headers }
    );

    return response.data;
}

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

    // Fetch all TWAP snapshots
    const snapshots = await fetchPerpTwapSnapshots(['ALL:ALL_DEXES']);

    for (const market of snapshots) {
        console.log(`\nMarket: ${market.market}`);
        for (const twap of market.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.asset} | ` +
                `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 = { fetchPerpTwapSnapshots, fetchPerpTwapTimestamp };
```

{% 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/perptwapsnapshot.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.
