Testnet Setup
Testnet Setup
[!WARNING] Testnet only This page documents operator procedures for the XE test network, not for mainnet. It assumes SSH access to the test network's own nodes, and a data wipe destroys the whole chain — something only possible before mainnet exists. Nothing here applies to a production network.
Procedure for wiping and setting up the testnet from scratch. This covers node deployment, genesis, and treasury distribution into multisig wallets.
[!NOTE] The wipe is scripted The steps below are the manual form. In practice a full reset is driven by
testnet/wipe.shin thexeprotocol/scriptsrepo, which stops all nodes, wipes ledger data, regenerates the genesis with a fresh random network ID, builds, deploys, and restarts in one pass. Prefer the script; use this page to understand what it does.
Prerequisites
- SSH access to all 5 nodes (3 bootstrap as root, 2 providers as ubuntu)
- The Go toolchain matching the core repo's
go.mod(Go 1.25+) for building binaries and running setup scripts - The genesis treasury seed (stored offline)
Node inventory
| Role | Host | SSH | Process | API |
|---|---|---|---|---|
| Bootstrap | ldn.test.network | root@ldn.test.network | pm2 | https://ldn.core.test.network |
| Bootstrap | ffm.test.network | root@ffm.test.network | pm2 | https://ffm.core.test.network |
| Bootstrap | nyc.test.network | root@nyc.test.network | pm2 | https://nyc.core.test.network |
| Provider | 189.1.171.51 | ubuntu@189.1.171.51 | systemd | — |
| Provider | 67.213.117.123 | ubuntu@67.213.117.123 | systemd | — |
These are the five hosts the core repo's deploy workflow targets. The data directory on all of them is /var/lib/xe-node.
Step 1: Build
From a checkout of xeprotocol/core:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build \
-ldflags "-X main.version=$(git rev-parse --short HEAD)" \
-o /tmp/xe ./cmd/xe/[!IMPORTANT] The genesis is baked into the binary Both the ledger genesis (
core/genesis.json) and the state chain genesis (statechain/genesis.json) are//go:embed'd. Regenerate the state chain genesis with thegen-genesisscript before building if the network needs a freshsys.network_idor new timekeeper keys — it takes the three bootstrap node key seeds, see Configuration.
Step 2: Stop all nodes
# Bootstrap nodes (pm2)
for host in ldn.test.network ffm.test.network nyc.test.network; do
ssh root@$host "pm2 stop xe-node"
done
# Provider nodes (systemd)
ssh ubuntu@189.1.171.51 "sudo systemctl stop xe-node"
ssh ubuntu@67.213.117.123 "sudo systemctl stop xe-node"Step 3: Deploy binary and wipe data
# Bootstrap nodes
for host in ldn.test.network ffm.test.network nyc.test.network; do
scp /tmp/xe root@$host:/usr/local/bin/xe-node
ssh root@$host "rm -rf /var/lib/xe-node/ledger"
done
# Provider nodes
for host in 189.1.171.51 67.213.117.123; do
scp /tmp/xe ubuntu@$host:/tmp/xe-node
ssh ubuntu@$host "sudo cp /tmp/xe-node /usr/local/bin/xe-node && \
sudo chmod +x /usr/local/bin/xe-node && \
sudo rm -rf /var/lib/xe-node/ledger"
done[!WARNING] Data wipe deletes libp2p keys If you also delete
/var/lib/xe-node/host.key, new peer IDs will be generated on startup and you must update all dial configurations. Only delete theledger/subdirectory to preserve peer IDs.
Step 4: Start nodes
# Bootstrap nodes
for host in ldn.test.network ffm.test.network nyc.test.network; do
ssh root@$host "pm2 start xe-node"
done
# Provider nodes
ssh ubuntu@189.1.171.51 "sudo systemctl start xe-node"
ssh ubuntu@67.213.117.123 "sudo systemctl start xe-node"Step 5: Verify genesis
Each node auto-applies the embedded ledger genesis block on first boot, minting the full XE supply — core.GenesisSupply, 42,000,000 XE (4.2×10¹³ µXE) — to a single genesis account.
for api in ldn.core.test.network ffm.core.test.network nyc.core.test.network; do
echo -n "$api: "
curl -s "https://$api/frontiers" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(len(d), 'accounts')"
doneAll nodes should show 1 account (the genesis treasury).
Step 6: Treasury distribution
The genesis block mints to a single-key account. The setup-treasury script moves part of that supply into four 2-of-3 multisig wallets. The amounts below are the literal on-chain values the script sends, which are micro-units (1 XE = 1,000,000 µXE):
| Wallet | Amount sent | Purpose |
|---|---|---|
| Treasury-A | 50,000,000 µXE (50 XE) | Primary reserve |
| Treasury-B | 30,000,000 µXE (30 XE) | Operations |
| Treasury-C | 10,000,000 µXE (10 XE) | Grants / ecosystem |
| Treasury-D | 10,000,000 µXE (10 XE) | Emergency reserve |
[!WARNING] The allocations have not been rescaled for micro-units The script's constants (
50_000_000and friends) are passed straight into the blockamountfield, so they distribute 100 XE in total, not 100,000,000 XE — and the script's own console output labels them "XE". Check and adjust the constants before running it against a network where the allocation actually matters.
go run ./scripts/setup-treasury <genesis-seed-hex>The script:
- Opens 4 multisig accounts (
multisig_open, 2-of-3 threshold) - Sends XE from genesis to each wallet
- Receives on each multisig wallet (1-of-N)
- Verifies all balances
- Prints key material for offline storage
Keys are deterministically derived from the genesis seed, so rerunning after a testnet wipe produces the same addresses.
[!DANGER] Save key material The script prints private seeds for all 12 multisig keys. Save these offline immediately. They are not stored anywhere else.
Optional: custom API endpoint
go run ./scripts/setup-treasury <genesis-seed-hex> https://ffm.core.test.networkStep 7: Verify
Check the explorer to confirm:
- Genesis account balance dropped by the total distributed (it keeps the remainder)
- Four multisig accounts exist with correct balances
- Each multisig account shows "multisig 2 of 3" badge
- Blocks propagated to all nodes (check frontiers)
# Quick verification
for api in ldn.core.test.network ffm.core.test.network nyc.core.test.network; do
echo "$api:"
curl -s "https://$api/frontiers" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(f' {len(d)} accounts')"
doneShould show 5 accounts on each node (1 genesis + 4 treasury wallets).
Token model summary
After setup, the network has:
| Token | Initial supply | Source |
|---|---|---|
| XE | 42,000,000 XE (core.GenesisSupply) | The ledger genesis block, part of it then distributed to the treasury multisig wallets |
| XUSD | 0 | Created only by mint blocks from an authorised sys.minter wallet |
- XE has no self-mint path at all. The only new XE after genesis is provider emission at
lease_settle; XE leaves circulation permanently viaburnblocks. - XUSD is minted exclusively by wallets registered under the state chain's
sys.minterkey, usingmintblocks (xe mint <amount>, orPOST /blocks/mint). Minters then distribute XUSD by ordinary send → receive. There is no permissionless claim block. - Voting weight is delegated XE balance. XUSD confers no consensus weight.
[!WARNING] Testnet funding is not self-service
xe faucetis the testnet command for requesting XUSD, but it calls a standalone faucet service (XE_FAUCET, defaulthttps://faucet.test.network) that is not deployed — the host does not resolve, so the command fails before it reaches any service. Bootstrap funding therefore has to come from the minter wallet directly.
Peer ID recovery
If peer IDs change (full data wipe including host.key), get new multiaddrs from logs:
for host in ldn.test.network ffm.test.network nyc.test.network; do
echo -n "$host: "
ssh root@$host "pm2 logs xe-node --lines 200 --nostream --no-color 2>/dev/null" \
| grep "Listening on: /ip4/" | grep -v "127.0.0.1\|172.17" | head -1 \
| grep -oP '/ip4/.+'
doneThen update PM2 configs (bootstrap) and systemd unit files (providers) with the new -dial addresses.