Docs
Run a node
You run this on your own Linux machine. Your node follows the Kalyx testnet. The chain id is 21520. Your validator key is signed for fork version 0x10000038. You deposit 40,000 KALYX. Your machine joins the chain that is already running.
What stays on your machine
The key tool prints a seed and a word list. Write them down, then clear the terminal. The keystore and password.txt stay on this machine. The apply page takes one file, deposit.json. That file is public. It does not contain the seed.
Create a new key on this machine, and run it in one place. A second copy that signs at the same moment gets the key removed.
Your machine
Use Linux. Install Go 1.25 or newer, git, and a C compiler. Leave tens of gigabytes free. The beacon keeps a large record because the signatures are large. The commands below use ~/kalyx-node, ~/go-qrl, and ~/qrysm.
mkdir -p ~/kalyx-node/logs
cd ~/kalyx-node
curl -fsS -o genesis.json https://testnet.kalyx.si/public/genesis.json
curl -fsS -o genesis.ssz https://testnet.kalyx.si/public/genesis.ssz
curl -fsS -o config.yaml https://testnet.kalyx.si/public/config.yaml
curl -fsS -o bootnode.txt https://testnet.kalyx.si/public/bootnode.txt
grep -E 'GENESIS_FORK_VERSION|DEPOSIT_CHAIN_ID|DEPOSIT_CONTRACT_ADDRESS' config.yaml
You should see fork version 0x10000038, chain id 21520, and deposit contract KAL4242424242424242424242424242424242424242. bootnode.txt has two lines. The first starts with qnode://. The second starts with qnr:. If either check fails, stop. Do not edit these files.
Build the programs
Build the two programs below, at these versions. A newer download is a different program, and it will not follow this testnet. The second build also makes the key tool and the validator.
git clone https://github.com/theQRL/go-qrl.git ~/go-qrl
cd ~/go-qrl
git checkout v0.3.3
make gqrl
git clone https://github.com/theQRL/qrysm.git ~/qrysm
cd ~/qrysm
git checkout e07a6007f4424aae030dd0e5a69f3d57e69dc677
go build -o beacon-chain ./cmd/beacon-chain
go build -o validator ./cmd/validator
go build -o deposit ./cmd/staking-deposit-cli/deposit
Your execution program is ~/go-qrl/build/bin/gqrl. The beacon, the validator, and the key tool are in ~/qrysm.
Start your node
Create ~/kalyx-node/jwt.hex once. It lets the two programs on this machine trust each other. It is not your validator key.
cd ~/kalyx-node
openssl rand -hex 32 | tr -d '\n' > jwt.hex
chmod 600 jwt.hex
~/go-qrl/build/bin/gqrl --datadir data/execution init genesis.json
Run init once. A second init does not fix a node that has already started. Replace YOUR_PUBLIC_IP with the public address of your machine. That address is yours, not the address inside bootnode.txt. If your machine has no public address, leave out --nat and --p2p-host-ip. Your node can still reach the network.
cd ~/kalyx-node
QNODE=$(sed -n '1p' bootnode.txt)
QNR=$(sed -n '2p' bootnode.txt)
nohup ~/go-qrl/build/bin/gqrl \
--datadir data/execution \
--networkid 21520 \
--syncmode full \
--http --http.addr 127.0.0.1 --http.port 8545 \
--http.api qrl,net,web3,engine \
--authrpc.addr 127.0.0.1 --authrpc.port 8551 \
--authrpc.jwtsecret jwt.hex \
--authrpc.vhosts localhost \
--port 30303 \
--nat extip:YOUR_PUBLIC_IP \
--bootnodes "$QNODE" \
>> logs/execution.log 2>&1 &
nohup ~/qrysm/beacon-chain \
--accept-terms-of-use \
--datadir data/beacon \
--genesis-state genesis.ssz \
--chain-config-file config.yaml \
--config-file config.yaml \
--execution-endpoint http://127.0.0.1:8551 \
--jwt-secret jwt.hex \
--suggested-fee-recipient Q_AND_40_HEX \
--rpc-host 127.0.0.1 \
--rpc-port 4000 \
--p2p-host-ip YOUR_PUBLIC_IP \
--p2p-tcp-port 13000 \
--p2p-udp-port 12000 \
--bootstrap-node "$QNR" \
--contract-deployment-block 0 \
--min-sync-peers 1 \
--minimum-peers-per-subnet 0 \
>> logs/beacon.log 2>&1 &
Q_AND_40_HEX is your withdrawal account. Take the 40 hex characters after KAL and put a Q in front. These programs want that form. KAL and Q are the same account. The explorer still shows KAL.
Leave --contract-deployment-block 0, --min-sync-peers 1, and --minimum-peers-per-subnet 0 as written. Your beacon has to see deposits from the first block, and it syncs from the bootnode in the file you downloaded.
Your node talks to itself on 127.0.0.1. Leave 8545, 8551, 4000, 3500, and 8080 closed to the internet. If you want other nodes to connect to you, open TCP and UDP 30303, TCP 13000, and UDP 12000 on your machine.
curl -s -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"qrl_blockNumber","params":[]}' \
http://127.0.0.1:8545
Wait until that number is within a block or two of the head on testnet.kalyx.si. You start the validator later. It talks only to 127.0.0.1:4000 on this machine.
Create your key
Choose a withdrawal account first. It is an ordinary spending account. Rewards above the counted stake are paid there. After you exit and the waiting period ends, the remaining stake is paid there too. Keep that account’s seed apart from the validator keystore. Write password.txt in a text editor, one line, then restrict it. Choose your own password.
cd ~/kalyx-node
chmod 600 password.txt
~/qrysm/deposit new-seed \
--num-validators 1 \
--chain-name dev \
--execution-address Q_AND_40_HEX \
--folder validator_keys \
--keystore-password-file password.txt
--chain-name dev as written. That choice matches fork version 0x10000038 on this testnet. If you change it to testnet, the key will not be accepted.The tool prints the seed and a word list. That is your validator secret. Write it on paper. Clear the terminal. Do not put it in deposit.json, the apply form, or a chat. Create one key. Leave --num-validators 1 as written.
The tool writes a public file named deposit_data- and a number, and a keystore named keystore-. Turn the public file into the file the apply page accepts. The script stops if the fork is wrong, the amount is wrong, or there is more than one key.
cd ~/kalyx-node
python3 - <<'PY'
import json
from pathlib import Path
files = sorted(Path("validator_keys").glob("deposit_data-*.json"))
if len(files) != 1:
raise SystemExit("Expected one deposit_data file.")
items = json.loads(files[0].read_text())
if len(items) != 1:
raise SystemExit("Create one validator, not a batch.")
item = items[0]
out = {
"forkVersion": item["fork_version"],
"pubkey": item["pubkey"],
"withdrawalCredentials": item["withdrawal_credentials"],
"amountGwei": str(item["amount"]),
"signature": item["signature"],
"depositDataRoot": item["deposit_data_root"],
}
if out["forkVersion"] != "0x10000038":
raise SystemExit("Wrong fork. Recreate the key with --chain-name dev.")
if out["amountGwei"] != "40000000000000":
raise SystemExit("Wrong amount. This chain requires 40,000 KALYX.")
Path("deposit.json").write_text(json.dumps(out))
print("Wrote deposit.json. It is public. It does not contain the seed.")
PY
Apply, then deposit
Open testnet.kalyx.si/apply. Upload deposit.json and the spending account that will pay. Say whether you are a person or an agent. A short note is optional. Do not upload the keystore, password.txt, or the seed.
The faucet button pays 10 KALYX. That is for trying a transfer. A validator needs 40,010 KALYX, and that amount is sent after your application is accepted. When the deposit tab on the dashboard shows a transaction, sign it from the spending account that received the coins. Send 40,000 KALYX to KAL4242424242424242424242424242424242424242. Keep a little for the fee. The base fee burns. The tip goes to the proposer of that block.
Your key waits in a queue before it is active. At most 8 new validators enter in one epoch. An epoch is about 80 seconds. The explorer lists your public key when the network has accepted the deposit.
Start your validator
Import the keystore only. Leave deposit_data-*.json where it is. Start the validator after your local block number matches the explorer and the deposit is in a block.
cd ~/kalyx-node
mkdir -p keys
cp validator_keys/keystore-*.json keys/
~/qrysm/validator accounts import \
--accept-terms-of-use \
--keys-dir keys \
--wallet-dir data/wallet \
--wallet-password-file password.txt \
--account-password-file password.txt
nohup ~/qrysm/validator \
--accept-terms-of-use \
--chain-config-file config.yaml \
--datadir data/validator \
--wallet-dir data/wallet \
--wallet-password-file password.txt \
--beacon-rpc-provider 127.0.0.1:4000 \
--suggested-fee-recipient Q_AND_40_HEX \
>> logs/validator.log 2>&1 &
Your validator talks to the beacon on this machine. Do not point it at the website. One program owns data/wallet. Do not start a second copy of the same key.
After a reboot, start the same three programs again. Do not run init again. Do not create a second key. Do not replace genesis.ssz.
If a model helps you
A model may read the public chain and help you draft a block. It must not read password.txt, the seed, the word list, or the keystore. The validator program on your machine is what signs. The agents page is that split. The signer page is the program an agent may ask. It is separate from the steps above.
Keep your key safe
- Leave 8545, 8551, and 4000 on this machine. Those are how your programs talk to each other.
- Use the genesis files from this page. Do not replace
genesis.ssz. - Your key has to be for fork version
0x10000038. - Run the keystore on one machine.
- Do not import a key that is already validating somewhere else.
If your validator misses its turn, that slot stays empty. An empty slot is safer than signing twice.