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
Operating System
- Production: Linux — Ubuntu 22.04 LTS or newer (Debian, RHEL 9+ also supported)
- Development: macOS (Apple Silicon or Intel)
Network Ports
| Network | P2P (QUIC) | RPC (HTTP) | WebSocket | Signer |
|---|---|---|---|---|
| Testnet | 7001 |
8899 |
8900 |
9201 |
| Mainnet | 8001 |
9899 |
9900 |
9201 |
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
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
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)
$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.
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?
- It creates the chosen state directory if needed.
- It generates or reuses the validator identity in that directory.
- It stores signer material, peer cache, and chain state under the same path.
- It connects to
seed-01.moltchain.networkandseed-02.moltchain.network, andseed-03.moltchain.network. - It syncs the network, then resumes or starts validating from the preserved local state.
- If
--auto-update=applyis 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.
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:
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:
./lichen-start.sh testnet --bootstrap seed-01.moltchain.network:7001
Stop / Restart
# 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.
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
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:
molt init --output ~/.lichen/validator-keypair.json
New validator keypair generated
Identity: Mo1tVa1idAtoR...xYz789
Saved to: ~/.lichen/validator-keypair.json
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.
--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.
[validator]
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). |
[network]
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. |
[consensus]
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. |
[graduation]
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. |
--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.
[rpc]
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]
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). |
[monitoring]
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]
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. |
[performance]
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]
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. |
[watchdog]
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. |
--no-watchdog since systemd's
Restart=on-failure already provides external supervision.
--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
$ 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
- Bootstrap Grant (first 200 validators): 100,000 LICN
(
100,000,000,000,000spores) — new validators receive this at $0 cost from the treasury - Self-funded (validator 201+): Must provide your own 75,000 LICN minimum — immediately fully vested, no debt
- Vesting (standard): 50% of settled epoch rewards go to debt repayment, 50% liquid — this is the default split for all bootstrap validators
- Performance bonus (≥95% uptime): 75% of settled epoch rewards go to debt
repayment, 25% liquid — accelerates graduation by ~1.5× (via
PERFORMANCE_BONUS_BPS = 15000) - Time cap: All remaining debt is forgiven after 547 days (~18 months) regardless of repayment progress
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:
expected_blocks = (current_slot - start_slot) / num_active_validators
uptime_bps = min(10000, blocks_produced × 10000 / expected_blocks)
- Fair share: With N active validators, each one is expected to produce 1/N of total blocks. A validator that produces its full share has 100% uptime (10,000 bps).
- Threshold: ≥ 9,500 bps (95%) triggers the performance bonus (75/25 split).
- Edge cases: New validators with no slot history default to 0 bps. If fewer slots have elapsed than validators exist, validators with at least 1 block get benefit of the doubt (10,000 bps).
Anti-Sybil Protection
Each validator collects a machine fingerprint (SHA-256 hash of platform UUID + MAC address) at startup. This fingerprint is:
- Included in & signed with validator announcements
- Registered in the stake pool — one fingerprint per bootstrap validator
- Prevents running 50 validators on one machine to steal 50 bootstrap grants
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:
- Validator Coral: Each validator is a "coral head." Delegators attach stake to a validator, growing the reef.
- Epoch Activation: Validator-set changes settle at epoch boundaries (about 2 days on the live chain). Membership is applied by the protocol's epoch-transition rules, not a simple top-N stake leaderboard.
- Reward Distribution: Transaction-fee share is earned when your validator produces blocks, while staking inflation is distributed proportionally to validators and delegators when the epoch boundary settlement runs.
- Slashing: Double-signing or extended downtime results in a percentage of staked
LICN being burned. With
enable_slashing = true, evidence is gossiped via P2P and processed by theSlashingTracker.
Check Stake Status
$ 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 |
# 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:
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:
# 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.
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
# 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:
# 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:
- Creates a dedicated
lichensystem user/group (no login shell) - Creates directories:
/etc/lichen,/var/lib/lichen,/var/log/lichen - Copies binaries to
/usr/local/bin/ - Generates
/etc/lichen/env-testnetand/or/etc/lichen/env-mainnetwith correct port assignments - Installs per-network systemd services:
lichen-validator-testnetandlichen-validator-mainnet
Systemd Unit File
The service reads its port configuration from the environment file. Key flags match
lichen-start.sh:
[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
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
# 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.
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
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
# 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
~/.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)
# 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
$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)
# 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
%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:
- Environment variables:
LICHEN_ZK_SHIELD_VK_PATH,LICHEN_ZK_UNSHIELD_VK_PATH,LICHEN_ZK_TRANSFER_VK_PATH - Shared cache:
~/.lichen/zk/(Linux/macOS) or%LOCALAPPDATA%\Lichen\zk\(Windows) - 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
# 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
Troubleshooting
Port Conflicts
If the validator fails to start with "address already in use":
# 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:
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:
- Verify
seed_nodesare correct and reachable - Check that the P2P port is open in your firewall (
sudo ufw allow 7001/tcpfor testnet) - Ensure
chain_idmatches the network you're connecting to - Try increasing log level:
RUST_LOG=debug,lichen_p2p=trace
# 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:
# 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
# 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
#validators channel or open an issue on GitHub with your logs and config (redact
your keypair and admin token).