Python SDK for GRVT Exchange — a ZK-powered perpetuals DEX.
Covers the full integration surface: cookie-based session auth, EIP-712 order signing, synchronous and async REST clients, and a reconnecting WebSocket client with per-channel typed dispatch and sequence gap detection.
import asyncio
from grvt_sdk import GRVTClient, GRVTEnv, Orderbook
async def main() -> None:
async with GRVTClient(api_key="...", env=GRVTEnv.TESTNET) as client:
# REST – fetch current orderbook
book = await client.rest.get_orderbook("BTC_USDT_Perp")
print(book.bids[0])
# WebSocket – stream live orderbook updates
async def on_book(b: Orderbook) -> None:
print(b.bids[0].price)
await client.ws.subscribe("orderbook.BTC_USDT_Perp", on_book, msg_type=Orderbook)
await client.ws.run_forever()
asyncio.run(main())pip install -e ".[dev]" # from sourceRequires Python 3.10+ and the following dependencies (installed automatically):
aiohttp, eth-account, pydantic>=2.5, requests, websockets.
| Area | What the SDK does |
|---|---|
| Auth | POST /auth/api_key/login → session cookie. Proactive refresh 5 min before expiry. asyncio.Lock prevents concurrent re-auth races. |
| EIP-712 signing | Fixed-point integer encoding for prices and sizes — avoids float precision bugs that would fail on-chain signature verification. |
| REST (sync) | GRVTRestClient — order CRUD, account summary, orderbook, trades, instruments. Exponential backoff on 429 / 5xx. |
| REST (async) | AsyncGRVTRestClient — aiohttp-based, same event loop as the WS client. |
| WebSocket | Reconnect with exponential backoff. Per-channel typed dispatch. Sequence number gap detection with on_gap callback. |
| Façade | GRVTClient — single object owning REST + WS on a shared auth instance. |
| Types | Pydantic v2 models with field-level validation on all inputs (hex hashes, decimal strings, int64 bounds, uint32 limits). |
src/grvt_sdk/
├── client.py # GRVTClient – unified façade
├── auth.py # GRVTAuth – cookie management, sync + async
├── signing.py # sign_order, recover_signer – EIP-712
├── rest.py # GRVTRestClient, AsyncGRVTRestClient
├── ws.py # GRVTWebSocketClient – reconnect, typed dispatch
└── types.py # Pydantic v2 models for the full API schema
examples/
├── quickstart.py # end-to-end: auth → sign → submit → subscribe
├── market_maker.py # two-sided quoting, SeqNonce, position limits, graceful shutdown
└── latency.py # REST RTT + fill-to-confirm latency benchmark
tests/
├── test_signing.py # 15 EIP-712 unit tests (offline)
├── test_types.py # 36 Pydantic model validation tests (offline)
├── test_ws.py # 22 WebSocket dispatch tests (offline)
└── test_client.py # 10 façade tests (offline)
from grvt_sdk import GRVTAuth, GRVTEnv, Order, OrderLeg, OrderMetadata, TimeInForce, sign_order
import time
auth = GRVTAuth(api_key="...", env=GRVTEnv.TESTNET)
order = Order(
sub_account_id=12345,
time_in_force=TimeInForce.GOOD_TILL_TIME,
expiration=int(time.time_ns()) + 60 * 10 ** 9, # 60 s from now
legs=[OrderLeg(
instrument_hash="0x...", # keccak256 of instrument name
size="0.01",
limit_price="50000.0",
is_buying_asset=True,
)],
metadata=OrderMetadata(client_order_id=1, create_time=time.time_ns()),
)
sign_order(order, private_key="0x...", chain_id=GRVTEnv.TESTNET.chain_id)
# order.signature is now set — ready to submitA runnable two-sided quoting loop that demonstrates the full integration surface:
subscribes to the orderbook WS stream, places a bid and ask around mid with a
configurable spread, re-quotes on fills, enforces position limits with reduce_only,
and cancels all open orders on Ctrl-C.
export GRVT_API_KEY="your_api_key"
export GRVT_PRIVATE_KEY="0x..."
export GRVT_SUB_ACCOUNT_ID="12345"
python examples/market_maker.pyMeasures the two latency dimensions that matter most to market makers:
| Benchmark | What it measures |
|---|---|
| REST round-trip | create_order() call → HTTP response |
| Fill-to-confirm | Order submit → fill event arrives on private WS stream |
Reports P50 / P95 / P99 / max / mean in milliseconds across N samples. High REST RTT points to network or exchange processing latency; high fill-to-confirm points to the WS pipeline.
export GRVT_API_KEY="your_api_key"
export GRVT_PRIVATE_KEY="0x..."
export GRVT_SUB_ACCOUNT_ID="12345"
export GRVT_INSTRUMENT_HASH="0x..." # keccak256 of instrument name
# REST round-trip benchmark (20 samples, far-from-market price — orders won't fill)
python examples/latency.py
# Also run fill-to-confirm benchmark (set an at-market price)
export GRVT_LIMIT_PRICE="95000.0"
export GRVT_N_SAMPLES="50"
python examples/latency.pyExample output:
GRVT Latency Benchmark
env=testnet instrument=BTC_USDT_Perp samples=20
Running REST round-trip benchmark…
REST round-trip (submit → HTTP response) (20 samples)
P50 : 42.3 ms
P95 : 61.8 ms
P99 : 68.2 ms
max : 68.2 ms
mean : 44.1 ms
# All 83 offline unit tests — no credentials required
pytest tests/ -v
# End-to-end demo against testnet
export GRVT_API_KEY="..."
export GRVT_PRIVATE_KEY="0x..."
export GRVT_SUB_ACCOUNT_ID="12345"
python examples/quickstart.py| Service | URL |
|---|---|
| Auth / Edge | https://edge.testnet.grvt.io |
| Trading REST | https://trades.testnet.grvt.io |
| Market Data REST | https://market-data.testnet.grvt.io |
| Trading WS | wss://trades.testnet.grvt.io/ws |
| Market Data WS | wss://market-data.testnet.grvt.io/ws |
| Chain ID | 326 |
The official grvt-pysdk is a solid
reference implementation. This SDK was built by studying it closely and addressing the gaps
that matter most in a production trading context.
GRVT's builder-examples covers
the basic integration pattern — API key auth and single-leg order submission. The examples
here (market_maker.py, latency.py) pick up where that leaves off: two-sided quoting with
position limits, SeqNonce for high-frequency submissions, graceful reconnect, and latency
measurement — the production concerns that surface once the basic integration is working.
| grvt-pysdk | this SDK | |
|---|---|---|
| Cookie name | Hardcoded "gravity" — breaks when server sends exchange_token (issue #97) |
Reads whichever cookie name the server returns |
| Async re-auth race | No lock — concurrent coroutines can trigger duplicate login requests | asyncio.Lock with double-checked locking |
| Proactive refresh | Refreshes only after expiry | Refreshes 5 min before expiry — long-running bots never hit auth failures |
| EIP-712 encoding | Uses float arithmetic for price/size |
Decimal throughout — int(float("1.013") * 1e9) == 1012999999, not 1013000000 |
| Nonce strategy | Timestamp-based only | Pluggable NonceProvider — sequence counter for high-frequency quoting |
| WebSocket gaps | No sequence tracking | Per-channel sequence number gap detection with on_gap callback |
| Type safety | Plain dataclasses, no validation | Pydantic v2 — field-level validation at construction, not at submission. All REST methods return typed models, none return raw dict |
| Static analysis | No mypy configuration | mypy --strict — zero errors across all 7 source files, blocking in CI on Python 3.10/3.11/3.12 |
| Unified entry point | Separate auth / REST / WS objects to wire manually | GRVTClient façade — one object, shared auth, async context manager |
| Test coverage | No offline tests | 83 offline unit tests + 5 integration smoke tests (--integration flag, skipped in CI) |
| Developer docs | No contributor guide | CONTRIBUTING.md — prerequisites, test commands, step-by-step guide for adding endpoints |
MIT