HFT-grade triangular arbitrage scanner for Solana, using Jupiter Aggregator quotes as the price oracle and Telegram for real-time opportunity alerts.
The scanner walks a configurable list of three-leg cycles
(USDC → SOL → JUP → USDC and similar) and on every tick requests
three sequential quotes from Jupiter for each cycle. Anything that
returns more USDC than it started with — net of fees — is logged and,
above the configured basis-point threshold, pushed to a Telegram chat
for execution review.
This is a latency-critical workload. The free Jupiter API tier is rate-limited; even the paid tier doesn't help where it actually matters:
| Constraint | Free | Paid | Self-hosted Metis |
|---|---|---|---|
| Rate limits | strict | relaxed | none |
| Network RTT | ~50ms | ~50ms | ~1ms (loopback) |
| Determinism | upstream-load dependent | upstream-load dependent | local |
| Custom routing | ❌ | ❌ | ✅ |
A single triangle is 3 sequential quotes. At 50ms RTT each, a single triangle scan is 150ms — half a Solana block (block time ~400ms). Arb windows on Solana close in 1-3 blocks. Self-hosted brings per-leg latency to single-digit ms via loopback, turning a 150ms triangle scan into a ~10ms one. That's the difference between catching an opportunity and watching it close.
Beyond raw latency:
- No rate limit — a useful scanner runs 30-50 triangles continuously. At 1s cadence that's 90-150 req/s. No public tier sustains that without per-request cost.
- Determinism — public API responses fluctuate with upstream load. Self-hosted gives consistent timing for evaluating routing decisions and backtesting.
- Custom routing — the binary lets you set DEX inclusion rules and excluded pools. Relevant for any builder also running LP positions on routed pools.
- Node.js 20+
- Native
fetch(no SDK dependency, no supply-chain surface) - BigInt arithmetic for token amounts
- Telegram Bot API for alerts (no SDK, single HTTPS POST)
- JSON config for triangles, tokens, thresholds
src/
index.js — main scan loop with per-batch latency log
scan.js — three-leg cycle evaluator (returns bps profit)
jupiter.js — Jupiter quote client (public ↔ self-hosted toggle)
triangles.js — cycle definitions
tokens.js — token registry (mint, decimals, symbol)
telegram.js — alert sender (rate-limited, no-op if not configured)
.env.example — API URLs, Telegram credentials, thresholds
Early. Triangle scan loop runs against the public API and prints profit/loss in basis points per cycle. Telegram alerts fire above the configured threshold. The scanner does not execute swaps yet — opportunity detection only.
- Triangular arb scanner (3-leg cycles, USDC base)
- Per-leg latency measurement
- Configurable basis-point alert threshold
- Telegram notifications with per-cycle rate limiting
- Concurrent triangle scanning with rate-limit budget
- N-leg cycle support (4-leg, 5-leg cycles)
- Multi-DEX direct quotes (Raydium, Orca, Meteora) for cross-DEX arb in addition to triangular
- Yellowstone gRPC stream subscription for on-chain pool state (event-driven scans, not just polling)
- Jito bundle submission for atomic execution of top opportunities
- Self-hosted Metis binary integration (drop-in URL swap via env)
cp .env.example .env
# fill in TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID for alerts
npm install
npm startFlip USE_SELF_HOSTED=1 once the Metis binary is running locally on
the URL in JUPITER_SELF_HOSTED_URL.