Skip to content

Latest commit

 

History

History
179 lines (141 loc) · 7.87 KB

File metadata and controls

179 lines (141 loc) · 7.87 KB

hyperliquid-trader-tracker

A real-time Hyperliquid wallet tracker / whale watcher as a multi-tenant Telegram bot, written in Rust: anyone can message it, follow any wallet trading perps on Hyperliquid, and get a DM the moment that wallet opens, scales into, reduces, or closes a position — with entry, size, notional, leverage, realized/unrealized PnL and ROI. Useful for copy trading, whale watching, and monitoring your own accounts, all over the public trades firehose.

  • No auth, no scraping. Uses only Hyperliquid's sanctioned public API (wss://api.hyperliquid.xyz/ws + POST /info). See docs/hyperliquid-api-map.md.
  • Scales to 1k+ wallets on one connection — the trades feed carries both counterparties, so one market-data subscription serves every subscriber by filtering.
  • Multi-tenant. Each subscriber keeps their own labelled watchlist; a wallet's activity fans out to every subscriber following it, in their own chat with their own label.
  • Stores nothing but the watchlists. Positions live in memory and are rebuilt from the chain (clearinghouseState) on every start; the SQLite tracker.db holds only (chat_id, address, label).

Design + rationale: docs/DESIGN.md.

How it works

trades firehose ─► resolve_deltas (users:[buyer,seller] filter) ─► apply_fill state machine
                                                                   └─► open/add/reduce/close ─► Telegram push
clearinghouseState ─► silent seed (cold-start correctness) + leverage ┘

The cold-start guarantee: before a wallet is admitted to the live filter, its current on-chain positions are seeded silently, so an add on a position opened before the tracker started is correctly reported as "Added to position", never a false "Started trade".

Notifications — one card per position, edited in place

The bot never sends a message per fill. Each (wallet, coin) position gets one message — a "TAPE" card — that is edited in place through the whole lifecycle: open → add → reduce → close. Telegram edits are silent, so you get one ping when the trade opens and the card then quietly morphs until it ends as the completed round-trip. No spam — and scrolling back shows each position's final state, not a trail of stale fills.

Cards are terminal-style grids: the same fixed slots across every event, in a monospace block, so the numbers a copy-trader acts on always sit in the same place. The trader name in the header is a link to the wallet's explorer page (the address is one tap away — no noisy raw-address line), and the meta line carries a TX explorer link and the fill time. A fresh open:

🟢 Whale-1 · BTC LONG 10x
ENTRY    $63,120
SIZE     2.5 BTC
NOTIONAL $157.8k
🔗 TX · 🕒 12:00 UTC

…becomes, via silent edits, the same message once the wallet closes:

🔴 Whale-1 · BTC LONG 10x
SIZE     2 BTC
ENTRY    $63,120
EXIT     $64,500
NET P/L  +$2,760
ROI      +21.9%
HELD     3h12m
🔗 TX · 🕒 15:12 UTC

In between, an add shows the fill, the % it grew the position, the new blended entry and the live uPnL (ADDED +1 BTC +50% / ENTRY $62,800 / uPNL +$960); a reduce shows the booked PnL, the ROI on the reduced slice, and what's left (REDUCED -0.5 BTC -20% / REALIZED +$440 / ROI +14.7% / SIZE 2 left). ROI is the leveraged return on margin, shown when leverage is known.

Every live card carries a 🔄 Update P&L button: tapping it re-reads the wallet's on-chain state and edits the card into a live snapshot — current MARK, fresh uPNL + ROI, and the liquidation price with its distance from the mark (LIQ $57,180 -10.7%).

Set TRACKER_NOTIFY_REDUCE_CLOSE=false to suppress new reduce/close pings (relevant after a restart, when there's no live card left to edit) — an existing card is always edited silently, so it never goes stale.

Credentials — what you need and how to get it

One credential: a Telegram bot token. It cannot be generated by code — Telegram issues it via @BotFather:

  1. Open Telegram, message @BotFather, send /newbot.
  2. Choose a display name, then a username ending in bot.
  3. BotFather replies with a token like 123456789:AA... — copy it.

Put it in a .env at the repo root (loaded via load_dotenv()), or export it:

# .env
TELEGRAM_BOT_TOKEN=123456789:AA...

Verify the token works (and optionally see chat ids that have messaged the bot):

cargo run --bin hl-tracker-telegram-setup   # reads TELEGRAM_BOT_TOKEN from .env

That's it — there is no chat id to configure. This is a public bot: each subscriber's chat is captured automatically when they message it. (On-chain data needs no key — Hyperliquid's public API is unauthenticated.)

Locking the bot to yourself (ADMIN_CHAT_ID)

Because the bot is public by default, anyone who finds it can use it. If ADMIN_CHAT_ID is present, the bot is only accessible to you — no one else. Every command (/start, /help, /add, …) and button tap from any other chat is silently ignored, and persisted watchlists from other chats stop receiving notifications (their rows stay in the DB — removing the variable makes the bot public again). Find your chat id by messaging the bot and running cargo run --bin hl-tracker-telegram-setup, then:

# .env
ADMIN_CHAT_ID=123456789

To allow a few extra chats besides the admin, add them to TRACKER_ALLOWED_CHAT_IDS (comma-separated); the two combine.

Run

The tracker is a Rust binary; docker is the intended deployment (the original Python implementation still lives side by side — uv run hl-tracker — but the Rust port is what ships and carries the current notification format):

make up                                 # docker: rebuild + start in the background
make logs                               # follow (notifications land here in log-only mode)
# or natively:
cargo run --release --bin hl-tracker    # a.k.a. make run-rs

Then, in Telegram, anyone can DM the bot and manage their own watchlist:

/add 0x6bea…597f Whale-1     follow a wallet (labelled)
/remove 0x6bea…597f          stop following
/rename 0x6bea…597f Whale     relabel
/list                        show the wallets you follow
/positions                   live open positions of a wallet you follow (tap to pick one)

/positions renders a terminal watchlist — largest position first, abbreviated money, and a TOT footer with the wallet's net notional and net unrealized PnL:

📋 Whale-1 · 3 open
COIN  SIDE     VALUE    uPNL
BTC   L 10x  $128.2k  +$3.1k
ETH   S 5x    $64.4k   -$820
DOGE  L 3x     $9.6k    +$95
----------------------------
TOT   3 pos  $202.2k  +$2.4k

Configuration (env vars, or a .env at the repo root)

Variable Default Meaning
TELEGRAM_BOT_TOKEN Bot token from @BotFather; without it, notifications are logged only
ADMIN_CHAT_ID (open to all) If present, the bot is only accessible to you — commands, buttons, and notifications from/to any other chat are ignored
TRACKER_ALLOWED_CHAT_IDS (open to all) Comma-separated chat-id allowlist to make the bot private (combines with ADMIN_CHAT_ID)
TRACKER_NOTIFY_REDUCE_CLOSE true Notify on reduce/close (false = open+add only)
TRACKER_RECONCILE_INTERVAL_S 45 Leverage/drift reconcile cadence (0 disables)
TRACKER_RECONCILE_BATCH 20 Wallets re-seeded per reconcile cycle
TRACKER_SEED_CONCURRENCY 8 Concurrency of the startup seed sweep
TRACKER_LIVE_COINS (all perps) Comma-separated coins to restrict the feed
TRACKER_DB_PATH tracker.db SQLite watchlist path (repo root)

Develop

make check      # everything: ruff + ty + pytest, plus clippy -D warnings + cargo test + fmt
make check-rs   # Rust side only