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, create a state directory, and run the validator with domain bootstrap peers and --auto-update=apply.

Linux x86_64

Shell
VERSION=$(curl -fsSL https://api.github.com/repos/lobstercove/moltchain/releases/latest | jq -r .tag_name)
curl -LO "https://github.com/lobstercove/moltchain/releases/download/${VERSION}/lichen-validator-linux-x86_64.tar.gz"
curl -LO "https://github.com/lobstercove/moltchain/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
mkdir -p "$HOME/.lichen/state-mainnet"
./lichen-validator \
    --network mainnet \
    --rpc-port 9899 \
    --ws-port 9900 \
    --p2p-port 8001 \
    --db-path "$HOME/.lichen/state-mainnet" \
    --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 \
    --auto-update=apply

macOS Apple Silicon

Shell
VERSION=$(curl -fsSL https://api.github.com/repos/lobstercove/moltchain/releases/latest | jq -r .tag_name)
curl -LO "https://github.com/lobstercove/moltchain/releases/download/${VERSION}/lichen-validator-darwin-aarch64.tar.gz"
curl -LO "https://github.com/lobstercove/moltchain/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
mkdir -p "$HOME/.lichen/state-mainnet"
./lichen-validator \
    --network mainnet \
    --rpc-port 9899 \
    --ws-port 9900 \
    --p2p-port 8001 \
    --db-path "$HOME/.lichen/state-mainnet" \
    --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 \
    --auto-update=apply

Windows x64 (PowerShell)

PowerShell
$version = (Invoke-RestMethod https://api.github.com/repos/lobstercove/moltchain/releases/latest).tag_name
Invoke-WebRequest -Uri "https://github.com/lobstercove/moltchain/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
.\lichen-validator.exe `
    --network mainnet `
    --rpc-port 9899 `
    --ws-port 9900 `
    --p2p-port 8001 `
    --db-path "$HOME\.lichen\state-mainnet" `
    --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 `
    --auto-update=apply

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

Shell
mkdir -p "$HOME/.lichen/state-mainnet"

mkdir -p "$HOME/.lichen/state-mainnet/home"

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" \
    --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 \
    --auto-update=apply

This is the fastest operational path for agents: binary, ports, state directory, bootstrap domains. If the state directory already exists, the validator resumes with the same identity and local chain state.

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 connects to seed-01.moltchain.network and seed-02.moltchain.network, and seed-03.moltchain.network.
  5. It syncs the network, then resumes or starts validating from the preserved local state.
  6. 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 P2P TLS identity and TOFU fingerprint 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_cert.der and node_key.der under the current process HOME, it keeps using that identity to avoid breaking established peers.

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?
Use seed-01.moltchain.network and seed-02.moltchain.network, and seed-03.moltchain.network for bootstrap peers. Domains let Lichen rotate seed infrastructure without forcing operators or agents to change commands or wait for a new binary.

Option B: Repo Workflow

If you are building from source or using the helper scripts, clone the repository and use the scripted workflow:

Shell
git clone https://github.com/lobstercove/moltchain.git
cd lichen
./lichen-start.sh testnet

The start script will automatically build the binary, create genesis (if this is the first validator), deploy all 27 smart contracts, seed AMM pools, and fund the insurance reserve. Everything is idempotent — run it again and it picks up where it left off.

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.

Joining an Existing Network

To join a network with existing validators, pass one or more bootstrap peers. Domains are preferred over raw IPs:

Shell
./lichen-start.sh testnet --bootstrap seed-01.moltchain.network:7001

Stop / Restart

Shell
# Stop
./lichen-stop.sh testnet

# Full reset (wipe all state and start fresh)
./reset-blockchain.sh testnet
./lichen-start.sh testnet

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/moltchain.git
cd lichen
cargo build --release

# 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 \
    --bootstrap-peers seed-01.moltchain.network:7001,seed-02.moltchain.network:7001,seed-03.moltchain.network:7001

# 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 \
    --bootstrap-peers seed-01.moltchain.network:7001,seed-02.moltchain.network: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 Ed25519 key is your node's on-chain identity. The start script does this automatically, but you can also do it manually:

Shell
molt 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.

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, --bootstrap, --bootstrap-peers, --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 Ed25519 keypair JSON file. Generate with molt 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).

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 [] Bootstrap peers. Testnet: ["seed-01.moltchain.network:7001", "seed-02.moltchain.network:7001", "seed-03.moltchain.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 127.0.0.1 for localhost-only access.
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
$ licn stake add 75000000000 --rpc-url https://testnet-rpc.moltchain.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 coral reef ecosystems:

Check Stake Status

Shell
$ licn stake status --rpc-url https://testnet-rpc.moltchain.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://localhost: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 http://localhost:8899 \
  -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=0.0.0.0:9201
      - LICHEN_ADMIN_TOKEN=${LICHEN_ADMIN_TOKEN:-}
    networks:
      - lichen
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost: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, use the provided setup script to install a systemd service per network.

Setup Script

Run the setup script as root. It accepts one or both network names:

Shell
# Build first
cargo build --release

# Setup for testnet only
sudo bash deploy/setup.sh testnet

# Setup for both networks on the same machine
sudo bash deploy/setup.sh testnet mainnet

The script performs:

Systemd Unit File

The service reads its port configuration from the environment file. Key flags match lichen-start.sh:

INI — lichen-validator.service
[Unit]
Description=Lichen Validator Node
Documentation=https://developers.moltchain.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 bundled ZK keys, 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/moltchain"

# 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"
    chmod +x "$BINARY"

    # Install bundled ZK verification keys to the shared cache
    ZK_SHARED="$HOME/.lichen/zk"
    if [ -d "zk" ]; then
        mkdir -p "$ZK_SHARED"
        cp zk/*.bin "$ZK_SHARED/"
        echo "$(date): ZK keys installed to $ZK_SHARED"
    fi
    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" \
    --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 \
    --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/zk/ ZK verification & proving keys
~/.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 = "v0.2.14"  # Update to latest
$Url = "https://github.com/lobstercove/moltchain/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", "$InstallDir\zk"

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

# Copy ZK keys if bundled
if (Test-Path "$InstallDir\bin\zk") {
    Copy-Item "$InstallDir\bin\zk\*" "$InstallDir\zk\"
}

# 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"
nssm set LichenValidator AppParameters "--network mainnet --p2p-port 8001 --rpc-port 9899 --ws-port 9900 --db-path $InstallDir\state-mainnet --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 --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 --bootstrap-peers seed-01.moltchain.network:8001,seed-02.moltchain.network:8001,seed-03.moltchain.network:8001 --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\bin\ Binary + ZK setup tool
%LOCALAPPDATA%\Lichen\state-mainnet\ Blockchain state DB
%LOCALAPPDATA%\Lichen\zk\ ZK verification & proving keys
%LOCALAPPDATA%\Lichen\logs\ Validator log files

ZK Verification Keys

The validator requires Groth16 ZK verification keys to process shielded transactions. These are pre-generated ceremony keys shipped in every release tarball under a zk/ directory.

On first run, the validator checks these locations in order:

  1. Environment variables: LICHEN_ZK_SHIELD_VK_PATH, LICHEN_ZK_UNSHIELD_VK_PATH, LICHEN_ZK_TRANSFER_VK_PATH
  2. Shared cache: ~/.lichen/zk/ (Linux/macOS) or %LOCALAPPDATA%\Lichen\zk\ (Windows)
  3. Bundled: zk/ directory next to the binary (auto-copied to shared cache)

If keys are missing, shielded transactions are unavailable but the validator continues normally for regular transactions.

Manual Installation

Shell
# Copy from the release tarball's zk/ directory
mkdir -p ~/.lichen/zk
cp /path/to/release/zk/*.bin ~/.lichen/zk/

# Verify keys (SHA-256 hashes must match across all validators)
sha256sum ~/.lichen/zk/vk_*.bin
# Expected:
# af980fb4...  vk_shield.bin
# e368eeaf...  vk_transfer.bin
# f39b6e2e...  vk_unshield.bin
Key Consistency
All validators in the network must use identical ZK verification keys. Keys generated from different ceremony runs will reject each other's proofs. Always use the canonical keys shipped in the official release tarball.

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
molt balance --rpc-url https://testnet-rpc.moltchain.network
molt stake status --rpc-url https://testnet-rpc.moltchain.network

Sync Failures

If your node can't sync with the network:

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

# Check current block height via RPC (testnet port 8899)
curl -s http://localhost:8899 \
  -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 http://localhost:8899 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' | jq

# Peer list
curl -s http://localhost:8899 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getClusterNodes"}' | jq

# Validator set
curl -s http://localhost:8899 \
  -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).