Validator Guide

Run a Lichen validator node to participate in consensus, produce blocks, and earn staking rewards. This guide covers hardware requirements, installation, configuration, staking, monitoring, and production deployment.

Requirements

Hardware

CPU
4+ cores (8+ recommended)
RAM
16 GB minimum
Storage
500 GB NVMe SSD
Network
100 Mbps symmetric

Operating System

Network Ports

Network P2P (QUIC) RPC (HTTP) WebSocket Signer
Testnet 7001 8899 8900 9201
Mainnet 8001 9899 9900 9201
Firewall
The P2P port (7001 testnet / 8001 mainnet) must be reachable from the public internet. RPC and WebSocket ports should be restricted to trusted IPs or kept behind a reverse proxy in production.

Installation

Option A: Binary Quick Start (Recommended for Agents)

If the machine already has a lichen-validator binary from a release bundle or prior build, you can start a validator immediately without cloning the repository.

The intended agent workflow is: download the latest signed release artifact for the host platform, extract it, keep the bundled zk-prove, lichen-genesis, lichen, and contracts/ bundle beside the validator, copy the bundled seeds.json into a writable state directory, and run the validator with --auto-update=apply.

Linux x86_64

Shell
VERSION=$(curl -fsSL https://api.github.com/repos/lobstercove/lichen/releases/latest | jq -r .tag_name)
curl -LO "https://github.com/lobstercove/lichen/releases/download/${VERSION}/lichen-validator-linux-x86_64.tar.gz"
curl -LO "https://github.com/lobstercove/lichen/releases/download/${VERSION}/SHA256SUMS"
grep 'lichen-validator-linux-x86_64.tar.gz' SHA256SUMS | sha256sum -c -
tar xzf lichen-validator-linux-x86_64.tar.gz --strip-components=1
chmod +x lichen-validator lichen-genesis lichen zk-prove
mkdir -p "$HOME/.lichen/state-mainnet"
cp seeds.json "$HOME/.lichen/state-mainnet/seeds.json"
export LICHEN_KEYPAIR_PASSWORD='set-a-long-random-secret-before-first-start'
./lichen-validator \
    --network mainnet \
    --rpc-port 9899 \
    --ws-port 9900 \
    --p2p-port 8001 \
    --db-path "$HOME/.lichen/state-mainnet" \
    --auto-update=apply

macOS Apple Silicon

Shell
VERSION=$(curl -fsSL https://api.github.com/repos/lobstercove/lichen/releases/latest | jq -r .tag_name)
curl -LO "https://github.com/lobstercove/lichen/releases/download/${VERSION}/lichen-validator-darwin-aarch64.tar.gz"
curl -LO "https://github.com/lobstercove/lichen/releases/download/${VERSION}/SHA256SUMS"
grep 'lichen-validator-darwin-aarch64.tar.gz' SHA256SUMS | shasum -a 256 -c -
tar xzf lichen-validator-darwin-aarch64.tar.gz --strip-components=1
chmod +x lichen-validator lichen-genesis lichen zk-prove
mkdir -p "$HOME/.lichen/state-mainnet"
cp seeds.json "$HOME/.lichen/state-mainnet/seeds.json"
export LICHEN_KEYPAIR_PASSWORD='set-a-long-random-secret-before-first-start'
./lichen-validator \
    --network mainnet \
    --rpc-port 9899 \
    --ws-port 9900 \
    --p2p-port 8001 \
    --db-path "$HOME/.lichen/state-mainnet" \
    --auto-update=apply

Windows x64 (PowerShell)

PowerShell
$version = (Invoke-RestMethod https://api.github.com/repos/lobstercove/lichen/releases/latest).tag_name
Invoke-WebRequest -Uri "https://github.com/lobstercove/lichen/releases/download/$version/lichen-validator-windows-x86_64.tar.gz" -OutFile "lichen-validator-windows-x86_64.tar.gz"
tar -xzf .\lichen-validator-windows-x86_64.tar.gz --strip-components=1
New-Item -ItemType Directory -Force -Path "$HOME\.lichen\state-mainnet" | Out-Null
Copy-Item .\seeds.json "$HOME\.lichen\state-mainnet\seeds.json" -Force
$env:LICHEN_KEYPAIR_PASSWORD = 'set-a-long-random-secret-before-first-start'
.\lichen-validator.exe `
    --network mainnet `
    --rpc-port 9899 `
    --ws-port 9900 `
    --p2p-port 8001 `
    --db-path "$HOME\.lichen\state-mainnet" `
    --auto-update=apply

If a release predates Windows packaging, use the source-build workflow for Windows until a newer release is published.

Production Key Password
Outside explicit local development, set LICHEN_KEYPAIR_PASSWORD before first start and on every restart. Validator, treasury, genesis-primary, and signer keypair files now use the shared encrypted-at-rest format, and production starts refuse plaintext keypair files.
Shell
mkdir -p "$HOME/.lichen/state-mainnet/home"
cp seeds.json "$HOME/.lichen/state-mainnet/seeds.json"
export LICHEN_KEYPAIR_PASSWORD='set-a-long-random-secret-before-first-start'

env HOME="$HOME/.lichen/state-mainnet/home" \
lichen-validator \
    --network mainnet \
    --rpc-port 9899 \
    --ws-port 9900 \
    --p2p-port 8001 \
    --db-path "$HOME/.lichen/state-mainnet" \
    --auto-update=apply

This is the fastest operational path for agents: validator binary, bundled zk-prove, ports, state directory, and bundled seed file. If the state directory already exists, the validator resumes with the same identity and local chain state. In apply mode, auto-update also refreshes {db-path}/seeds.json from newer release archives.

What happens when the validator starts?

  1. It creates the chosen state directory if needed.
  2. It generates or reuses the validator identity in that directory.
  3. It stores signer material, peer cache, and chain state under the same path.
  4. It loads seeds.json from {db-path}, /etc/lichen, or the current directory, then uses the listed seed RPC endpoint to fetch and persist the authoritative genesis.json when the state directory is empty.
  5. It imports the canonical opcode-41 genesis state bundle from block 0, verifies it against the block state root, then syncs and replays later blocks from peers.
  6. It does not copy RocksDB state, genesis wallet files, genesis keys, peer cache, consensus WAL, custody keys, or faucet keys from existing validators.
  7. It submits validator registration once synced, then starts validating after registration lands and the node is eligible.
  8. If --auto-update=apply is enabled, it checks GitHub Releases in the background, stages a newer signed binary, and requests a restart to apply it.

Typical files under the state path include validator identity files, signer material, RocksDB chain state, known-peers.json, and home/.lichen/ for the persistent native PQ P2P identity and peer-identity TOFU store. Preserving that directory preserves the validator identity and local progress.

For new or state-scoped installs, the validator prefers --db-path/home for those P2P runtime files. If an existing deployment already has node_identity.json under the current process HOME, it keeps using that identity instead of generating a new node address.

For unattended updates, run the validator under a restart supervisor or service manager. Auto-update handles download and staging; the supervisor handles relaunch.

Why domains instead of IPs?
The bundled seeds.json uses seed-01.lichen.network, seed-02.lichen.network, and seed-03.lichen.network so Lichen can rotate seed infrastructure through normal release and auto-update flows without forcing operators or agents to rewrite start commands.

Option B: Repo Workflow

The supported repo-local validator path is the 3-validator launcher. It prepares genesis on validator 1, starts the local cluster, and generates signed metadata once the chain is healthy.

Shell
git clone https://github.com/lobstercove/lichen.git
cd lichen
cargo build --release
export LICHEN_KEYPAIR_PASSWORD='local-e2e-secret'
bash scripts/start-local-3validators.sh start-reset

If you need custody, faucet, and post-genesis bootstrap for browser or service testing, extend the local validator path with the full-stack wrapper:

Shell
export LICHEN_KEYPAIR_PASSWORD='local-e2e-secret'
./scripts/start-local-stack.sh testnet
./scripts/status-local-stack.sh testnet

Port Assignments

Network RPC WebSocket P2P Signer
Testnet 8899 8900 7001 9201
Mainnet 9899 9900 8001 9201

You can run both testnet and mainnet on the same machine — they use different ports and separate data directories.

Local Status / Stop

Use the same launcher family for status checks, clean stops, and full resets:

Shell
# Validator-only cluster
bash scripts/start-local-3validators.sh status
bash scripts/start-local-3validators.sh stop
bash scripts/start-local-3validators.sh start-reset

# Full local stack
./scripts/status-local-stack.sh testnet
./scripts/stop-local-stack.sh testnet
./scripts/start-local-stack.sh testnet

Stop / Restart

Shell
# Clean local reset
bash scripts/start-local-3validators.sh stop
bash scripts/start-local-3validators.sh start-reset

Option C: Build from Source (Manual)

If you prefer manual control, build the binary and run it directly. Requires Rust 1.75+ stable toolchain.

Shell
git clone https://github.com/lobstercove/lichen.git
cd lichen
cargo build --release
export LICHEN_KEYPAIR_PASSWORD='set-a-long-random-secret-before-first-start'

# Start — the built-in supervisor auto-restarts on stalls/crashes
./target/release/lichen-validator \
    --network testnet \
    --rpc-port 8899 \
    --ws-port 8900 \
    --p2p-port 7001 \
    --db-path ./data/state-testnet

# Disable the supervisor (e.g. when using systemd Restart=on-failure)
./target/release/lichen-validator --no-watchdog \
    --network testnet --rpc-port 8899 --p2p-port 7001

Option D: Docker

Shell
docker-compose up validator

See the Docker Deployment section below for full details.

Initialize Validator Identity

Generate a validator keypair. This native PQ key is your node's on-chain identity. The start script does this automatically, but you can also do it manually:

Shell
lichen init --output ~/.lichen/validator-keypair.json

 New validator keypair generated
  Identity: Mo1tVa1idAtoR...xYz789
  Saved to: ~/.lichen/validator-keypair.json
Back Up Your Keypair
Your validator keypair controls your staked funds. Back it up securely and never expose it in public repositories. Keep the matching LICHEN_KEYPAIR_PASSWORD in your operator secret store; without it, encrypted runtime key files cannot be reopened after restart or restore.

Configuration

Lichen validators are currently configured via CLI flags when starting the binary. The section labels below (for example [network] and [rpc]) are conceptual groupings for readability, not literal TOML sections consumed by the validator today. A real config.toml input format is planned for a future release.

Note
These settings map to command-line flags: --network, --p2p-port, --rpc-port, --ws-port, --db-path, --keypair, --cold-store, --archive-mode, --admin-token, --no-watchdog, --watchdog-timeout, --max-restarts, --dev-mode, --import-key. They are documentation aliases, not direct TOML keys parsed by the current binary.

Core validator identity and data paths.

Key Type Default Description
keypair_path String ~/.lichen/validator-keypair.json Path to the validator native PQ keypair JSON file. Generate with lichen init.
data_dir String ~/.lichen/data Directory for blockchain state, ledger, and snapshots.
enable_validation Boolean true Set to false to run as a full-node observer (no block production).

Optional archival settings for cold block retention and historical account lookups.

Flag Type Default Description
--cold-store <path> String Attach a secondary RocksDB for archival block, transaction, and index retention. The validator migrates data older than the hot retention window into this path every 5 minutes.
--archive-mode Boolean false Persist historical account snapshots so getAccountAtSlot can answer archive queries. Pair with --cold-store on archival nodes to keep hot-state growth bounded.

Peer-to-peer networking and gossip settings.

Key Type Default Description
p2p_port Integer 7001 QUIC transport listening port for P2P gossip and block propagation. Testnet default: 7001, Mainnet: 8001.
rpc_port Integer 8899 JSON-RPC HTTP server port. Testnet default: 8899, Mainnet: 9899.
seed_nodes Array [] Seed-file peer list loaded from seeds.json. Testnet: ["seed-01.lichen.network:7001", "seed-02.lichen.network:7001", "seed-03.lichen.network:7001"]
enable_p2p Boolean true Enable P2P networking. Disable for local-only testing.
gossip_interval Integer 10 Gossip protocol interval in seconds.
cleanup_timeout Integer 300 Seconds before cleaning up stale peer connections.

BFT consensus and staking parameters.

Key Type Default Description
min_validator_stake Integer 75000000000000 Minimum stake in spores (1 LICN = 1,000,000,000 spores). Mainnet uses 75,000 LICN (75K); testnet genesis uses 75 LICN to keep validator onboarding accessible. Bootstrap validators receive a 100,000 LICN grant on the contributory-stake path.
slot_duration_ms Integer 400 Base slot duration in milliseconds. Actual block time is adaptive: ~800ms for heartbeat blocks, ~200ms with transactions (Tendermint BFT).
enable_slashing Boolean true Enable slashing for double-signing and other protocol violations.

Validator graduation and anti-fraud settings (CLI flags).

Flag Type Default Description
--dev-mode Boolean false Skip machine fingerprint uniqueness check. Allows multiple validators per machine (for local testing). Uses SHA-256(pubkey) as fingerprint. Blocked on mainnet.
--import-key <path> String Import an existing keypair from another machine. Copies <path> to the validator's data directory. The validator resumes with its existing stake, debt, and progress. Fingerprint auto-updates on next announcement.
Machine Migration
To move a validator to a new machine: (1) Stop the validator on the old machine. (2) Copy the keypair file to the new machine. (3) Start with --import-key /path/to/keypair.json. All progress (debt, earned rewards, blocks produced) is tied to the keypair, not the machine. The machine fingerprint auto-updates with a 2-day cooldown between migrations.

JSON-RPC server configuration.

Key Type Default Description
enable_rpc Boolean true Enable the JSON-RPC server.
bind_address String "0.0.0.0" Bind address. Use a private interface or reverse proxy when access should stay internal.
enable_cors Boolean true Enable Cross-Origin Resource Sharing for browser clients.
max_connections Integer 1000 Maximum concurrent RPC connections.

Logging verbosity and output targets.

Key Type Default Description
level String "info" Log level. One of: trace, debug, info, warn, error.
log_to_file Boolean false Write logs to a file in addition to stdout.
log_file_path String ~/.lichen/validator.log Path to the log file (when log_to_file = true).
log_format String "text" Output format. "json" for structured logging (recommended for production).

Prometheus metrics and health checks.

Key Type Default Description
enable_metrics Boolean true Enable the Prometheus-compatible metrics HTTP endpoint.
metrics_port Integer 9100 Port for the Prometheus metrics server.
enable_health_check Boolean true Enable the /health HTTP endpoint for load balancers.

Genesis block and chain identity.

Key Type Default Description
genesis_path String "./genesis.json" Path to the genesis configuration file. Required on first startup.
chain_id String "lichen-testnet-1" Chain identifier. Must match the genesis file exactly.

Tuning options for throughput and resource usage.

Key Type Default Description
worker_threads Integer 0 Tokio worker threads. 0 = auto-detect CPU core count.
optimize_block_production Boolean true Enable block production optimizations (parallel tx execution, pipelining).
tx_batch_size Integer 1000 Maximum transactions per block batch.

Security hardening and access control.

Key Type Default Description
check_firewall Boolean true Check that required ports are accessible on startup.
require_encryption Boolean false Require TLS for all RPC connections.
allowed_rpc_methods Array [] Whitelist of RPC methods. Empty = all methods enabled.
rpc_rate_limit Integer 100 Maximum RPC requests per second per client.
admin_token String "" Bearer token for admin RPC endpoints. Generate with openssl rand -hex 32. Empty = admin endpoints disabled.

Built-in self-healing supervisor. The validator automatically monitors itself for stalls (deadlocks, resource exhaustion) and restarts when needed. No external scripts or cron jobs required — just run the binary.

Key CLI Flag Default Description
watchdog_timeout --watchdog-timeout 120 Seconds of inactivity (no blocks produced or received) before the watchdog triggers a restart.
max_restarts --max-restarts 50 Maximum number of automatic restarts before the supervisor gives up and exits. Set to 0 for unlimited.
no_watchdog --no-watchdog false Disable the built-in supervisor entirely. Use this when running under systemd or another process manager that handles restarts.
How It Works
The validator binary runs as a supervisor + worker pair. The supervisor launches the validator as a child process, monitors its health, and restarts it with exponential backoff (1s → 2s → 4s → … → 30s cap) on crashes or stall exits. Clean shutdowns (Ctrl+C, SIGTERM) stop both processes gracefully. When using systemd, pass --no-watchdog since systemd's Restart=on-failure already provides external supervision.
Tip — Minimal Config
You don't need every setting. A minimal configuration for testnet only needs --network testnet, --p2p-port, and --rpc-port flags -- everything else uses sane defaults.

Staking

Validators must stake LICN tokens to participate in consensus. Stake weight determines your share of block production slots and epoch-settled staking rewards. Transaction-fee share is earned as you produce blocks; inflationary staking rewards settle at epoch boundaries.

Stake via CLI

Shell
$ lichen stake add 75000000000 --rpc-url https://testnet-rpc.lichen.network

 Staking 75 LICN for validator Mo1tVa1idAtoR...xYz789

  Transaction: 5bN2...kQrT
  Bootstrap:   100,000.000000000 LICN (granted, #47 of 200)
    Debt:        100,000.000000000 LICN (repaid from settled epoch rewards: 50% standard, 75% at ≥95% uptime)
  Uptime:      98.5% (≥ 95% → 75/25 debt/liquid split)
  Status:      Finalized (slot 1,204,817)

 Validator staked! Your node will enter the active set next epoch.

Minimum Stake

Graduation System

Bootstrap validators go through a graduation process from Bootstrapping to FullyVested:

Path Reward Split Condition Effect
Standard 50% debt / 50% liquid Bootstrap debt reaches 0 Status → FullyVested, 100% of settled rewards become liquid
Performance Bonus 75% debt / 25% liquid ≥95% uptime (9,500+ basis points) Same graduation, reached ~1.5× sooner
Time Cap N/A 547 days since validator started (~118M slots) Remaining debt forgiven, immediate graduation

Uptime Calculation

Uptime is calculated in basis points (0–10,000 = 0%–100%) based on actual block production vs. expected share:

Formula
expected_blocks = (current_slot - start_slot) / num_active_validators
uptime_bps      = min(10000, blocks_produced × 10000 / expected_blocks)

Anti-Sybil Protection

Each validator collects a machine fingerprint (SHA-256 hash of platform UUID + MAC address) at startup. This fingerprint is:

Rule: One machine = one bootstrap grant. Self-funded validators (201+) are not restricted by fingerprint.

Liquid Staking Mechanism

Lichen uses a delegated proof-of-stake liquid staking model inspired by moss ecosystems:

Check Stake Status

Shell
$ lichen stake status --rpc-url https://testnet-rpc.lichen.network

  Identity:    Mo1tVa1idAtoR...xYz789
  Stake:       100,000.000000000 LICN (bootstrap)
  Delegated:   450.000000000 LICN
  Total:       100,450.000000000 LICN
  Active:      Yes (rank #7 of 21)
  Commission:  10%
  Last Vote:   slot 1,205,142

Monitoring

Prometheus Metrics

When [monitoring] enable_metrics = true, the validator exposes a Prometheus-compatible endpoint at http://<host>:9100/metrics.

Key Metrics

Metric Type Description
lichen_block_height Gauge Current finalized block height
lichen_tps Gauge Transactions per second (rolling 10s window)
lichen_peer_count Gauge Number of connected P2P peers
lichen_mempool_size Gauge Pending transactions in the mempool
lichen_slot_duration_ms Histogram Actual slot processing time
lichen_vote_count Counter Total votes cast by this validator
lichen_blocks_produced Counter Blocks produced by this validator
Shell
# Fetch metrics
curl -s http://127.0.0.1:9100/metrics | grep lichen_

# Example output
lichen_block_height 1205247
lichen_tps 842
lichen_peer_count 18
lichen_mempool_size 127

Health Check

The RPC server provides a health check via the getHealth JSON-RPC method:

Shell
curl -s https://testnet-rpc.lichen.network \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}'

# Response: {"jsonrpc":"2.0","result":"ok","id":1}

Log Levels

Control verbosity with the RUST_LOG environment variable or the [logging] level config key:

Shell
# Per-module granularity via RUST_LOG
RUST_LOG=info,lichen_p2p=debug,lichen_rpc=warn lichen-validator

# Or set in config.toml
# [logging]
# level = "debug"
# log_format = "json"

Docker Deployment

The recommended production deployment uses Docker Compose. The provided docker-compose.yml runs the validator, faucet, and optional explorer.

YAML — docker-compose.yml
version: "3.8"

services:
  validator:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: lichen-validator
    restart: unless-stopped
    ports:
      - "7001:7001"   # P2P (testnet)
      - "8899:8899"   # RPC (testnet)
      - "8900:8900"   # WebSocket (testnet)
      - "9100:9100"   # Metrics
    volumes:
      - lichen-data:/var/lib/lichen
    environment:
      - RUST_LOG=info
      - LICHEN_NETWORK=testnet
      - LICHEN_RPC_PORT=8899
      - LICHEN_WS_PORT=8900
      - LICHEN_P2P_PORT=7001
            - LICHEN_SIGNER_BIND=127.0.0.1:9201
      - LICHEN_ADMIN_TOKEN=${LICHEN_ADMIN_TOKEN:-}
    networks:
      - lichen
    healthcheck:
            test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8899/ -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getHealth\"}' -H 'Content-Type: application/json' || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

volumes:
  lichen-data:
    driver: local

networks:
  lichen:
    driver: bridge

Volume Mounts

Mount Purpose
lichen-data:/var/lib/lichen Persistent blockchain state — survives container restarts and upgrades.
./config.toml:/etc/lichen/config.toml:ro Optional configuration file (future). Currently all settings are passed as CLI flags / environment variables in docker-compose.

Running

Shell
# Start in foreground (see logs)
docker-compose up validator

# Start in background
docker-compose up -d validator

# View logs
docker-compose logs -f validator

# Stop
docker-compose down

Health Check

Docker will automatically restart the validator if the getHealth RPC call fails 3 consecutive times. The start_period: 15s gives the node time to initialize before checks begin.

Systemd Deployment

For bare-metal Linux servers, install the release binary under a systemd service. Managed-host provisioning automation is intentionally outside the public repo.

Systemd Unit

The public repo ships the validator binary and service templates. Operators can adapt the unit file to their own host paths and secret-management system:

Shell
# Example: install the release binary, then install a systemd unit
sudo install -m 755 lichen-validator /usr/local/bin/lichen-validator
sudo install -m 644 deploy/lichen-validator.service /etc/systemd/system/lichen-validator.service
sudo systemctl daemon-reload
sudo systemctl enable --now lichen-validator

A production unit should perform the same high-level setup:

Systemd Unit File

The service reads its port configuration from the environment file. Key flags map directly to the validator binary:

INI — lichen-validator.service
[Unit]
Description=Lichen Validator Node
Documentation=https://developers.lichen.network/validator.html
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=lichen
Group=lichen
# --no-watchdog: systemd handles restarts via Restart=on-failure
ExecStart=/usr/local/bin/lichen-validator --no-watchdog \
    --network ${LICHEN_NETWORK} \
    --rpc-port ${LICHEN_RPC_PORT} \
    --ws-port ${LICHEN_WS_PORT} \
    --p2p-port ${LICHEN_P2P_PORT} \
    --db-path /var/lib/lichen/state-${LICHEN_NETWORK}

Restart=on-failure
RestartSec=10
LimitNOFILE=65536

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/lichen /var/log/lichen
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

# Environment
Environment=RUST_LOG=info
EnvironmentFile=/etc/lichen/env

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=lichen-validator

[Install]
WantedBy=multi-user.target
Security Hardening
The unit file includes Linux security features: NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, and kernel protection directives. The process runs as the unprivileged lichen user with write access only to /var/lib/lichen and /var/log/lichen.

Service Management

Shell
# Start the testnet validator
sudo systemctl start lichen-validator-testnet

# Enable on boot
sudo systemctl enable lichen-validator-testnet

# Check status
sudo systemctl status lichen-validator-testnet

# Follow logs
journalctl -u lichen-validator-testnet -f

# Restart after config change
sudo systemctl restart lichen-validator-testnet

# Mainnet uses the same commands with -mainnet suffix
sudo systemctl start lichen-validator-mainnet

macOS LaunchAgent

On macOS, use a LaunchAgent to run the validator as a persistent background service that starts automatically on login. This approach downloads the latest release binary and keeps it up to date.

Create the Wrapper Script

The wrapper script downloads the latest release binary (using semver sorting, not GitHub's latest endpoint), verifies its checksum, installs the bundled seeds.json, keeps the bundled zk-prove, lichen-genesis, lichen, and contracts/ artifacts, and then execs the validator.

Shell
mkdir -p ~/.lichen/bin ~/.lichen/state-mainnet ~/.lichen/logs

cat > ~/.lichen/bin/lichen-service.sh << 'WRAPPER'
#!/bin/bash
set -euo pipefail

INSTALL_DIR="$HOME/.lichen/bin"
BINARY="$INSTALL_DIR/lichen-validator"
STATE_DIR="$HOME/.lichen/state-mainnet"
LOG_DIR="$HOME/.lichen/logs"
REPO="lobstercove/lichen"

# Set ASSET based on architecture
ARCH=$(uname -m)
case "$ARCH" in
    arm64|aarch64) ASSET="lichen-validator-darwin-aarch64.tar.gz" ;;
    x86_64)        ASSET="lichen-validator-darwin-x86_64.tar.gz" ;;
    *)             echo "Unsupported arch: $ARCH"; exit 1 ;;
esac

mkdir -p "$INSTALL_DIR" "$STATE_DIR" "$LOG_DIR"

# Download binary if not present
if [ ! -x "$BINARY" ]; then
    echo "$(date): Downloading latest release..."
    TMPDIR=$(mktemp -d)
    trap 'rm -rf "$TMPDIR"' EXIT

    # Fetch highest semver tag (not GitHub "latest" which sorts by publish date)
    VERSION=$(/usr/bin/curl -fsSL "https://api.github.com/repos/$REPO/releases" \
        | /usr/bin/python3 -c "
import sys, json
releases = [r for r in json.load(sys.stdin) if not r['draft'] and not r['prerelease']]
releases.sort(key=lambda r: [int(x) for x in r['tag_name'].lstrip('v').split('.')], reverse=True)
print(releases[0]['tag_name'])
")
    echo "$(date): Latest version: $VERSION"

    /usr/bin/curl -fSL -o "$TMPDIR/$ASSET" \
        "https://github.com/$REPO/releases/download/${VERSION}/${ASSET}"
    /usr/bin/curl -fSL -o "$TMPDIR/SHA256SUMS" \
        "https://github.com/$REPO/releases/download/${VERSION}/SHA256SUMS"

    cd "$TMPDIR"
    grep "$ASSET" SHA256SUMS | shasum -a 256 -c -
    tar xzf "$ASSET" --strip-components=1
    mv lichen-validator "$BINARY"
    mv zk-prove "$INSTALL_DIR/zk-prove"
    mv lichen-genesis "$INSTALL_DIR/lichen-genesis"
    mv lichen "$INSTALL_DIR/lichen"
    cp -R contracts "$INSTALL_DIR/contracts"
    chmod +x "$BINARY" "$INSTALL_DIR/zk-prove" "$INSTALL_DIR/lichen-genesis" "$INSTALL_DIR/lichen"
    cp seeds.json "$STATE_DIR/seeds.json"

    cd /

    echo "$(date): Installed lichen-validator $VERSION"
    trap - EXIT
    rm -rf "$TMPDIR"
fi

# Start the validator
echo "$(date): Starting lichen-validator..."
exec "$BINARY" \
    --network mainnet \
    --p2p-port 8001 \
    --rpc-port 9899 \
    --ws-port 9900 \
    --db-path "$STATE_DIR" \
    --auto-update=apply
WRAPPER

chmod +x ~/.lichen/bin/lichen-service.sh

Install the LaunchAgent

Shell
cat > ~/Library/LaunchAgents/network.lichen.validator.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>network.lichen.validator</string>

    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>$HOME/.lichen/bin/lichen-service.sh</string>
    </array>

    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <true/>

    <key>ThrottleInterval</key>
    <integer>15</integer>

    <key>StandardOutPath</key>
    <string>$HOME/.lichen/logs/validator.log</string>

    <key>StandardErrorPath</key>
    <string>$HOME/.lichen/logs/validator.log</string>

    <key>EnvironmentVariables</key>
    <dict>
        <key>RUST_LOG</key>
        <string>info</string>
    </dict>
</dict>
</plist>
EOF

Manage the Service

Shell
# Load and start (runs immediately and on every login)
launchctl load ~/Library/LaunchAgents/network.lichen.validator.plist

# Stop the validator
launchctl unload ~/Library/LaunchAgents/network.lichen.validator.plist

# Restart (stop then start)
launchctl unload ~/Library/LaunchAgents/network.lichen.validator.plist
sleep 2
launchctl load ~/Library/LaunchAgents/network.lichen.validator.plist

# Follow logs
tail -f ~/.lichen/logs/validator.log

# Check if running
launchctl list | grep lichen

# Force re-download of the binary (e.g. after a new release)
launchctl unload ~/Library/LaunchAgents/network.lichen.validator.plist
rm ~/.lichen/bin/lichen-validator
launchctl load ~/Library/LaunchAgents/network.lichen.validator.plist
File Layout
~/.lichen/bin/lichen-validator Binary
~/.lichen/bin/lichen-service.sh Wrapper script
~/.lichen/state-mainnet/ Blockchain state DB
~/.lichen/logs/validator.log Stdout/stderr log
~/Library/LaunchAgents/network.lichen.validator.plist LaunchAgent plist

Windows Service

On Windows, use NSSM (Non-Sucking Service Manager) to run the validator as a Windows service. Alternatively, use a PowerShell scheduled task.

Option A: NSSM (Recommended)

PowerShell (Admin)
# 1. Download and extract the release
$Version = (Invoke-RestMethod https://api.github.com/repos/lobstercove/lichen/releases/latest).tag_name
$Url = "https://github.com/lobstercove/lichen/releases/download/$Version/lichen-validator-windows-x86_64.tar.gz"
$InstallDir = "$env:LOCALAPPDATA\Lichen"
New-Item -ItemType Directory -Force -Path "$InstallDir\bin", "$InstallDir\state-mainnet", "$InstallDir\logs"

Invoke-WebRequest -Uri $Url -OutFile "$env:TEMP\lichen.tar.gz"
tar -xzf "$env:TEMP\lichen.tar.gz" -C "$InstallDir\bin" --strip-components=1

# 2. Install NSSM (https://nssm.cc/download)
# Extract nssm.exe to a directory in your PATH

# 3. Create the service
nssm install LichenValidator "$InstallDir\bin\lichen-validator.exe"
Copy-Item "$InstallDir\bin\seeds.json" "$InstallDir\state-mainnet\seeds.json" -Force
nssm set LichenValidator AppParameters "--network mainnet --p2p-port 8001 --rpc-port 9899 --ws-port 9900 --db-path $InstallDir\state-mainnet --auto-update=apply"
nssm set LichenValidator AppDirectory "$InstallDir"
nssm set LichenValidator AppStdout "$InstallDir\logs\validator.log"
nssm set LichenValidator AppStderr "$InstallDir\logs\validator.log"
nssm set LichenValidator AppEnvironmentExtra "RUST_LOG=info" "USERPROFILE=$InstallDir"
nssm set LichenValidator Start SERVICE_AUTO_START
nssm set LichenValidator AppRestartDelay 10000

# 4. Start
nssm start LichenValidator

Option B: PowerShell Scheduled Task

PowerShell (Admin)
$InstallDir = "$env:LOCALAPPDATA\Lichen"
$Action = New-ScheduledTaskAction `
    -Execute "$InstallDir\bin\lichen-validator.exe" `
                    -Argument "--network mainnet --p2p-port 8001 --rpc-port 9899 --ws-port 9900 --db-path $InstallDir\state-mainnet --auto-update=apply" `
    -WorkingDirectory "$InstallDir"
$Trigger = New-ScheduledTaskTrigger -AtStartup
$Settings = New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Seconds 15)

Register-ScheduledTask -TaskName "LichenValidator" `
    -Action $Action -Trigger $Trigger -Settings $Settings `
    -RunLevel Highest -User "SYSTEM"

# Start immediately
Start-ScheduledTask -TaskName "LichenValidator"

Service Management (Windows)

PowerShell (Admin)
# NSSM commands
nssm status LichenValidator
nssm start LichenValidator
nssm stop LichenValidator
nssm restart LichenValidator

# View logs
Get-Content "$env:LOCALAPPDATA\Lichen\logs\validator.log" -Tail 50 -Wait

# Remove service
nssm stop LichenValidator
nssm remove LichenValidator confirm
Windows File Layout
%LOCALAPPDATA%\Lichen\state-mainnet\ Blockchain state DB
%LOCALAPPDATA%\Lichen\logs\ Validator log files

Shielded Runtime

The live shielded verifier consumes native Plonky3 proof envelopes directly. Validators do not load proving-key or verification-key bundles at startup, and no shared zk/ cache is required for shielded verification.

Operationally, this means a validator installation only needs the standard binaries, contract artifacts, and state/log directories. The shielded pool RPC surface now reports the active scheme via zkScheme / zk_scheme instead of publishing verification-key hashes.

Troubleshooting

Port Conflicts

If the validator fails to start with "address already in use":

Shell
# Find what's using the port (testnet example)
sudo lsof -i :7001   # P2P
sudo lsof -i :8899   # RPC

# The port scheme is fixed per network — see the Installation section above

Insufficient Stake

Your node won't produce blocks without meeting the minimum stake. Check your balance and stake:

Shell
lichen balance --rpc-url https://testnet-rpc.lichen.network
lichen stake status --rpc-url https://testnet-rpc.lichen.network

Sync Failures

If your node can't sync with the network:

Shell
# Test connectivity to seed nodes
nc -zv seed-01.lichen.network 7001

# Check current block height via RPC (testnet port 8899)
curl -s https://testnet-rpc.lichen.network \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getBlockHeight"}' | jq .result

Disk Full

The ledger grows over time. Monitor disk usage and plan for growth:

Shell
# Check data directory size
du -sh /var/lib/lichen/

# Check available disk
df -h /var/lib/lichen/

# Set up monitoring alert (example with Prometheus Alertmanager)
# Alert when disk usage > 80%

Debug Commands

Shell
# Full node status dump (testnet RPC port)
curl -s https://testnet-rpc.lichen.network \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' | jq

# Peer list
curl -s https://testnet-rpc.lichen.network \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getClusterNodes"}' | jq

# Validator set
curl -s https://testnet-rpc.lichen.network \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getValidators"}' | jq

# Check systemd logs for crash details
journalctl -u lichen-validator-testnet --since "1 hour ago" --no-pager

# Run validator with maximum verbosity (testnet)
RUST_LOG=trace lichen-validator --network testnet --rpc-port 8899 --ws-port 8900 --p2p-port 7001 --db-path /var/lib/lichen/state-testnet 2>&1 | head -200
Need Help?
Join the Lichen Discord #validators channel or open an issue on GitHub with your logs and config (redact your keypair and admin token).