Run a node

xrs-node 0.1.1Rustports 4000 · 56001 · 50008

One binary, three ports, one keypair file.

Build

git clone https://github.com/ZZachWWins/xeriscointestnet && cd xeriscointestnet cargo build --release
xrs-nodethe node
keypair_genwrites keypair.json
walletbuilds transactions, prints the curl to submit them
rustc1.94.1 in Xeris’s last remediation report; no pin
cc, perl, makevendored OpenSSL, bundled SQLite

Keys

# keypair.json; --mainnet writes keypair_mainnet.json RUST_LOG=info ./target/release/keypair_gen
FileContentMode
keypair.jsonJSON array of 64 bytes, the Ed25519 secret key0600
dilithium3_sk.binML-DSA-65 secret key, 4,032 bytes; written at first start in the working directory; self-tested on every start0600
dilithium3_pk.binpublic key, 1,952 bytesumask

keypair.json loads with XerisKeypair.fromJsonFile.

Start

FlagDefaultMeaning
--validator <seed-ip> <keypair.json> <data-dir>—join through a seed; a missing keypair is generated
--bootstrap <keypair.json> <data-dir>—first node of a chain; needs the treasury key
--mainnetoffchain id xeris-mainnet-v1; roster required; <seed-ip> ignored
--genesisoffwrite a genesis descriptor and exit
--p2p-port4000P2P
--rpc-port56001RPC
--explorer-port50008Explorer API
--genesis-pre-stake—deprecated, ignored
RUST_LOG=info ./target/release/xrs-node \ --validator 138.197.116.81 keypair.json ./data # mainnet: three P2P addresses export XRS_FEDERATED_PRODUCERS='<pk1>@<ip1>:4000,<pk2>@<ip2>:4000,<pk3>@<ip3>:4000' RUST_LOG=info ./target/release/xrs-node --mainnet \ --validator 138.197.116.81 keypair_mainnet.json ./data

Environment

XRS_FEDERATED_PRODUCERS3 pubkey@ipv4:port entries, the P2P addresses; required with --mainnet
XRS_LOCAL_ONLY=1P2P and RPC bind 127.0.0.1, no DNS seeds; the explorer still binds 0.0.0.0
XRS_SEED_PEERSip[:port] comma-separated, ≤ 200, trusted; testnet only
XRS_MAX_DEEP_SYNC_BYTESdeep-sync disk budget; default 512 GiB, minimum 1 GiB
RUST_LOGinfo shows the banner and block log; unset prints errors only

Ports

PortServiceProtocolBind
4000P2PTCP, Ed25519 challenge-response0.0.0.0, or 127.0.0.1 local-only
56001RPCHTTP/1.1, no TLS0.0.0.0, or 127.0.0.1 local-only
50008Explorer APIHTTP/1.1, no TLS0.0.0.0 always

Terminate TLS on a reverse proxy. The write limiter keys on the TCP peer: one proxy, one 30-per-minute budget.

Files

<data-dir>/ledger.datblocks, one JSON line each, fsynced before apply
<data-dir>/tx_receipts.sqliteexplorer history, schema 2; rebuilt by replay when the version differs; required on every network
<data-dir>/ledger.dat.recovered-txs.sqliterecovery journal; created on every start
<data-dir>/price_history_<pool>.json≤ 10,080 snapshots per pool, one per 100 blocks
<data-dir>/ledger_snapshot.json, contract_state.jsonwritten, never loaded
<cwd>/dilithium3_*.binthe PQ keys
Two nodes started from one working directory share one PQ key.

Peers

--validator <seed-ip>dialled first; port 4000 when none is given
XRS_SEED_PEERStrusted
DNS seed listone HTTPS URL fetched at start, 5 s timeout, unless local-only
built-in listempty
mainnetthe roster replaces every source above
addressIPv4, optional port; hostnames refused

Stake and produce

1generate keypair.json
2start with --validator; the log reads needs 1000 to mine
3fund the address: ≥ 1,000 XRS plus fees; no faucet
4wallet stake → POST /stake
5wallet pq-register → POST /pq-register, before the first block
# each prints the curl to submit; run it ./target/release/wallet stake keypair.json 1000 ./target/release/wallet pq-register keypair.json
import { readFileSync } from 'node:fs'; import { XerisClient, XerisKeypair } from 'xeris-sdk'; const client = new XerisClient('http://127.0.0.1', { rpcPort: 56001, explorerPort: 50008 }); const kp = XerisKeypair.fromJsonFile('keypair.json'); const pk = readFileSync('dilithium3_pk.bin'); await client.stakeXrs(kp, 1000); // POST /stake await client.pqKeyRegister(kp, pk, 'dilithium3', 3); // POST /pq-register
produce blocksstake ≥ 1,000 XRS; counts from the next block
first Stakeresulting stake ≥ 1,000 XRS; /stake does not pre-check it
staking rewardsstake ≥ 100 XRS; 7 % a year, every 900 blocks, to the liquid balance
unstakeall, or keep ≥ 1,000 XRS; partial ≥ 1 XRS, POST /unstake
unbonding151,200 slots (7 days); slashable; released without a claim, GET /account/{address}/unstaking
attestationsstake ≥ 100 XRS; 0.01 XRS, one per 10 blocks, within 200 slots, hash of 32 bytes, ValidatorAttestation
slashing10 % of stake per proven double-sign; 5 % of that to the reporter, the rest burned, SlashReport
PQ keyone per node; blocks need it from slot 2
mainnet Stakeroster keys only; others: Public self-staking is closed during federated beta
Mainnet: the node key must be in the 3-entry XRS_FEDERATED_PRODUCERS roster or startup panics.

Health

curl -s http://127.0.0.1:56001/health curl -s http://127.0.0.1:50008/v2/stats curl -s http://127.0.0.1:56001/stake/<address>
SignalRouteSDK
upGET /healthgetHealth()
height, TPS estimateGET /v2/statsgetStats()
stake, isValidator, blocksProposedGET /stake/{address}getStakeInfo(address)
PQ keyGET /pq/keys/{address}getPqKey(address)
block reward, fees, halvingGET /network/economicsgetNetworkEconomics()
peersthe node log—
metricsnone—