Codex · Run a node · Peer
RUN A NODE

Run a Peer

A peer is the base unit of the Liqua network: a booted genesis instance that finds other peers, opens an encrypted channel to them, and gossips transactions and blocks. Everything else — mining, validation — rides on top of this layer. This page gets one running and shows you how to drive it.

Where this runs

The networking node lives in liqua/node/ and has its own dependencies. All commands on this page are run from inside that folder unless noted. It's ours, not geth — same discv4 design, but Liqua-native: lnode:// URIs, JSON payloads, and a node's identity is its genesis instance (R6 · instance = auth). We build on audited crypto (@noble secp256k1 / keccak / ChaCha20) — we don't roll our own.

What a peer is

A node's identity is derived from a seed — the key of a (public, private) genesis instance:

a node = a booted genesis instance
  identity   priv = key of the (public, private) instance        (--seed)
  node-id    compressed secp256k1 pubkey            → lnode://<id>@host:port
  dht-key    keccak256(uncompressed pubkey)         → Kademlia distance metric

Peers are addressed by an lnode://<node-id>@host:port URI — the Liqua analog of an enode. The node-id is the public key; the sender of every packet is recovered from its signature, so no pubkey ever travels on the wire.

Install

cd liqua/node
npm install

Three dependencies, all audited: @noble/curves (secp256k1), @noble/hashes (keccak256), @noble/ciphers (ChaCha20-Poly1305). Node ≥18.

Boot a node · the GO start

start.mjs brings the whole stack up in one command — BOOT IS GENESIS (R7): GENESIS → IDENTITY → DISCOVERY → TRANSPORT → READY. The first node is the genesis seed; everyone else bootstraps from it.

# ① be the genesis seed node (others bootstrap from you)
node start.mjs --seed genesis --port 30303

# ② join from another terminal / machine, pointing at the seed
node start.mjs --seed me --port 30304 --bootnode lnode://<id>@127.0.0.1:30303

One --port serves both UDP discovery and TCP transport. The seed prints its own lnode:// URI at boot — copy that into the joiner's --bootnode. There's also an npm run go shortcut for the seed node.

Discovery-only mode

If you only want the peer-finding daemon (no transport / gossip), run liqd.mjs instead — the geth-like discv4 bootnode. Useful as a standalone, always-on rendezvous point:

node liqd.mjs --seed liqua-genesis-boot --port 30303
node liqd.mjs --seed alice --port 30304 --bootnode lnode://<id>@127.0.0.1:30303

Live dashboard

For a browser UI over a running peer — identity, chain head, connected peers, recent blocks, a live event log, and a PRODUCE BLOCK button — boot the dashboard server. It runs a full node (discovery + transport + gossip + chain + control) and serves the UI, because a browser can't reach the raw-TCP control plane directly:

node server.mjs --port 30303 --http 8040 --seed genesis
# dashboard → http://localhost:8040 · node udp/tcp :30303 · liqctl :31303

Open http://localhost:8040. State streams over SSE; the liqctl socket stays open alongside it. The Server Hub launches this as node · peer dashboard, and every route is in the API reference.

Fastest way to see a chain move

Boot one dashboard node and hit PRODUCE BLOCK a few times — height climbs and blocks stream in, no second machine needed. Then boot a second node pointed at the first (--bootnode) to watch discovery and sync go live.

Command-line flags

FlagMeaning
--port <n>UDP+TCP port (default 30303)
--host <ip>bind host (default 127.0.0.1)
--seed <s>genesis instance seed (hex32 or phrase) → this node's identity. Omit for a random identity.
--bootnode <uri>lnode://… to bootstrap from. Repeatable.
--genesis <file>a genesis.json carrying a bootnodes[] list
Your seed is your identity

The --seed derives the private key that is your node. Anyone with the seed can impersonate the node on the network. Use a real, secret seed for anything beyond localhost; the phrases above (genesis, alice) are for devnet demos only.

How peers find & trust each other

Four phases, in order. The first three are discovery; the fourth is the genesis gate that keeps your mesh on the same chain.

  1. Bootstrap

    PING the bootnodes from --bootnode / genesis.json.

  2. Bond — both directions

    PING ⇄ PONG in both directions before a peer is usable. This bidirectional endpoint-proof is anti-amplification and was the fix for the early "peers find the bootnode but not each other" bug.

  3. Learn the graph

    FINDNODE(target) → NEIGHBORS(k closest), then bond the new peers. Random-target refresh lookups keep the Kademlia routing table live.

  4. Genesis gate

    Over the encrypted channel, the first message is STATUS. A peer is accepted only if its chainId and genesis (the SOMA-256 root) match yours. Peers on a different genesis are rejected at the gate — the mesh agrees on genesis before block 1.

The wire packet is MAGIC('LIQD') · sig(64) · recid(1) · payloadJSON. Stale packets (20 s TTL) and bad signatures are dropped. Once bonded and gated, the transport is an authenticated encrypted TCP channel — secp256k1 ECDH → keccak key → ChaCha20-Poly1305 frames — over which tx and block messages gossip (deduped by hash, relayed onward).

Drive a running node · liqctl

A full node exposes an admin control plane on port + 1000 (line-JSON over TCP). liqctl is the operator's handle on it:

node liqctl.mjs --port 30303 status      # name · height · head · peers · miner
node liqctl.mjs --port 30303 head        # the current canonical head block
node liqctl.mjs --port 30303 chain       # chain summary
node liqctl.mjs --port 30303 peers       # discovered / connected / evm peers
node liqctl.mjs --port 30303 produce '{"n":1}'   # produce N blocks (operator-driven)
node liqctl.mjs --port 30303 stop        # graceful shutdown

Pass --port as the node's UDP/TCP port — liqctl adds 1000 to reach the control socket (you can also target it directly with --control 31303). A status reads like:

  liqctl · status   → control:31303
  name      node-30303  lnode://02af…@127.0.0.1:30303
  height    #6  head 8f2c1a09e4b7…
  stateRoot 0x37d309f2091ec80a…
  peers     2 evm / 3 tcp / 4 discovered
  miner     idle

Block sync is by re-execution: a node validates a gossiped block by deterministically re-running produceBlock and checking the SOMA root matches. A late-joining peer catches up with getblocks / blocks range requests, then converges on the same head and state root as everyone else.

Prove it · the mesh demos

Two self-contained gates that spin up multiple peers in one process and exit 0 only on success:

npm run demo       # bootnode + 3 peers · exits 0 when the full discovery mesh forms
npm run evmdemo    # bootnode + 2 peers form an ENCRYPTED genesis mesh, gossip a tx,
                   # and REJECT an intruder booted on the wrong genesis
Verify your setup

Run npm run evmdemo first — a clean exit 0 proves discovery, the encrypted transport, the genesis gate, and tx gossip all work on your machine before you wire up real peers.

Files in node/

FileRole
identity.mjsnode key / id / dht-key · lnode:// · sign & recover
kademlia.mjsXOR-distance k-bucket routing table
wire.mjssigned packet encode/decode (PING / PONG / FINDNODE / NEIGHBORS)
discover.mjsthe UDP daemon — bonding, lookup, refresh
genesis-id.mjsthe chain's genesis id (SOMA root) peers must agree on
transport.mjsencrypted TCP channel — ECDH + ChaCha20-Poly1305
protocol.mjsEVM sub-protocol — STATUS genesis-gate + tx / block gossip
node.mjs · full.mjsLiquaNode / FullNode = discovery + transport + protocol + chain
control.mjs · liqctl.mjsadmin control plane (port + 1000) + its CLI
liqd.mjs · start.mjsdiscovery-only CLI · full-node GO boot
demo.mjs · evmdemo.mjs · sync-demo.mjsthe mesh / EVM / late-join sync test gates

Honest limits

Devnet · draft

Next: with peers talking, you can produce blocks or stake and validate. Every endpoint touched here is catalogued in the API reference.

LIQUA · CODEX · Run a Peer · part of Liqua Chain · 7SLF STUDIOS