Docker compose
After this page you have the full stack running on your machine: ingester, API, Telegram bot (mock transport), MCP server, and ClickHouse — watching a real devnet program, with zero paid keys.
The compose file lives at infra/docker-compose.yml in the repo. Every external dependency
has a keyless fallback: polling adapter instead of a paid stream, mock Telegram instead
of a real token, Stripe test mode instead of live billing. You lose nothing functional —
only latency (polling) and real chat delivery.
Bring it up
Section titled “Bring it up”git clone <repo-url> wiretap && cd wiretapcp .env.example .env # defaults are fine to startecho 'WIRETAP_RPC_URL=https://api.devnet.solana.com' >> .envecho 'WIRETAP_PROGRAMS=<your-program-id>' >> .env
docker compose -f infra/docker-compose.yml --profile full up -dWhat starts:
| Service | Port | Role |
|---|---|---|
clickhouse |
8123 / 9000 | Event analytics store (migrations auto-applied by migrate) |
ingester |
— | Rust chain-plane: watches programs via the configured provider |
api |
8790 | Ingest + query API, SQLite metadata at a volume |
bot |
— | Telegram bot; mock transport unless TELEGRAM_BOT_TOKEN is set |
mcp |
8791 | MCP server (HTTP transport) |
web |
8792 | This website + leaderboard shell |
Verify
Section titled “Verify”npx @wiretap-labs/cli doctorExpect telegram: mock and everything else ok. The stack comes up with seeded demo
data, so the answers work immediately:
pnpm demo # prints a real decoded failure, program status, and the bot's /why replyOn localnet with the demo program deployed, generate live failures instead with
pnpm --filter @wiretap/demo-program traffic (slippage 6001, guard 6000, CU exhaustion).
Upgrade paths
Section titled “Upgrade paths”- Real Telegram: 5-minute BotFather setup, set
TELEGRAM_BOT_TOKEN, restartbot. - Lower latency: switch the provider from
pollingtoyellowstoneorhelius. - Everything configurable: see the configuration reference.
Scaling & limits (what to expect under load)
Section titled “Scaling & limits (what to expect under load)”- Ingest throughput. The ClickHouse sink uses
async_insertand sustains well above 25,000 events/second on a single node in batched inserts — verified by the ingester’s burst/no-loss test (cargo test -p wiretap-ingester -- --ignored, which writes a burst and asserts every signature reads back). Throughput scales with batch size and CH resources. - Polling-adapter freshness is honest. On the default
pollingprovider, event latency is bounded by the poll interval (WIRETAP_POLL_INTERVAL_MS, default 2s) plus RPC round-trips, and rows are stampedprovider = "polling"atconfirmedcommitment. It is rate-budgeted (WIRETAP_RPC_BUDGET_RPS) so it never exceeds a free RPC’s limits — the trade-off is latency, not correctness. Switch to a streaming provider for sub-second latency. - Forks are handled. Rows are versioned by commitment; a finalizer retracts transactions
that fork out below
finalized(theretractedflag excludes them from every rollup and query). This is covered by the fork/retraction integration test. - Gaps & restarts. The ingester persists a signature cursor per program, so a restart resumes without loss or duplication; a cold start caps backfill to the most recent signatures to protect the RPC budget.
- Failure modes. If ClickHouse is briefly unreachable, the ingester logs and retries the
poll cycle without advancing its cursor — nothing is lost. If the RPC rate-limits, the
budgeted limiter backs off.
wiretap doctorsurfaces each dependency’s health.
Self-hosted instances phone home to nothing, publish nothing, and bill nothing.