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

# Quickstart

> Get the price stream flowing - connect, decode, and read live depth in minutes.

TetraFi's price feed pushes live indicative pricing over WebSocket, with depth snapshots wire-encoded as protobuf to keep messages small and latency low. By the end of this page you'll have a client connected, decoding, and printing live market state.

<Info>
  **Outcome:** a working WebSocket consumer decoding live depth snapshots.

  **Time:** 10-15 minutes.

  **Before you start:** Python 3.10+, [uv](https://docs.astral.sh/uv/) or pip, and a TetraFi API key.
</Info>

## 1. Generate the Schema Bindings

Depth snapshots travel as Protocol Buffers, so the first step is turning the schema into a Python module you can decode with.

### The Wire Schema

Create `tetrafi.proto`:

```protobuf theme={null} theme={null}
syntax = "proto3";
package tetrafi;

message PriceUpdate {
  optional bytes  base            = 1;
  optional bytes  quote           = 2;
  optional uint64 last_update_ts  = 3;
  repeated float  bids            = 4 [packed = true];
  repeated float  asks            = 5 [packed = true];
}

message TetraFiPricingUpdate {
  repeated PriceUpdate pairs = 1;
}
```

What each `PriceUpdate` carries:

| Field            | Meaning                                                           |
| ---------------- | ----------------------------------------------------------------- |
| `base`           | The pair's base-side contract address, as raw bytes               |
| `quote`          | The pair's quote-side contract address, as raw bytes              |
| `last_update_ts` | Millisecond timestamp of the most recent refresh                  |
| `bids`           | Interleaved floats - price then size, repeating, best level first |
| `asks`           | Same interleaved layout on the ask side                           |

### Compile to Python

Pull in the protobuf toolchain and compile:

<CodeGroup>
  ```bash uv theme={null} theme={null}
  uv pip install grpcio-tools protobuf

  python -m grpc_tools.protoc \
    --proto_path=. \
    --python_out=. \
    tetrafi.proto
  ```

  ```bash pip theme={null} theme={null}
  pip install grpcio-tools protobuf

  python -m grpc_tools.protoc \
    --proto_path=. \
    --python_out=. \
    tetrafi.proto
  ```
</CodeGroup>

The compiler emits `tetrafi_pb2.py`; importing it gives you the decoder classes used below.

## 2. Open the Stream

All streaming runs over TetraFi's WebSocket endpoint:

```
wss://api.tetrafi.io/api/v1/ws
```

### What Goes on the URL

| Parameter | Required       | Description                                                                                                                                                                                                   |
| --------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`   | Recommended    | Your API key (or JWT) - passed as a query parameter, since browsers cannot set custom WebSocket headers. Price streams accept anonymous connections; authenticated connections see workspace-scoped liquidity |
| `format`  | For depth mode | Set to `protobuf` to receive full depth snapshots                                                                                                                                                             |
| `network` | For depth mode | The network whose aggregated book you want to snapshot                                                                                                                                                        |

<Note>
  Topic-based JSON subscriptions (`prices:{srcChain}:{input}:{dstChain}:{output}:{tier}` - see the [Reference](/price-api/reference)) are always on. The protobuf depth-snapshot mode below ships per environment - it's a deployment switch, so confirm with your TetraFi contact that it's enabled where you're integrating before wiring up a decoder. The `network` parameter accepts a chain id or an active chain name and selects the source side of each corridor: same-chain pairs and cross-chain corridors originating on that network both stream.
</Note>

### First Connection

```python theme={null} theme={null}
import asyncio

import websockets

from tetrafi_pb2 import TetraFiPricingUpdate  # type: ignore

NETWORK = "optimism"
API_KEY = "tfk_live_..."

WSS_URL = (
    f"wss://api.tetrafi.io/api/v1/ws"
    f"?token={API_KEY}"
    f"&format=protobuf"
    f"&network={NETWORK}"
)

async def main():
    async with websockets.connect(
        WSS_URL,
        ping_interval=20,
        ping_timeout=10,
        max_size=2**21,
    ) as ws:
        print(f"Stream open on {NETWORK}")

        async for blob in ws:
            update = TetraFiPricingUpdate()
            update.ParseFromString(blob)
            print(f"Snapshot covers {len(update.pairs)} pairs")
asyncio.run(main())
```

## 3. Turn Bytes into Prices

A snapshot spans many pairs at once; picking out yours is a matter of matching the `base` and `quote` address bytes.

### Look Up Asset Addresses

(You can skip this if you already track addresses.) Resolve symbols through the tradable-pair list:

```python theme={null} theme={null}
def fetch_pair_assets(chain_id: int) -> dict:
    resp = httpx.get(
        "https://api.tetrafi.io/api/v1/pairs",
        headers={"X-API-Key": API_KEY},
        timeout=10.0,
    )
    resp.raise_for_status()
    assets = {}
    for p in resp.json()["pairs"]:
        for side in (p["input"], p["output"]):
            if side["chainId"] == chain_id:
                assets[side["symbol"]] = side
    return assets

assets = fetch_pair_assets(10)  # Optimism

# Example: WETH/USDC
base_addr = assets["WETH"]["address"].lower()
quote_addr = assets["USDC"]["address"].lower()
```

### Unpack the Depth Arrays

`bids` and `asks` arrive flattened - price, size, price, size - so re-pair them before use:

```python theme={null} theme={null}
def address_to_hex(b: bytes) -> str:
    """Render the protobuf address bytes as a 0x-hex string."""
    return "0x" + b.hex()

def to_levels(flat: list[float]) -> list[tuple[float, float]]:
    """Re-pair the flattened price/size floats into (price, size) levels."""
    it = iter(flat)
    return list(zip(it, it, strict=True))

async def stream_prices(pair_base: str, pair_quote: str):
    async with websockets.connect(
        WSS_URL,
        ping_interval=20,
        ping_timeout=10,
        max_size=2**21,
    ) as ws:
        async for blob in ws:
            update = TetraFiPricingUpdate()
            update.ParseFromString(blob)

            for pair in update.pairs:
                base_hex = address_to_hex(pair.base)
                quote_hex = address_to_hex(pair.quote)

                if base_hex.lower() != pair_base or quote_hex.lower() != pair_quote:
                    continue

                bids = to_levels(list(pair.bids))
                asks = to_levels(list(pair.asks))

                if not bids or not asks:
                    continue

                best_bid = bids[0][0]
                best_ask = asks[0][0]
                mid_price = (best_bid + best_ask) / 2
                spread_bps = (best_ask - best_bid) / mid_price * 10_000

                print(
                    f"WETH/USDC  mid: {mid_price:.2f}  "
                    f"spread: {spread_bps:.1f} bps  "
                    f"bid levels: {len(bids)}  "
                    f"ask levels: {len(asks)}"
                )

asyncio.run(stream_prices(base_addr, quote_addr))
```

### How to Read a Level

Both sides arrive pre-sorted from your perspective: the top bid is the highest, the top ask the lowest, and every level pairs a price with the base-token size available there.

So `bids = [2411.20, 2.4, 2410.55, 5.1]` decodes to:

| Level | Price   | Size     |
| ----- | ------- | -------- |
| 1     | 2411.20 | 2.4 WETH |
| 2     | 2410.55 | 5.1 WETH |

## 4. End to End

Everything wired together - resolve the pair, open the stream, print live pricing:

```python theme={null} theme={null}
import asyncio

import httpx
import websockets

from tetrafi_pb2 import TetraFiPricingUpdate  # type: ignore

NETWORK = "optimism"
CHAIN_ID = 10
API_KEY = "tfk_live_..."

WSS_URL = (
    f"wss://api.tetrafi.io/api/v1/ws"
    f"?token={API_KEY}"
    f"&format=protobuf"
    f"&network={NETWORK}"
)

PAIR = "WETH/USDC"

def address_to_hex(b: bytes) -> str:
    return "0x" + b.hex()

def to_levels(flat: list[float]) -> list[tuple[float, float]]:
    it = iter(flat)
    return list(zip(it, it, strict=True))

# Symbol -> address resolution via the tradable-pair list
resp = httpx.get("https://api.tetrafi.io/api/v1/pairs",
                 headers={"X-API-Key": API_KEY}, timeout=10.0)
assets = {}
for p in resp.json()["pairs"]:
    for side in (p["input"], p["output"]):
        if side["chainId"] == CHAIN_ID:
            assets[side["symbol"]] = side
base_symbol, quote_symbol = PAIR.split("/")
base_addr = assets[base_symbol]["address"].lower()
quote_addr = assets[quote_symbol]["address"].lower()

# Open the stream and print live pricing
async def main():
    async with websockets.connect(
        WSS_URL,
        ping_interval=20,
        ping_timeout=10,
        max_size=2**21,
    ) as ws:
        print(f"Stream open - {PAIR} on {NETWORK}\n")

        async for blob in ws:
            update = TetraFiPricingUpdate()
            update.ParseFromString(blob)

            for pair in update.pairs:
                if (
                    address_to_hex(pair.base).lower() != base_addr
                    or address_to_hex(pair.quote).lower() != quote_addr
                ):
                    continue

                bids = to_levels(list(pair.bids))
                asks = to_levels(list(pair.asks))

                if not bids or not asks:
                    continue

                best_bid = bids[0][0]
                best_ask = asks[0][0]
                mid = (best_bid + best_ask) / 2
                spread = (best_ask - best_bid) / mid * 10_000

                print(
                    f"{PAIR}  mid: {mid:.2f}  "
                    f"spread: {spread:.1f} bps  "
                    f"best bid: {best_bid:.2f}  "
                    f"best ask: {best_ask:.2f}  "
                    f"levels: {len(bids)}b / {len(asks)}a"
                )
asyncio.run(main())
```

<Note>
  **Nothing on this stream is executable.** Streamed depth is for monitoring, sizing, and pre-trade analysis - when you're ready to trade, the [RFQ API](/rfq-api/introduction) turns intent into a firm commitment.
</Note>

## Keep Going

<CardGroup cols={2}>
  <Card title="RFQ API Quickstart" icon="bolt" href="/rfq-api/quickstart">
    Turn what you're streaming into settled trades.
  </Card>

  <Card title="Price Feed Reference" icon="book" href="/price-api/reference">
    Topic grammar, stream families, and expiry behavior.
  </Card>
</CardGroup>
