# Hyperliquid Source
A Drasi source that connects to [Hyperliquid](https://hyperliquid.xyz)'s public APIs to stream real-time DeFi perpetual and spot market data as a continuously-queryable graph. No API keys required.
## What You Can Build
- **Price alert bots** — trigger when BTC crosses $100k or ETH drops 5% in a minute
- **Whale watchers** — detect trades over $1M in real time
- **Spread monitors** — alert when bid-ask spread widens beyond a threshold
- **Funding rate dashboards** — track which coins have extreme funding
- **Liquidation feeds** — monitor liquidation cascades across assets
- **Cross-asset correlation** — join trades, prices, and funding through graph traversal
## Quick Start
```rust
use drasi_lib::{DrasiLib, Query};
use drasi_reaction_log::LogReaction;
use drasi_source_hyperliquid::{HyperliquidSource, HyperliquidNetwork};
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
// Create a source streaming BTC and ETH data
let source = HyperliquidSource::builder("hl")
.with_network(HyperliquidNetwork::Mainnet)
.with_coins(vec!["BTC", "ETH"])
.with_mid_prices(true)
.with_trades(true)
.start_from_now()
.build()?;
// Alert when BTC price changes
let query = Query::cypher("btc-price")
.query(r#"
MATCH (m:MidPrice)-[:PRICE_OF]->(c:Coin {name: "BTC"})
RETURN c.name AS coin, m.price AS price
"#)
.from_source("hl")
.auto_start(true)
.enable_bootstrap(true)
.build();
let reaction = LogReaction::builder("log")
.from_query("btc-price")
.build()?;
let core = Arc::new(
DrasiLib::builder()
.with_source(source)
.with_query(query)
.with_reaction(reaction)
.build().await?,
);
core.start().await?;
println!("Streaming... press Ctrl+C to stop");
tokio::signal::ctrl_c().await?;
core.stop().await?;
Ok(())
}
```
## Graph Model
All data flows through a **Coin-centric** graph. Every market data entity connects back to a `Coin` node, enabling graph traversal queries that correlate across data types.
```
┌───────────┐
│ Coin │
│ (anchor) │
└─────┬─────┘
┌────────┬─────┼─────┬──────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Trade MidPrice │ Liquidation FundingRate
OrderBook
```
See [GRAPH_SCHEMA.md](GRAPH_SCHEMA.md) for complete property tables, ID formats, and relationship details.
## Example Queries
### 🐋 Whale Detection — Large Trades in Real Time
Continuously alert on trades over $100k notional value:
```cypher
MATCH (t:Trade)-[:TRADED_ON]->(c:Coin)
WHERE t.price * t.size > 100000
RETURN c.name AS coin, t.side AS side,
t.price AS price, t.size AS size,
t.price * t.size AS notional
```
Every time a whale trade lands, your reaction fires with the coin, direction, and size.
### 📊 Spread Monitor — Bid-Ask Spread Alert
Alert when any coin's spread widens beyond 0.1%:
```cypher
MATCH (ob:OrderBook)-[:BOOK_OF]->(c:Coin),
(m:MidPrice)-[:PRICE_OF]->(c)
WHERE (ob.best_ask_price - ob.best_bid_price) / m.price > 0.001
RETURN c.name AS coin,
ob.best_bid_price AS bid, ob.best_ask_price AS ask,
ob.best_ask_price - ob.best_bid_price AS spread,
m.price AS mid_price
```
### 💰 Funding Arbitrage Scanner
Find coins with extreme funding rates (potential arbitrage opportunity):
```cypher
MATCH (f:FundingRate)-[:FUNDING_OF]->(c:Coin),
(m:MidPrice)-[:PRICE_OF]->(c)
WHERE f.rate > 0.01 OR f.rate < -0.01
RETURN c.name AS coin, f.rate AS funding_rate,
f.mark_price AS mark, m.price AS mid,
f.mark_price - m.price AS basis,
f.open_interest AS oi
```
### 🔥 Liquidation Cascade Detector
Monitor liquidation events alongside funding rates to spot cascades:
```cypher
MATCH (l:Liquidation)-[:LIQUIDATED_ON]->(c:Coin),
(f:FundingRate)-[:FUNDING_OF]->(c)
RETURN c.name AS coin, l.side AS liq_side,
l.price AS liq_price, l.size AS liq_size,
f.rate AS funding, f.open_interest AS oi
```
### 📈 Multi-Asset Price Dashboard
Track real-time prices for your watchlist with leverage info:
```cypher
MATCH (m:MidPrice)-[:PRICE_OF]->(c:Coin)
RETURN c.name AS coin, c.max_leverage AS leverage,
c.market_type AS type, m.price AS price
```
### 🔗 Cross-Entity Correlation — Price vs Mark vs Funding
Join mid-price, order book, and funding through the Coin node in one query:
```cypher
MATCH (m:MidPrice)-[:PRICE_OF]->(c:Coin {name: "ETH"}),
(ob:OrderBook)-[:BOOK_OF]->(c),
(f:FundingRate)-[:FUNDING_OF]->(c)
RETURN m.price AS mid_price,
ob.best_bid_price AS bid, ob.best_ask_price AS ask,
f.mark_price AS mark, f.rate AS funding,
f.open_interest AS open_interest
```
### 🏦 Open Interest Monitor
Alert when a coin's open interest crosses a threshold:
```cypher
MATCH (f:FundingRate)-[:FUNDING_OF]->(c:Coin)
WHERE f.open_interest > 10000000
RETURN c.name AS coin, f.open_interest AS oi, f.volume_24h AS vol_24h
```
### ⚡ High-Leverage Coin Scan
Find all coins offering 50x+ leverage with their current metrics:
```cypher
MATCH (f:FundingRate)-[:FUNDING_OF]->(c:Coin),
(m:MidPrice)-[:PRICE_OF]->(c)
WHERE c.max_leverage >= 50
RETURN c.name AS coin, c.max_leverage AS leverage,
m.price AS price, f.rate AS funding, f.volume_24h AS volume
```
## Configuration
```rust
let source = HyperliquidSource::builder("my-source")
.with_network(HyperliquidNetwork::Mainnet) // or Testnet, or Custom { rest_url, ws_url }
.with_coins(vec!["BTC", "ETH", "SOL"]) // or .with_all_coins()
.with_trades(true) // trade events (high volume)
.with_order_book(true) // L2 book snapshots (high volume)
.with_mid_prices(true) // mid-market prices (moderate)
.with_funding_rates(true) // funding rate polling (low volume)
.with_liquidations(true) // liquidation events (sporadic)
.with_funding_poll_interval_secs(30) // poll every 30s instead of default 60s
.start_from_beginning() // or .start_from_now() / .start_from_timestamp(ms)
.build()?;
```
### Config Reference
| `network` | `HyperliquidNetwork` | `Mainnet` | API environment |
| `coins` | `CoinSelection` | `All` | `Specific { coins }` or `All` |
| `enable_trades` | `bool` | `false` | Trade event stream |
| `enable_order_book` | `bool` | `false` | L2 order book stream |
| `enable_mid_prices` | `bool` | `true` | Mid-price stream |
| `enable_funding_rates` | `bool` | `false` | Funding rate polling |
| `enable_liquidations` | `bool` | `false` | Liquidation event stream |
| `funding_poll_interval_secs` | `u64` | `60` | Funding poll interval |
| `initial_cursor` | `InitialCursor` | `StartFromNow` | Where to start streaming |
## Bootstrap & Streaming
**Bootstrap** (REST): When a query subscribes with `enable_bootstrap`, the source fetches a snapshot of the relevant current data via Hyperliquid's `/info` REST endpoints. Coin and SpotPair nodes are always included. MidPrice, FundingRate, and OrderBook are bootstrapped only when their respective streams are enabled.
**Streaming** (WebSocket + REST polling): The source opens a WebSocket connection and subscribes to enabled channels. Funding rates are polled via REST at the configured interval.
| Coin | ✅ Always | — | Insert once, never updated |
| SpotPair | ✅ Always | — | Insert once, never updated |
| MidPrice | ✅ If enabled | WebSocket `allMids` | Insert first, then update |
| OrderBook | ✅ If enabled | WebSocket `l2Book` | Insert first, then update |
| FundingRate | ✅ If enabled | REST poll | Insert first, then update |
| Trade | — | WebSocket `trades` | Insert per event (append-only) |
| Liquidation | — | WebSocket `liquidations` | Insert per event (append-only) |
## State Persistence
When a `StateStoreProvider` is available:
- **Trade deduplication** — persists last seen `tid` per coin; prevents duplicates on WebSocket reconnect
- **Funding snapshots** — persists last funding state; skips redundant updates when data hasn't changed
## Testing
```bash
# Unit tests (no network)
cargo test -p drasi-source-hyperliquid
# Integration test against live mainnet (requires internet)
cargo test -p drasi-source-hyperliquid -- --ignored --nocapture
```
## Troubleshooting
| REST API unreachable | Verify connectivity to `https://api.hyperliquid.xyz/info` |
| WebSocket errors | `wscat -c wss://api.hyperliquid.xyz/ws` |
| No changes detected | Set `RUST_LOG=debug`, confirm channels are enabled |
| Too much data | Reduce coin list or disable high-volume streams (`trades`, `l2Book`) |
| Duplicate events on restart | Ensure a `StateStoreProvider` is configured |
## Limitations
- No historical replay — WebSocket starts from current data
- Funding rates are polled, not pushed in real time
- Subscribing to all trades + order books for all coins is very high volume
- SpotPair nodes have no relationships (standalone metadata)