Standalone bot that drives the AR.IO Network's permissionless epoch lifecycle on Solana.
Each epoch goes through six steps:
create_epoch → tally_weights → prescribe_epoch → [observe] → distribute_epoch → close_epoch
The cranker polls on-chain state and submits whichever step is due. All instructions are permissionless and idempotent — multiple crankers can run concurrently without conflict.
The image is hosted on a private GHCR registry, so operators need a GitHub PAT with read:packages scope to pull it. One-time setup:
echo "$GITHUB_PAT" | docker login ghcr.io -u "$GITHUB_USERNAME" --password-stdinThen:
docker run -d \
--name ar-io-cranker \
--restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-v /path/to/cranker-keypair.json:/keys/cranker.json:ro \
-e SOLANA_RPC_URL=https://api.mainnet-beta.solana.com \
-e SOLANA_KEYPAIR_PATH=/keys/cranker.json \
ghcr.io/ar-io/ar-io-cranker:latestThe host keypair file must be readable by uid 10001 inside the container — either chmod to 644 or chown to uid 10001 on the host.
Reference manifests in deploy/:
deploy/docker-compose.ymldeploy/k8s/— Deployment, ConfigMap, Secret example, ServiceMonitordeploy/systemd/— unit file + install helper
yarn install # requires GitHub Packages auth — see below
yarn build
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com \
SOLANA_KEYPAIR_PATH=/path/to/cranker-keypair.json \
yarn startAll settings are environment variables.
| Variable | Default | Notes |
|---|---|---|
SOLANA_RPC_URL |
(required) | Solana JSON-RPC endpoint |
SOLANA_KEYPAIR_PATH |
(required) | Path to signer keypair JSON |
POLL_INTERVAL_MS |
derived | Tick interval ms (min 1000). Optional — derived from the epoch duration when unset (~60s on 24h epochs, down to 10s on short ones); an explicit value overrides. |
CLEANUP_MIN_INTERVAL_MS |
derived | Min ms between cleanup passes (min 30000). Optional — derived from the epoch duration when unset (~30min on 24h, 5min on short); an explicit value overrides. |
BATCH_SIZE |
15 |
Tally/distribute batch size |
ENABLE_CLOSE_EPOCHS |
true |
Close old epochs |
EPOCH_RETENTION |
7 |
Epochs to keep before closing |
LOG_LEVEL |
info |
debug / info / warn / error |
LOG_FORMAT |
json |
json (production) / text (dev) |
HEALTH_PORT |
8080 |
Health/metrics HTTP port |
HEALTH_HOST |
127.0.0.1 |
Bind address (loopback by default) |
WARN_BALANCE_SOL |
0.3 |
Log warn below this |
CRITICAL_BALANCE_SOL |
0.1 |
Health endpoint returns 503 below this |
MIN_START_BALANCE_SOL |
0.01 |
Refuse to start below this |
SHUTDOWN_TIMEOUT_MS |
12000 |
Graceful shutdown deadline |
ARIO_*_PROGRAM_ID |
(SDK constants) | Override for localnet/devnet |
See .env.example for a copy-paste template.
The cranker exposes a small HTTP server (default 127.0.0.1:8080):
GET /health— JSON operational state. Returns 200 when healthy, 503 when:- No tick recorded in
3 × pollIntervalMs - Wallet balance below
CRITICAL_BALANCE_SOL - 10+ consecutive real errors
- No tick recorded in
GET /metrics— Prometheus exposition. Counters for each pipeline step, error categories, wallet balance gauge.GET /— Plainok(basic liveness, never 503).
Use /health for Kubernetes readiness probes and /metrics for Prometheus scraping.
Each cycle submits a small number of transactions per epoch (1 create + ⌈gateways/15⌉ tally + 1 prescribe + ⌈gateways/15⌉ distribute + 1 close). Actual SOL burn depends on active gateway count, priority fees, and contention with other crankers.
| Threshold | Default | Action |
|---|---|---|
| Warn | 0.3 SOL | level=warn log line each balance check |
| Critical | 0.1 SOL | level=error log + /health returns 503 |
| Refuse start | 0.01 SOL | Cranker exits with code 3 |
Reasonable starting allocation: 1 SOL. Alert on cranker_wallet_balance_sol dropping below your warn threshold and top up from a treasury wallet. See docs/OPERATIONS.md for refill patterns and Prometheus alert templates.
The AR.IO observer (ar-io-observer) ships with a built-in cranker that gateway operators can enable with ENABLE_EPOCH_CRANKING=true — same algorithm, reuses the observer's keypair and connection. Both modes are safe to run concurrently: each instruction is permissionless and idempotent, and the cranker adds 1–5 s of random jitter per tick so independent crankers naturally space out across pipeline steps. When two do race the same tx, one lands and the other gets an already_done debug log.
Pick the standalone (this repo) when:
- You're not running an observer (e.g., dedicated cranker host, foundation infrastructure)
- You want separate logs / metrics / restart policy from the observer
- You want a dedicated funded wallet for cranking
Pick the observer-embedded version when:
- You already run an observer and want to contribute to keeping the network alive without managing a second service
ar-io-cranker [--help|-h] [--version|-v]
Daemon-only — no other subcommands.
yarn install
yarn build # outputs to dist/
yarn start # node dist/index.js
yarn dev # tsx src/index.ts (no build needed)
yarn typecheck # tsc --noEmitThis repo depends on @ar-io/sdk from GitHub Packages. To install:
- Create a GitHub Personal Access Token (PAT) with
read:packagesscope. - Export it as
NODE_AUTH_TOKEN:export NODE_AUTH_TOKEN=<your-pat>
- Run
yarn install.
The included .npmrc references ${NODE_AUTH_TOKEN} so no token lands in the repo.
Apache-2.0 — see LICENSE.