A lightweight proxy router for ar.io network gateways with content verification.
- Content Verification — Verifies content integrity via hash checking against trusted gateways
- Smart Gateway Selection — Multiple routing strategies (fastest, random, round-robin, temperature) with health tracking and circuit breakers
- ArNS Support — Resolves Arweave Name System names with consensus verification across multiple trusted gateways
- Manifest Verification — Verifies path manifests and their content mappings against trusted sources
- Root Domain Hosting — Serve any ArNS name or txId directly at your root domain
- Restrict to Root Host Mode — Lock down router to serve only root domain content
- GraphQL Proxy — Proxy GraphQL requests to an upstream Arweave query endpoint
- Two Operating Modes:
proxy— Fetches, verifies, and serves content through the routerroute— Redirects clients to gateway URLs (client fetches directly)
- Content Cache — LRU cache for verified content with optional disk-backed persistence for large cache sizes
- Arweave HTTP API Proxy — Proxy Arweave node API endpoints (
/info,/tx/*,/block/*,/wallet/*, etc.) - Telemetry — SQLite-backed metrics for gateway performance tracking with configurable sampling
- Admin UI — Built-in web admin dashboard on a separate port with setup wizard, status monitoring, gateway health, telemetry, moderation, and settings
- Content Moderation — Admin API for blocking ArNS names and transaction IDs
- Rate Limiting — Per-IP rate limiting with configurable windows and thresholds
- Graceful Shutdown — Drain period for in-flight requests before shutdown, with configurable timeouts
- Gateway Ping Service — Background latency probing for temperature-based routing
- Standalone Binaries — Cross-compile to a single executable for Linux, macOS, and Windows
- Bun >= 1.0.0
Download the latest binary for your platform from GitHub Releases:
| Platform | Binary |
|---|---|
| Linux x64 | wayfinder-router-linux-x64 |
| Linux ARM64 | wayfinder-router-linux-arm64 |
| macOS Intel | wayfinder-router-darwin-x64 |
| macOS Apple Silicon | wayfinder-router-darwin-arm64 |
| Windows x64 | wayfinder-router-windows-x64.exe |
Each release also includes a checksums.txt with SHA256 hashes for verification.
Then run it:
# Make executable (Linux/macOS)
chmod +x wayfinder-router-linux-x64
# Verify checksum (optional but recommended)
sha256sum wayfinder-router-linux-x64
# Compare with checksums.txt from the release
# Run — the admin UI opens at http://localhost:3001 with a setup wizard
./wayfinder-router-linux-x64The setup wizard at http://localhost:3001 will guide you through configuration. No .env file needed to start.
Or build binaries from source:
bun install
bun run build:binaries # outputs to ./builds/# Install dependencies
bun install
# Copy example environment file and configure
cp .env.example .env
# Start development server with hot reload
bun run dev
# Or run production directly (Bun runs TypeScript natively)
bun run startThe server starts at http://localhost:3000 by default. The admin UI is available at http://localhost:3001.
# Build and run with docker compose
docker compose up wayfinder-router
# Or build and run manually
docker build -t wayfinder-router .
docker run -p 3000:3000 -p 3001:3001 --env-file .env -v ./data:/app/data wayfinder-routerWayfinder Router includes a built-in web admin dashboard that runs on a separate port from the public router (port 3001 by default). This ensures admin endpoints are never exposed to public traffic.
http://localhost:3001
- Status — Live dashboard showing uptime, operating mode, verification status, gateway health bar, cache utilization, and ping service stats
- Gateways — Sortable table of all ar.io network gateways with health, temperature score, latency, success rate, and traffic stats
- Telemetry — Time-ranged metrics (1h/6h/24h/7d) with request totals, success rates, bytes served, and per-gateway performance table with CSV export
- Moderation — Block/unblock ArNS names and transaction IDs, view blocklist, or enable moderation if not yet configured
- Settings — View all current configuration grouped by category (server, mode, routing, verification, cache, telemetry, rate limiting, HTTP, shutdown)
On first run (when BASE_DOMAIN=localhost), the admin UI shows a guided setup wizard:
- Domain — Configure your base domain, port, and optional root host content
- Routing — Choose operating mode (proxy/route), routing strategy, and gateway source
- Verification — Enable content verification, choose trust source, set gateway count and consensus threshold
The wizard generates a .env configuration file that can be copied to clipboard or saved directly.
| Public Port (3000) | Admin Port (3001) | |
|---|---|---|
| Default bind | 0.0.0.0 (all interfaces) |
127.0.0.1 (localhost only) |
| Admin UI | Not available (404) | Full access |
| Content serving | Normal operation | N/A |
- Admin is never exposed on the public port
- Default localhost binding means only local access (use SSH tunnel for remote)
- Set
ADMIN_HOST=0.0.0.0to expose over network — this requiresADMIN_TOKENto be set ADMIN_PORTmust differ fromPORT(validated at startup)
| Variable | Default | Description |
|---|---|---|
ADMIN_UI_ENABLED |
true |
Enable admin UI server |
ADMIN_PORT |
3001 |
Admin server port (separate from public) |
ADMIN_HOST |
127.0.0.1 |
Admin bind address (127.0.0.1 = localhost only) |
ADMIN_TOKEN |
(empty) | Bearer token for auth (required when ADMIN_HOST is not localhost) |
ADMIN_OPEN_BROWSER |
true |
Auto-open admin UI in browser on startup (skipped in CI/Docker/non-TTY) |
To access the admin UI from a remote machine while keeping it secure:
# Option 1: SSH tunnel (recommended)
ssh -L 3001:localhost:3001 your-server
# Then open http://localhost:3001 in your browser
# Option 2: Expose with token auth
ADMIN_HOST=0.0.0.0
ADMIN_TOKEN=your-secure-token-here
# The UI will prompt for the token on loadConfigure ROOT_HOST_CONTENT to serve an ArNS name or transaction ID at your root domain:
# In .env — serve an ArNS name (auto-detected by format)
ROOT_HOST_CONTENT=wayfinder
# Or serve a transaction ID directly (43-char base64url)
ROOT_HOST_CONTENT=bNbA3TEQVL60xlgCcqdz4ZPHFZ711cZ3hmkpGttDt_UWith this configuration:
https://yourdomain.com/→ Serves the configured contenthttps://yourdomain.com/docs→ Serves at path/docshttps://yourdomain.com/wayfinder/info→ Router info page
Lock down the router to serve only root domain content:
ROOT_HOST_CONTENT=wayfinder
RESTRICT_TO_ROOT_HOST=trueWhen enabled:
- Subdomain requests are rejected with 404
- TxId path requests (e.g.,
/{txId}) are rejected with 404 - Root domain paths work normally
- Router management endpoints (
/wayfinder/*) still work
Proxy /graphql requests to an upstream GraphQL endpoint:
GRAPHQL_PROXY_URL=https://arweave-search.goldsky.com/graphqlAccess ArNS names via subdomain:
http://{arns-name}.localhost:3000/
Access content by Arweave transaction ID:
http://localhost:3000/{txId}
http://localhost:3000/{txId}/path/to/file
Force a specific mode via query parameter (when ALLOW_MODE_OVERRIDE=true):
http://localhost:3000/{txId}?mode=route
http://localhost:3000/{txId}?mode=proxy
Verified Arweave content is cached in an LRU with optional disk-backed persistence. Since Arweave data is immutable, verified content can be cached indefinitely — only LRU eviction bounds storage.
# Enable disk-backed cache (recommended for production)
CONTENT_CACHE_ENABLED=true
CONTENT_CACHE_PATH=./data/content-cache
CONTENT_CACHE_MAX_SIZE_BYTES=53687091200 # 50GB
CONTENT_CACHE_MAX_ITEM_SIZE_BYTES=2147483648 # 2GBWhen CONTENT_CACHE_PATH is set:
- LRU holds metadata only (low memory footprint)
- Content bytes stored as files on disk (
<sha256>.bin+<sha256>.meta.json) - Cache survives restarts — index restored from disk on startup
- Atomic writes via temp file + rename for crash safety
When CONTENT_CACHE_PATH is empty, the cache operates entirely in-memory.
Proxy Arweave node HTTP API endpoints through the router:
ARWEAVE_API_ENABLED=trueSupported endpoints:
| Endpoint | Description |
|---|---|
/info |
Network info (height, version) |
/peers |
Connected peers list |
/tx/{id} |
Transaction by ID |
/tx/{id}/status |
Transaction confirmation status |
/tx/{id}/{field} |
Transaction field (owner, tags, data_size, etc.) |
/tx/{id}/data |
Transaction data |
/tx/{id}/data.{ext} |
Transaction data with content-type hint |
/tx/{id}/offset |
Transaction offset |
/wallet/{addr}/balance |
Wallet balance |
/wallet/{addr}/last_tx |
Wallet's last transaction |
/price/{bytes} |
Price for data upload |
/price/{bytes}/{target} |
Price for data upload to target |
/block/hash/{hash} |
Block by hash |
/block/height/{height} |
Block by height |
Responses are cached with category-aware TTLs: immutable data (transactions, blocks) cached for 24 hours, dynamic data (info, balances) cached for 30 seconds.
Block ArNS names or transaction IDs from being served:
MODERATION_ENABLED=true
MODERATION_ADMIN_TOKEN=<your-secure-token>Admin API (requires Bearer token):
# Block an ArNS name
curl -X POST http://localhost:3000/wayfinder/moderation/block \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"type":"arns","value":"badcontent","reason":"Policy violation"}'
# Block a transaction ID
curl -X POST http://localhost:3000/wayfinder/moderation/block \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"type":"txid","value":"abc123...","reason":"DMCA takedown"}'
# List blocked content
curl http://localhost:3000/wayfinder/moderation/blocklist \
-H "Authorization: Bearer <token>"
# Unblock
curl -X DELETE http://localhost:3000/wayfinder/moderation/block/arns/badcontent \
-H "Authorization: Bearer <token>"All configuration is via environment variables. See .env.example for the full list with descriptions. Key variables:
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
HOST |
0.0.0.0 |
Bind address |
BASE_DOMAIN |
localhost |
Base domain for ArNS subdomain routing |
ROOT_HOST_CONTENT |
(empty) | ArNS name or txId to serve at root domain |
RESTRICT_TO_ROOT_HOST |
false |
Only serve root domain content |
GRAPHQL_PROXY_URL |
(empty) | Upstream GraphQL endpoint |
SERVER_IDLE_TIMEOUT_SEC |
(derived) | Idle connection timeout, 1–255s. Defaults to the upstream retry budget (HTTP_REQUEST_TIMEOUT_MS × RETRY_ATTEMPTS) plus headroom. Usually leave unset |
| Variable | Default | Description |
|---|---|---|
DEFAULT_MODE |
proxy |
Operating mode: proxy or route |
ALLOW_MODE_OVERRIDE |
true |
Allow ?mode= query parameter |
| Variable | Default | Description |
|---|---|---|
VERIFICATION_ENABLED |
true |
Enable content hash verification |
VERIFICATION_GATEWAY_SOURCE |
top-staked |
Source for trusted gateways: top-staked or static |
VERIFICATION_GATEWAY_COUNT |
3 |
Number of top-staked gateways for verification |
VERIFICATION_STATIC_GATEWAYS |
(see .env.example) | Comma-separated gateway URLs |
ARNS_CONSENSUS_THRESHOLD |
2 |
Minimum gateways that must agree on ArNS resolution |
VERIFICATION_RETRY_ATTEMPTS |
3 |
Gateways to try before failing |
| Variable | Default | Description |
|---|---|---|
ROUTING_STRATEGY |
fastest |
Strategy: fastest, random, round-robin, temperature |
ROUTING_GATEWAY_SOURCE |
network |
Source: network, trusted-peers, static, trusted-ario |
ROUTING_STATIC_GATEWAYS |
(see .env.example) | Comma-separated gateway URLs |
TRUSTED_PEER_GATEWAY |
https://turbo-gateway.com |
Gateway for peer list (when source=trusted-peers) |
TRUSTED_ARIO_GATEWAYS |
(empty) | Trusted gateways (when source=trusted-ario) |
| Variable | Default | Description |
|---|---|---|
CONTENT_CACHE_ENABLED |
true |
Enable verified content cache |
CONTENT_CACHE_MAX_SIZE_BYTES |
53687091200 (50GB) |
Maximum cache size |
CONTENT_CACHE_MAX_ITEM_SIZE_BYTES |
2147483648 (2GB) |
Maximum single item size |
CONTENT_CACHE_PATH |
(empty) | Disk path for persistence (empty = in-memory only) |
ARNS_CACHE_TTL_MS |
300000 (5min) |
ArNS resolution cache TTL |
| Variable | Default | Description |
|---|---|---|
RETRY_ATTEMPTS |
3 |
Retry attempts for failed requests |
RETRY_DELAY_MS |
100 |
Delay between retries |
CIRCUIT_BREAKER_THRESHOLD |
3 |
Failures before opening circuit |
CIRCUIT_BREAKER_RESET_MS |
60000 (1min) |
Time before retrying a broken gateway |
GATEWAY_HEALTH_TTL_MS |
300000 (5min) |
Health status cache TTL |
STREAM_TIMEOUT_MS |
120000 (2min) |
Per-chunk stream read timeout |
| Variable | Default | Description |
|---|---|---|
TELEMETRY_ENABLED |
true |
Enable telemetry collection |
TELEMETRY_ROUTER_ID |
router-{timestamp} |
Instance identifier |
TELEMETRY_DB_PATH |
./data/telemetry.db |
SQLite database path |
TELEMETRY_RETENTION_DAYS |
30 |
Data retention period |
TELEMETRY_SAMPLE_SUCCESS |
0.1 |
Sampling rate for successful requests |
TELEMETRY_SAMPLE_ERRORS |
1.0 |
Sampling rate for errors |
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_ENABLED |
false |
Enable per-IP rate limiting |
RATE_LIMIT_WINDOW_MS |
60000 (1min) |
Rate limit window |
RATE_LIMIT_MAX_REQUESTS |
1000 |
Max requests per IP per window |
| Variable | Default | Description |
|---|---|---|
MODERATION_ENABLED |
false |
Enable content moderation |
MODERATION_BLOCKLIST_PATH |
./data/blocklist.json |
Blocklist file (auto-created, hot-reloaded) |
MODERATION_ADMIN_TOKEN |
(empty) | Bearer token for admin endpoints |
| Variable | Default | Description |
|---|---|---|
ARWEAVE_API_ENABLED |
false |
Enable Arweave API proxy |
ARWEAVE_READ_NODES |
(Arweave tip nodes) | Nodes for GET requests |
ARWEAVE_WRITE_NODES |
(falls back to read nodes) | Nodes for POST requests |
ARWEAVE_API_CACHE_ENABLED |
true |
Cache API responses |
| Variable | Default | Description |
|---|---|---|
SHUTDOWN_DRAIN_TIMEOUT_MS |
15000 (15s) |
Grace period for in-flight requests |
SHUTDOWN_TIMEOUT_MS |
30000 (30s) |
Total shutdown timeout |
Router management endpoints are under the /wayfinder/ prefix:
| Endpoint | Description |
|---|---|
GET /wayfinder/health |
Health check |
GET /wayfinder/ready |
Readiness check |
GET /wayfinder/metrics |
Prometheus metrics |
GET /wayfinder/info |
Router info and configuration |
GET /wayfinder/stats/gateways |
Gateway performance statistics |
GET /wayfinder/stats/gateways/list |
List all tracked gateways |
GET /wayfinder/stats/gateways/:gateway |
Detailed stats for a specific gateway |
GET /wayfinder/stats/export |
Export telemetry data |
Moderation endpoints (when MODERATION_ENABLED=true):
| Endpoint | Auth | Description |
|---|---|---|
GET /wayfinder/moderation/check/:type/:value |
No | Check if content is blocked |
GET /wayfinder/moderation/blocklist |
Yes | List all blocked content |
GET /wayfinder/moderation/stats |
Yes | Moderation statistics |
POST /wayfinder/moderation/block |
Yes | Block content |
POST /wayfinder/moderation/reload |
Yes | Reload blocklist from disk |
DELETE /wayfinder/moderation/block/:type/:value |
Yes | Unblock content |
Other endpoints (public port):
| Endpoint | Description |
|---|---|
ALL /graphql |
GraphQL proxy (requires GRAPHQL_PROXY_URL) |
/info, /tx/*, /block/*, /wallet/*, /price/*, /peers |
Arweave API (requires ARWEAVE_API_ENABLED) |
Admin UI endpoints (admin port, default 3001):
| Endpoint | Description |
|---|---|
GET / |
Admin UI SPA |
GET /api/status |
Aggregated status data |
GET /api/gateways |
Gateway list with health and scores |
GET /api/telemetry |
Time-ranged telemetry stats |
GET /api/config |
Current configuration (sanitized) |
GET /api/moderation |
Moderation status and blocklist |
POST /api/config/save |
Save .env file |
POST /api/restart |
Validate config and restart router |
When ROOT_HOST_CONTENT is not set, the root endpoint (/) displays router info.
# Build and run with docker compose (maps to host port 3020)
docker compose up wayfinder-router
# Or build and run manually
docker build -t wayfinder-router .
docker run -p 3000:3000 -p 3001:3001 --env-file .env -v ./data:/app/data wayfinder-routerThe production container runs as a non-root user with resource limits (2 CPUs, 512MB memory). The ./data volume persists telemetry, content cache, and blocklist data.
# Start dev service with hot reload (requires Dockerfile.dev)
docker compose --profile dev up wayfinder-router-dev# Core
bun run dev # Start with hot reload (bun --watch)
bun run start # Run production (bun src/index.ts)
bun run typecheck # Type check without emitting
bun run test # Run tests
bun run test:watch # Run tests in watch mode
# Build
bun run build # Compile TypeScript (tsc)
bun run build:binaries # Cross-compile standalone binaries for all platforms
# Code quality
bun run lint # Run ESLint
bun run lint:fix # Auto-fix ESLint issues
bun run format # Format with Prettier
bun run format:check # Check formatting
# Utilities
bun run stats # Show gateway telemetry statistics
bun run clear:telemetry # Clear telemetry database
bun run clear:all # Clear all data (telemetry + cache)Additional documentation is available in the docs/ directory:
- Architecture — System design, request flows, verification, routing strategies
- Operations — Running, configuring, monitoring, admin UI, troubleshooting
Apache-2.0