Skip to content

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.

Terminal window
git clone <repo-url> wiretap && cd wiretap
cp .env.example .env # defaults are fine to start
echo 'WIRETAP_RPC_URL=https://api.devnet.solana.com' >> .env
echo 'WIRETAP_PROGRAMS=<your-program-id>' >> .env
docker compose -f infra/docker-compose.yml --profile full up -d

What 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
Terminal window
npx @wiretap-labs/cli doctor

Expect telegram: mock and everything else ok. The stack comes up with seeded demo data, so the answers work immediately:

Terminal window
pnpm demo # prints a real decoded failure, program status, and the bot's /why reply

On localnet with the demo program deployed, generate live failures instead with pnpm --filter @wiretap/demo-program traffic (slippage 6001, guard 6000, CU exhaustion).

Scaling & limits (what to expect under load)

Section titled “Scaling & limits (what to expect under load)”
  • Ingest throughput. The ClickHouse sink uses async_insert and 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 polling provider, event latency is bounded by the poll interval (WIRETAP_POLL_INTERVAL_MS, default 2s) plus RPC round-trips, and rows are stamped provider = "polling" at confirmed commitment. 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 (the retracted flag 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 doctor surfaces each dependency’s health.

Self-hosted instances phone home to nothing, publish nothing, and bill nothing.