Join our community of builders on Discord!

Run a Worker on Mainnet

Workers are the supply side of the Lightchain AI network. A worker runs whitelisted models via Ollama alongside the Worker Sidecar (shipped as a Docker image), claims inference sessions directly on-chain, streams answers to users through the public Worker Gateway, and earns fees for every completed job. This guide walks through the end-to-end lifecycle on Mainnet — from generating a worker key and funding it to registering on-chain, going live, and deregistering when you're done. Mainnet runs in dispatcher-free mode: no central service hands out jobs. Your worker watches the chain, wins sessions by sortition, and serves them itself — see Dispatcher-free Mode for how that works. Everything below configures the external profile from that page: no Redis, no VPN, no inbound ports. Already running a mainnet worker? If its docker run command sets WORKER_GATEWAY_URL but not SORTITION_ENABLED=true, it connects and sends heartbeats but is never given a job. Go to Move an existing worker to dispatcher-free mode.
Mainnet LCAI is real value. Double-check every address, RPC URL, and chain ID before signing transactions. Funds sent to the wrong address are unrecoverable.

Prerequisites

  • Docker installed (the image is linux/amd64)
  • GPU machine with enough VRAM for your model (8 GB+ for llama3-8b, 40 GB+ for llama3-70b)
  • Ollama installed and running
  • Funder wallet holding at least 50,051 LCAI (50,000 stake, a 50 LCAI gas buffer for claim, ack, blob and completion transactions, and a little on top because registering itself spends gas) — this is your existing wallet that pays to bring the worker online. It is NOT the worker key. You will generate a fresh worker key in Step 0 and send LCAI from the funder to it in Step 5.
  • Foundry installed (cast is used for key generation, balance checks, and contract reads)
  • Outbound HTTPS/WSS — nothing needs to be opened inbound

Network reference

ServiceValue
RPC URLhttps://rpc.mainnet.lightchain.ai
Chain ID9200
Beacon APIhttps://beacon.mainnet.lightchain.ai
Worker Gatewayhttps://worker-gateway.mainnet.lightchain.ai
Worker imageregistry.lightchain.ai/mainnet/worker:latest (anonymous pull)
https://worker-gateway-v2.mainnet.lightchain.ai is the same gateway service under a second name. A worker that already uses that URL needs no change.

Resolve mainnet contract addresses

AIConfig, JobRegistry and SessionManager are deployed at runtime, so their addresses are best resolved from the predeployed WorkerRegistry rather than hard-coded. Resolve them once and export them — the env file in Step 4 picks them up from your shell:
CodeBASH
Cross-check the values against the Mainnet Contract Addresses page before continuing.

Step 0: Generate a fresh worker key

Always use a dedicated, brand-new key for the worker. Do not reuse your funder key — the worker key sits in a Docker container with the keystore password, and should hold only the working capital (stake + small gas buffer) you're comfortable exposing to that machine.
CodeBASH
Copy the printed Address and Private key, then export them:
CodeBASH
Sanity check — the privkey must derive to the address you'll be funding and registering:
CodeBASH
The output must equal $WORKER_ADDR. If it doesn't, stop and re-export — anything sent to the wrong address is unrecoverable, and registering with mismatched keys locks the stake on the wrong account.
Do not set WORKER_PRIVKEY to your funder's private key. The two must be different. The funder pays from your existing wallet (Step 5); the worker is the new key from this step.

Step 1: Install Ollama and pull the model

On-chain model names are the Ollama tags, used verbatim (the on-chain modelId is keccak256(name)). Pick from Whitelisted models and pull each one into Ollama. Only llama3-8b and llama3-70b are not native Ollama tags — create them as aliases. The example below uses llama3-8b.
CodeBASH

Step 2: Pull the worker image

CodeBASH
The image contains two binaries: /bin/worker (the sidecar, default entrypoint) and /bin/lightchain-worker (the operator CLI for keys, registration, preflight, drain, balance and release). Pull again even if you already have the image. Since 16 October 2026 latest is worker release v0.0.5. An image pulled before then starts a new worker at the chain's first block, and one pulled before 13 September 2026 does not support dispatcher-free mode, preflight or web search.

Step 3: Import the worker private key

Imports $WORKER_PRIVKEY (set in Step 0) into an encrypted keystore on disk:
CodeBASH
The Address: printed by the command must equal $WORKER_ADDR from Step 0. If they differ, stop — the wrong key was imported.

Step 4: Write the worker env file

The sidecar and the CLI read the same variables, so keep them in one file. Write it from the shell where you resolved the addresses (the $… values expand from your exports):
CodeBASH
Replace <YOUR_PASSWORD> with the keystore password from Step 3. Every docker run below passes this file with --env-file.

Generate the ECDH encryption key

Every worker advertises an encryption public key on-chain so users can encrypt prompts for it — keygen produces that pair locally:
CodeBASH
The encryption private key stays on the machine running inference — it is what decrypts incoming prompts. Back it up together with the keystore: sessions encrypted to a lost ECDH key cannot be served.

Step 5: Fund the worker from your funder wallet

Send LCAI from your funder wallet (the existing one mentioned in Prerequisites) to $WORKER_ADDR (the fresh address from Step 0). Send 50,051 LCAI: 50,000 for the minimum stake, a 50 LCAI gas buffer, and a little on top. The worker pays for its own claimSession transactions as well as the ack, blob and completion transactions of every job. preflight warns when the balance is below 50 LCAI, and registering spends a little gas, so exactly 50,050 would leave the balance just under that line. If your funder wallet is in MetaMask/Rabby, send the transfer through the wallet UI. If you have its private key locally and want to use Foundry:
CodeBASH
Verify the worker received the funds:
CodeBASH
Common mistake: setting FUNDER_PRIVKEY to the same value as WORKER_PRIVKEY. That sends LCAI from the worker to itself (a no-op minus gas) and the worker stays unfunded. Cross-check with cast wallet address --private-key "$FUNDER_PRIVKEY" — it must NOT equal $WORKER_ADDR.

Step 6: Register on-chain

CodeBASH
This will:
  • Stake 50,000 LCAI (auto-queried from AIConfig)
  • Register your ECDH public key on-chain
  • Add every model in SUPPORTED_MODELS to your supported models
You can only serve models that are currently whitelisted on AIConfig. Attempting to register for a delisted or non-existent model will revert.

Step 7: Preflight

preflight is a read-only check of everything above in one go — RPC and chain id, registration, suspension, stake against the live on-chain minimum, wallet balance, local ECDH key vs the on-chain key, each model's whitelist / enabled / added state, gateway login, Ollama tags, and the beacon API. It prints one line per check and exits 1 if anything fails, so it is safe to run as often as you like:
CodeBASH
A healthy worker looks like this:
CodeTEXT
Run it before Step 6 too: with an unfunded, unregistered address it tells you exactly how much to send and whether your model names are whitelisted. preflight --worker 0xADDRESS inspects any address without a keystore — paste its output when asking for help.

Step 8: Run the worker

Start the container

The --add-host flag below makes host.docker.internal resolve to the Docker host on Linux (where it isn't provided by default). On macOS and Windows it's a no-op — Docker Desktop already maps that hostname — so the same command works everywhere.
CodeBASH

Step 9: Verify it's working

CodeBASH
The worker emits structured JSON logs. A healthy startup shows this sequence (timestamps and addresses elided):
CodeTEXT
On the first start two more lines, no session cursor stored; starting behind the safe head and no job cursor stored; starting behind the safe head, show where the worker begins: 2,000 blocks behind the chain head, not at the chain's first block.
CodeBASH
From here the worker watches the chain for session requests it is eligible for, self-claims via sortition, runs inference on your Ollama, streams tokens through the gateway, and submits the authoritative result as an on-chain blob. A quiet log is normal while other workers win the draws — there is no "job received" line until your worker wins a session. Then you'll see claimed session request followed by the per-job stages:
CodeBASH
If a registered worker never claims, run preflight (Step 7) first — the usual causes are a model-name mismatch or a suspension.

Whitelisted models

All of these are registered on AIConfig and whitelisted on WorkerRegistry (values as of 2026-10-21; read the live fee with cast call $AI_CONFIG_ADDRESS "getModelFee(bytes32)(uint256)" $(cast keccak <name>) --rpc-url $RPC_URL):
Model (on-chain name = Ollama tag)Fee per jobMax output tokens
llama3-8b0.02 LCAI2,048
llama3-70b0.15 LCAI4,096
gemma4:e2b0.02 LCAI2,048
glm-4.7-flash0.02 LCAI8,192
qwen3-coder-next0.05 LCAI16,384
gpt-oss:20b0.04 LCAI8,192
gpt-oss:120b0.20 LCAI8,192
qwen3-vl:8b0.02 LCAI4,096
qwen3-vl:30b0.08 LCAI4,096
tts-piper0.02 LCAI2,048
qwen3.6:27b0.06 LCAI8,192
deepseek-r1:32b0.08 LCAI8,192
mistral-small:24b0.05 LCAI4,096
gemma4:26b0.04 LCAI8,192
To serve several models, list them comma-separated in SUPPORTED_MODELS (for example SUPPORTED_MODELS=llama3-8b,gemma4:e2b), pull each into Ollama, and re-run register (or add-models for a worker that is already registered). Every model name must match the on-chain name exactly — a different string hashes to a different modelId and the registration reverts with ModelNotWhitelisted.

Rewards and fund handling

Per-job fees accrue in JobRegistry as the worker's balance: the worker releases each job once its dispute window has passed, and the fee is credited then. withdraw moves that balance to the worker's wallet ($WORKER_ADDR from Step 0). Run the same command with balance in place of withdraw to see the amount first:
CodeBASH
There is no separate payout address registered on-chain and no automatic forwarding to the funder wallet — withdrawn earnings accumulate on the worker key. Because the worker key lives inside the Docker container alongside its keystore password, you should not treat it as long-term storage. Sweep accumulated fees to your own designated wallet (typically the funder, a hardware wallet, or any cold-storage address you control) on whatever cadence matches your risk tolerance. Check the worker wallet's balance:
CodeBASH
Sweep funds from the worker to your designated wallet, leaving a gas buffer behind so the sidecar can keep paying for its claim, ack, blob and completion transactions:
CodeBASH
Do not drain the worker wallet to zero while the worker is registered and running — it needs gas to claim sessions and to ack and complete jobs. A worker that cannot pay for a transaction misses its deadline, and missed deadlines lead to slashing (see Slashing & Rehabilitation). Top up from your funder if it dips.
The staked 50,000 LCAI is held by WorkerRegistry, not by the worker wallet, and is only released when you deregister (see Deregister and withdraw stake).

Check registration status

preflight (Step 7) is the most complete check. For the bare on-chain registration flags:
CodeBASH

Move an existing worker to dispatcher-free mode

A worker started with the commands this guide gave before October 2026 runs the old gateway profile: WORKER_GATEWAY_URL is set and SORTITION_ENABLED is not. It still authenticates with the gateway and sends heartbeats, and its startup log says worker sidecar running (gateway mode). It is never given a job. The dispatcher that used to assign jobs is retired (since 19 October 2026 getDispatcherAddress() returns the burn address), and workers now claim sessions themselves. Your key, registration and stake stay as they are. To start earning again:
  1. Resolve the addresses in your shell, as in Resolve mainnet contract addresses. SESSION_MANAGER_ADDRESS is new.
  2. Pull the image again (Step 2). An image pulled before 16 October 2026 reads the chain from its first block when it starts in dispatcher-free mode.
  3. Write the env file (Step 4) with the keystore password you already use. Skip keygen: your encryption key is already registered.
  4. Top up gas. The worker now pays for its own claimSession transactions. Bring the worker address above 50 LCAI; preflight warns below that.
  5. Run preflight (Step 7) and fix whatever it reports.
  6. Replace the container. Stop and remove the old one:
    CodeBASH
    Then start the new container (Step 8). It begins 2,000 blocks behind the chain head, not at the jobs your worker was assigned in the past. If the old container was started without RELEASE_STATE_PATH=/data/release_state.json, its release ledger is inside the container and docker rm discards it; the new container rebuilds the ledger from the chain when it starts, as described under "I removed the container" in Drain & Graceful Exit.
  7. Check the log (Step 9). It must say worker sidecar running (sortition mode).
From now on stop the worker with docker stop -t 300. lightchain-worker drain no longer takes a worker out of rotation — see Stopping and draining.

Slashing and suspension

Workers that miss deadlines or lose disputes get their stake slashed. After three offenses the worker is automatically suspended for 7 days and must call WorkerRegistry.reinstate() to come back online. Mainnet's slash rates have been 0 since 13 September 2026 (the dispute rate since 10 September), and automatic suspension is off (suspensionThreshold 1,000,000), but offences are still recorded. Full mechanics, live rates, and the step-by-step recovery runbook are in Slashing & Rehabilitation.

Stopping and draining

In dispatcher-free mode a running worker keeps claiming sessions until the process stops, so drain alone does not take it out of rotation. Stop the process:
CodeBASH
On SIGTERM the worker stops claiming, starts no new job and sets its drain marker via the gateway. Jobs it is already running get up to SHUTDOWN_TIMEOUT (240 s in the env file above) to finish; keep docker stop -t above it. Later jobs in sessions you already claimed still target your worker and time out while it is offline. See Dispatcher-free Mode → Stopping, then follow Drain & Graceful Exit for the settlement and deregister sequence — deregister reverts with ActiveJobsExist until the dispute window has passed and the jobs are released.

Deregister and withdraw stake

Run this only after confirming activeJobsCount is 0 — otherwise it reverts with ActiveJobsExist. See Drain & Graceful Exit for the full flow.
CodeBASH
This removes your worker from the registry and returns your staked LCAI (minus any slashing penalties).

Stop the worker

Remove the container once deregister has succeeded, as in Drain & Graceful Exit.
CodeBASH

Troubleshooting

SymptomCause / fix
Log says worker sidecar running (gateway mode) and no job ever arrivesThe worker runs the old gateway profile — follow Move an existing worker to dispatcher-free mode
SESSION_MANAGER_ADDRESS is required on startSORTITION_ENABLED=true without the address — re-run the resolve step and fix the env file
authenticate with worker-gateway: … 403 worker not registered on-chainRegistration missing or on a different address — re-run Step 6, check keystore path/password
gateway stream dial failed … retryIn …Gateway unreachable — outbound WSS blocked? The worker retries with backoff; jobs still settle on-chain
Registered but never claimsRun preflight. Usual causes: model mismatch (keccak256(SUPPORTED_MODELS entry) must equal a whitelisted, enabled ID that you have added), suspension (getOffenseCount / isWorkerSuspended on WorkerRegistry), or simply other workers winning the draws
session watcher pass failed or job watcher pass failed with an error that names a cursorA file in sortition-state is empty, or the container user (uid 1000) cannot write there. Delete the empty file (that watcher starts 2,000 blocks behind the chain head again) or run sudo chown -R 1000 ~/lightchain-worker/keys/sortition-state
Frames rejected / connection closed by gatewayYou're streaming for a session you don't own (should never happen with an unmodified worker) — the gateway closes after 3 violations; the worker reconnects automatically

Mainnet contract addresses

ContractAddress
WorkerRegistry0x0000000000000000000000000000000000001002 (predeploy)
AIConfigResolve from WorkerRegistry — see Resolve mainnet contract addresses
JobRegistryResolve from WorkerRegistry — see Resolve mainnet contract addresses
SessionManager0xfFFCed75eF4Cdae0Cc33A27Bb46531B957a9f4e7 — or resolve from AIConfig.getSessionManagerAddress(), see Resolve mainnet contract addresses
FeePool0x0000000000000000000000000000000000001004 (predeploy)
Full address book on the Mainnet Contract Addresses page.