From 75098a6035efec79b4147fc274b8005e3c521df7 Mon Sep 17 00:00:00 2001 From: samuel lemikan <142722758+atomdbc@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:42:55 +0100 Subject: [PATCH 1/2] docs: add beginner validator guide, correct stale mainnet status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a step-by-step guide for launching a mainnet validator, aimed at operators with no prior blockchain experience, and fixes several errors in the existing validator path. New: docs/validators/validator-walkthrough.md — covers install through create-validator, with a glossary, the operator/consensus key split, and a troubleshooting table drawn from a real end-to-end mainnet setup. Corrections: - Mainnet has been live since 2026-06-25, but six pages still advertised a "Q3 2026" target, contradicting join-mainnet.md and mainnet-endpoints.md. - create-validator documented `--gas auto --gas-adjustment 1.3`, which can under-estimate and fail with `out of gas` while still charging the fee, since the fee is deducted before execution. Uses a flat `--gas` instead and shows how to verify inclusion with `query tx` rather than trusting the broadcast response. - Balance checks nested `$(safrochaind keys show validator -a)`, which only resolves against the currently-active keyring and breaks across machines and backends. Uses a literal address. - Documents that `edit-validator` takes `--new-moniker`, not `--moniker`. Field notes behind this change also surfaced two peer-discovery dead ends (a stale hardcoded IP, and the public RPC hostname whose P2P socket is intentionally closed behind Cloudflare). Both are avoided by using the official foundation seeds, so the guide points there and explains why. Co-Authored-By: Claude Opus 4.8 --- docs/intro.md | 2 +- docs/resources/faq.md | 7 +- docs/run-a-node/overview.md | 4 +- docs/validators/become-a-validator.md | 38 +- docs/validators/overview.md | 5 +- docs/validators/validator-walkthrough.md | 712 +++++++++++++++++++++++ sidebars.ts | 1 + 7 files changed, 758 insertions(+), 11 deletions(-) create mode 100644 docs/validators/validator-walkthrough.md diff --git a/docs/intro.md b/docs/intro.md index adba300d6..87e598c68 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -36,7 +36,7 @@ predictable fees, and IBC rails to the wider Cosmos ecosystem. - **Token**: **SAF** - **Supply**: **1,000,000,000 SAF** (fixed) - **Denom**: `usaf` (`1 SAF = 1_000_000 usaf`) -- **Mainnet target**: **Q3 2026** +- **Mainnet**: **live** — `safrochain-1`, genesis `2026-06-25T10:00:00Z` ## External links diff --git a/docs/resources/faq.md b/docs/resources/faq.md index 15aa11b6f..fa745db58 100644 --- a/docs/resources/faq.md +++ b/docs/resources/faq.md @@ -11,7 +11,10 @@ Safrochain is an Africa-first Layer 1 blockchain built on the Cosmos SDK. It pri SAF is the native token used for fees, staking, and governance. Total fixed supply is **1 billion SAF**. -## When is mainnet launching? +## Is mainnet live? -Mainnet is planned for **Q3 2026**. +Yes. `safrochain-1` has been producing blocks since its genesis time of +**2026-06-25T10:00:00Z**. See [Mainnet endpoints](../networks/mainnet-endpoints) +for RPC, REST, and seed details, or [Join mainnet](../run-a-node/join-mainnet) +to run a node. diff --git a/docs/run-a-node/overview.md b/docs/run-a-node/overview.md index 83218991f..711fb99f3 100644 --- a/docs/run-a-node/overview.md +++ b/docs/run-a-node/overview.md @@ -20,7 +20,7 @@ This section covers running a Safrochain full node with **`safrochaind`**. - Run a **local single-node sandbox** for development. - Join the **public testnet** (live) for end-to-end testing. -- Join the **mainnet** (Q3 2026) as a full or pruned node. +- Join the **mainnet** (live) as a full or pruned node. - Become a **validator** and help secure the network. ## Decision matrix @@ -39,7 +39,7 @@ This section covers running a Safrochain full node with **`safrochaind`**. 2. [Hardware requirements](./hardware): sizing per role (validator, sentry, RPC, archive). 3. [Local devnet](./local-devnet): single-node `safro-devnet-1` on localhost. 4. [Join testnet](./join-testnet): live `safro-testnet-1`. -5. [Join mainnet](./join-mainnet): `safrochain-1` (opens Q3 2026). +5. [Join mainnet](./join-mainnet): live `safrochain-1`. 6. [Snapshots](./snapshots): fast catch-up via tarballs. 7. [Statesync](./statesync): cryptographic light-client sync. 8. [Upgrades](./upgrades): cosmovisor flow for chain upgrades. diff --git a/docs/validators/become-a-validator.md b/docs/validators/become-a-validator.md index 6a5ac6334..12589b0da 100644 --- a/docs/validators/become-a-validator.md +++ b/docs/validators/become-a-validator.md @@ -23,6 +23,13 @@ sign blocks, and earn block rewards plus commission from delegations. This guide walks you through the entire lifecycle: prerequisites → key generation → `create-validator` → post-launch checks → going live. +:::tip First time? +This page assumes you're comfortable with Cosmos tooling and moves fast. +[Launch your first validator](./validator-walkthrough) covers the same ground +step by step, explaining what each command does and the mistakes that commonly +trip people up. +::: + ## 1 · Prerequisites | Requirement | Value | @@ -102,9 +109,15 @@ keyring to a public machine or repository. Verify the balance: ```bash -safrochaind query bank balances "$(safrochaind keys show validator -a)" +safrochaind query bank balances "addr_safro1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` +:::note Use the literal address +A nested `"$(safrochaind keys show validator -a)"` only resolves if that key +name exists in the keyring that is active at that moment — it breaks across +machines, sessions, and keyring backends. Paste the actual address string. +::: + ## 4 · Get your consensus pubkey Each validator process generates `priv_validator_key.json` on first init. @@ -161,7 +174,7 @@ EOF safrochaind tx staking create-validator validator.json \ --from validator \ --chain-id safro-testnet-1 \ - --gas auto --gas-adjustment 1.3 \ + --gas 250000 \ --gas-prices 0.05usaf \ --keyring-backend file \ --node https://rpc.testnet.safrochain.com:443 \ @@ -169,13 +182,13 @@ safrochaind tx staking create-validator validator.json \ ``` - + ```bash safrochaind tx staking create-validator validator.json \ --from validator \ --chain-id safrochain-1 \ - --gas auto --gas-adjustment 1.3 \ + --gas 250000 \ --gas-prices 0.05usaf \ --keyring-backend file \ --node https://rpc.safrochain.network:443 \ @@ -185,6 +198,22 @@ safrochaind tx staking create-validator validator.json \ +:::warning Prefer a flat `--gas` over `--gas auto` here +`--gas auto --gas-adjustment 1.3` can under-estimate `create-validator` and +fail with `out of gas` — **and the fee is still charged**, because it is +deducted before execution. Use a flat, generous `--gas`, or raise +`--gas-adjustment` to `1.8`+ if you prefer simulation. + +Then confirm the result rather than trusting the broadcast response, which +only reports acceptance into the mempool: + +```bash +safrochaind query tx --node https://rpc.safrochain.network:443 +# code: 0 → success +# code: 11 → out of gas; raise --gas and retry +``` +::: + ## 7 · Verify the validator was created @@ -251,6 +280,7 @@ production-ready Docker images and detailed migration guides. | Action | Command | | --- | --- | | Edit description / commission | `safrochaind tx staking edit-validator --commission-rate 0.06 …` | +| Rename the validator | `safrochaind tx staking edit-validator --new-moniker "new-name" …` (the flag is `--new-moniker`; there is no `--moniker`) | | Self-delegate more | `safrochaind tx staking delegate $VALADDR 5000000usaf …` | | Unjail after downtime | `safrochaind tx slashing unjail --from validator …` | | Unbond (begin exit) | `safrochaind tx staking unbond $VALADDR usaf …` | diff --git a/docs/validators/overview.md b/docs/validators/overview.md index 6ffdf51be..fb796b8aa 100644 --- a/docs/validators/overview.md +++ b/docs/validators/overview.md @@ -72,8 +72,9 @@ day-2 operations to graceful exit. | If you want to… | Read | | --- | --- | +| Launch a validator for the first time | [Launch your first validator](./validator-walkthrough) | | Understand operator vs consensus keys | [Key management](./key-management) | -| Stand up your first validator end-to-end | [Become a validator](./become-a-validator) | +| Stand up your first validator end-to-end (concise) | [Become a validator](./become-a-validator) | | Move signing off the validator host | [Remote signing](./remote-signing) | | Lay out sentries and validator topology | [Sentry architecture](./sentry-architecture) | | Set up Prometheus + Grafana | [Monitoring](./monitoring) | @@ -104,7 +105,7 @@ same six pillars. | Network | Chain ID | Status | Validator slots | | --- | --- | --- | --- | -| Mainnet | `safrochain-1` | upcoming (Q3 2026) | confirmed in genesis | +| Mainnet | `safrochain-1` | **live** since `2026-06-25T10:00:00Z` | open via `MsgCreateValidator` | | Testnet | `safro-testnet-1` | live | open, faucet-funded | Always validate on testnet first. Every page in this section assumes you diff --git a/docs/validators/validator-walkthrough.md b/docs/validators/validator-walkthrough.md new file mode 100644 index 000000000..e67b1c04f --- /dev/null +++ b/docs/validators/validator-walkthrough.md @@ -0,0 +1,712 @@ +--- +title: Launch your first validator +description: "Set up a Safrochain mainnet validator from an empty machine: install, sync, keys, funding, and create-validator, with the mistakes that commonly trip people up." +sidebar_position: 2 +keywords: + - Safrochain validator + - validator guide + - beginner validator + - mainnet validator + - create-validator + - safrochain-1 + - staking SAF + - validator walkthrough +--- + +This page takes you from an empty machine to a validator signing blocks on +Safrochain mainnet (`safrochain-1`). It assumes **no prior blockchain +experience**, and explains what each step does and why. + +If you already run Cosmos validators and just want the commands, use +[Become a validator](./become-a-validator) instead — same process, no +explanations. + +:::tip The one rule that matters +Everything up to and including [Part 8](#part-8--sync-your-node) is **local +and reversible**. You can delete it all and start over as many times as you +like, and nothing reaches the chain. The **only** irreversible step is +broadcasting `create-validator` in [Part 13](#part-13--go-live-the-irreversible-step). +Slow down there. Nowhere else. +::: + +## What a validator actually is + +Picture the blockchain as a shared notebook that thousands of people write +in at once — every transfer, every balance change. For that notebook to be +trustworthy, someone has to check each new page before it's added: no one +cheating, no one spending money they don't have, no one rewriting old pages. + +A **validator** is a computer that does that checking, continuously, 24/7. +Instead of one bank controlling the notebook, a network of independent +validators each hold a copy and agree on what's true together. That's the +whole point of a blockchain — nobody is in charge. + +### What you get out of it + +- **Rewards.** New SAF and transaction fees get paid out to validators for + doing this work. Part goes to you; part goes to anyone who delegates their + SAF to you. +- **A vote.** Validators vote on proposals that steer the network — upgrades, + parameter changes, treasury spending. More stake behind you, more weight. +- **Reputation.** Your validator's name is public and permanent. Running a + reliable one is a real way to build credibility in the ecosystem. + +### What it costs you + +Running a validator is a **real operational commitment**, not passive income. +If your node goes offline too long you get [jailed](./slashing) and stop +earning. If it double-signs, you get **slashed** — tokens permanently +destroyed, and not only yours: your delegators lose too. + +If you only want rewards without the responsibility, delegate your SAF to +someone else's validator instead. That's simpler, still pays, and carries +none of the uptime risk. This page assumes you've weighed that and want to +run infrastructure properly. + +## Glossary + +Skim this once. Every term comes up again below. + +| Term | What it actually means | +| --- | --- | +| **Node** | A computer running the Safrochain software, keeping its own copy of the chain and talking to other nodes to stay in sync. | +| **Validator** | A node allowed to check and approve new blocks, in exchange for staking tokens as a good-behaviour deposit. | +| **Full node** | A node that keeps a copy of the chain and answers questions about it, but doesn't approve anything. | +| **Syncing** | Your node downloading and verifying history until it's up to date — like a new hire reading months of records before making decisions. | +| **Genesis file** | The very first page of the notebook: the chain's starting state. Every node needs a byte-identical copy. | +| **Statesync** | A shortcut. Instead of replaying every page from page 1, you start from a recent verified snapshot. Minutes instead of hours. | +| **Peer** | Another node yours talks to. | +| **Seed** | A well-known public node whose job is to introduce you to peers. Safrochain runs two. | +| **RPC** | A way to ask a node a question over the internet ("what's the latest block?") without running your own. | +| **Mnemonic (seed phrase)** | 24 words that regenerate your key. Anyone who sees them controls your funds. It is the master password to your money. | +| **Operator key** | The key representing *you* — signs transactions, holds funds, earns rewards. | +| **Consensus key** | A separate key belonging to *the node itself* — signs blocks automatically every few seconds. Kept apart from your operator key so a hacked server doesn't cost you your funds. | +| **Gas** | The processing fee for a transaction, based on how much computation it takes. | +| **Moniker** | Your validator's public display name. | +| **Commission** | The cut you take from rewards earned by people who delegate to you — your service fee. | +| **Self-delegation** | Your own tokens staked on your own validator. Your skin in the game. | +| **Active set** | The validators currently allowed to validate and earn. If you're outside it, you're registered but idle. | +| **Jailed** | A time-out. Temporarily removed from duty for being offline too much. | +| **Slashing** | A financial penalty — staked tokens permanently destroyed for serious misbehaviour. Hits your delegators too. | +| **Unbonding** | Cashing out your stake. There's a mandatory waiting period before tokens move again. | +| **Double-signing** | Signing two conflicting blocks at the same height. Gets you slashed. This is exactly why a consensus key must never run on two machines at once. | +| **Sentry node** | A bodyguard node between your validator and the public internet, hiding your validator's real address. | + +## Before you start + +You'll need: + +- **A machine.** A VPS for anything long-running — see + [Hardware requirements](../run-a-node/hardware). A laptop is fine to learn on, + but a validator that sleeps when you close the lid will get jailed. +- **Real SAF.** Mainnet SAF is real money. You need enough to self-delegate + (minimum 1 SAF, realistically much more to reach the active set) plus a + little for fees. +- **About 30–60 minutes**, most of it waiting for the node to sync. +- **Ubuntu/Debian** is assumed below. macOS works for learning; see + [Install](../run-a-node/install) for the differences. + +:::note Practise on testnet first +Everything below works identically on testnet (`safro-testnet-1`), where the +tokens are free from a [faucet](https://faucet.safrochain.com/) and mistakes +cost nothing. Doing one full dry run there first is genuinely worth the hour. +See [Part 15](#part-15--already-on-testnet-moving-to-mainnet). +::: + +## Part 1 — Install the software + +Safrochain is built with the Cosmos SDK, which is written in Go, so you +install Go first, then build the Safrochain binary (`safrochaind`) with it. + +:::warning Version matching is not optional +Mainnet runs a **specific** version. Building the wrong one is a common cause +of your node failing to agree with everyone else. Right now mainnet is +**Go 1.25.8** and tag **`v0.2.2`**. Before you build, confirm those are still +current in [Mainnet endpoints](../networks/mainnet-endpoints) — this page can +go stale, that one is the source of truth. +::: + +```bash +sudo apt update +sudo apt install -y git make jq build-essential wget curl + +wget https://go.dev/dl/go1.25.8.linux-amd64.tar.gz +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.25.8.linux-amd64.tar.gz +rm go1.25.8.linux-amd64.tar.gz + +echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> $HOME/.bashrc +source $HOME/.bashrc + +go version +# expect: go version go1.25.8 linux/amd64 +``` + +Now build `safrochaind` itself: + +```bash +git clone https://github.com/Safrochain-Org/safrochain-node ~/safrochain-node +cd ~/safrochain-node +git fetch --tags +git checkout v0.2.2 +make install + +safrochaind version +# expect: v0.2.2 +``` + +If `safrochaind: command not found`, your shell can't see Go's bin directory. +Re-run `source $HOME/.bashrc` and try again. + +## Part 2 — Tell the CLI which chain you're on + +Rather than typing `--chain-id` and `--node` on every command forever, save +them once: + +```bash +safrochaind config set client chain-id safrochain-1 +safrochaind config set client node https://rpc.safrochain.network:443 +safrochaind config set client keyring-backend file +``` + +Commands below still spell these flags out explicitly, so they work either +way — but once you're comfortable you can drop them. + +## Part 3 — Initialise your node + +This creates your node's home directory and its starting config: + +```bash +safrochaind init "your-validator-name" --chain-id safrochain-1 --home ~/.safrochain +``` + +Replace `your-validator-name` with whatever you want to be publicly known as. + +This generates: + +- `config/priv_validator_key.json` — your **consensus key**. Created right + now, automatically. Not the same as your wallet key (see [Part 14](#part-14--security-why-two-keys)). +- `config/node_key.json` — your node's network identity. +- `config/genesis.json` — a **placeholder**. You replace it next. +- `config/config.toml` and `config/app.toml` — settings. + +:::warning Running `init` twice reuses the same consensus key +That's usually fine. But if you ever need a genuinely *new* consensus +identity (say, migrating off a compromised server), use a **new, separate +`--home` directory** — don't try to regenerate `priv_validator_key.json` by +hand inside an existing one. +::: + +## Part 4 — Get the real genesis file + +The genesis file `init` created is a placeholder. Every node on mainnet must +have a byte-identical copy of the real one, or yours will disagree with the +network about basic reality and never sync. + +```bash +curl -fsSL https://raw.githubusercontent.com/Safrochain-Org/mainnet-genesis/main/genesis.json \ + -o ~/.safrochain/config/genesis.json +``` + +Now verify you got the real file and not a corrupted download: + +```bash +( cd ~/.safrochain/config && \ + echo "c05ac5aec1918df9edb257e8e0eea184d73edc51370eb4aa9f0b4f0aad615c4d genesis.json" \ + | shasum -a 256 -c - ) +# expect: genesis.json: OK +``` + +If that doesn't say `OK`, stop and re-download. Don't continue with a genesis +file that doesn't match — nothing after this will work. + +## Part 5 — Point your node at the network + +Your node needs to know how to find other nodes. Safrochain runs two public +**seed** nodes whose entire job is introducing you to peers. + +```bash +SEEDS="bc772fdc9749e6dfd200a9428f07d86fe4fd34ec@seed.safrochain.network:26666,d323d296ba55e89fb6ce1a724f8da1740bd8cbb0@seed2.safrochain.network:26670" + +sed -i.bak -e "s|^seeds *=.*|seeds = \"$SEEDS\"|" ~/.safrochain/config/config.toml + +grep -E '^(seeds|pex) ' ~/.safrochain/config/config.toml +``` + +Those two seeds are enough. Once your node connects, `pex` (peer exchange) +populates its address book and it finds more peers on its own. + +:::warning Don't hunt for peer IPs by hand +It's tempting to copy a raw IP address out of an old guide or a chat message. +Don't. Community node IPs go stale and you'll spend an hour debugging a dead +host that isn't your fault. + +Equally, don't try to use `rpc.safrochain.network` as a peer. The public RPC +nodes sit behind Cloudflare, which serves HTTPS on port 443 but +[intentionally does not expose their P2P sockets](../networks/mainnet-endpoints#p2p-seeds-cometbft-p2p-over-tcp). +A raw P2P connection there will just time out. The seeds above are the +supported way in. +::: + +## Part 6 — Set your gas price floor + +Open `~/.safrochain/config/app.toml` and set the minimum gas price. This is +chain-wide policy — your node should match it: + +```bash +sed -i.bak 's|^minimum-gas-prices *=.*|minimum-gas-prices = "0.05usaf"|' ~/.safrochain/config/app.toml +``` + +## Part 7 — Set up statesync (strongly recommended) + +Syncing from block 1 means replaying the entire chain history — hours. Statesync +instead grabs a recent verified snapshot and starts from there — minutes. + +It's not a shortcut that trades away safety: the snapshot's contents are +cryptographically checked against the real chain header, so a malicious server +can't feed you a fake chain. See [Statesync](../run-a-node/statesync) for why. + +Get a recent trust height and its hash: + +```bash +LATEST=$(curl -s https://rpc.safrochain.network/status | jq -r '.result.sync_info.latest_block_height') +TRUST=$((LATEST - 1000)) +HASH=$(curl -s "https://rpc.safrochain.network/block?height=$TRUST" | jq -r '.result.block_id.hash') + +echo "trust_height = $TRUST" +echo "trust_hash = \"$HASH\"" +``` + +Put those into `~/.safrochain/config/config.toml` under `[statesync]`: + +```toml +[statesync] +enable = true +rpc_servers = "https://rpc1.safrochain.network:443,https://rpc2.safrochain.network:443" +trust_height = +trust_hash = "" +trust_period = "168h" +``` + +Four things people get wrong here, all of which produce the same unhelpful +"it just doesn't sync": + +1. **Leaving out `trust_period`.** All five settings are needed together. +2. **Using a stale trust height.** Keep it recent — within a couple thousand + blocks of the tip. Grab it fresh, right before you start. +3. **Listing the same RPC twice** in `rpc_servers`. Statesync needs **two + independent** sources to cross-check the snapshot. Use `rpc1` and `rpc2` as + shown, not `rpc` twice. +4. **Using the round-robin `rpc` alias** for both entries — it points at + rpc1/rpc2 randomly, so you can silently end up asking the same server twice. + +## Part 8 — Sync your node + +```bash +safrochaind start --home ~/.safrochain +``` + +Watch the logs. You want to see a line like: + +```text +Discovered snapshot height=... chunks=... +``` + +That confirms a peer is actually serving snapshot data. + +:::note If you only see "Discovering snapshots" forever +If two minutes pass with no snapshot ever discovered, your peers don't have +snapshots to serve. Refresh your trust height and restart. If it still fails +repeatedly, fall back to a full sync from genesis — just set +`enable = false` under `[statesync]` and start again. It's slow, but it always +works. + +If you need to reset and retry: `safrochaind tendermint unsafe-reset-all --home ~/.safrochain --keep-addr-book` +(the flag preserves discovered peers so you don't start peer discovery from zero). +::: + +Confirm you're actually synced: + +```bash +curl -s http://localhost:26657/status | jq -r '.result.sync_info | {catching_up, latest_block_height}' +``` + +`catching_up: false` means you're done. **Do not continue until you see that.** +Nothing below works on a node that hasn't caught up. + +For a real deployment, run the node under systemd so it restarts +automatically — see [Join mainnet](../run-a-node/join-mainnet#8-start-under-systemd). + +## Part 9 — Create your operator key + +This is your personal identity on the network — like opening a bank account. +It's **different** from the consensus key your node already made back in +Part 3 (see [Part 14](#part-14--security-why-two-keys) for why they're +separate). Think of it this way: you (the operator key) hire the machine (the +consensus key) to do the validating on your behalf. + +```bash +safrochaind keys add validator --keyring-backend file +``` + +You'll set a passphrase (needed every time you sign something), and it prints +a **24-word mnemonic exactly once**. + +:::danger Write the mnemonic down offline, right now +Paper, or a password manager. Anyone who sees these words owns your funds +permanently. + +**Do not:** save it to a file on the machine, paste it into any chat or +terminal that gets logged, or have it on screen while screen-sharing or +recording. + +If a mnemonic is ever exposed — even once, even briefly — treat that key as +burned. Generate a new one and move the funds. Any SAF sent to that address +is at risk forever, not just in the moment it leaked. +::: + +Confirm it exists and note your address: + +```bash +safrochaind keys list --keyring-backend file +``` + +Copy the `addr_safro1…` address somewhere handy — you'll paste it in the next +step. + +## Part 10 — Fund your operator key + +Your validator needs SAF in this wallet to pay gas and to self-delegate. +Mainnet SAF comes from an exchange or the foundation's validator programme — +there's no faucet for real money. + +If you're moving funds in from another wallet: + +```bash +safrochaind tx bank send usaf \ + --from \ + --chain-id safrochain-1 \ + --gas 100000 --gas-prices 0.05usaf \ + --keyring-backend file \ + --node https://rpc.safrochain.network:443 --yes +``` + +Amounts are in `usaf`, the chain's base unit. **1 SAF = 1,000,000 usaf**, so +10 SAF is `10000000usaf`. Getting this wrong by a factor of a million is an +easy and expensive mistake — count the zeros. + +Now check the balance actually arrived: + +```bash +safrochaind query bank balances "addr_safro1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ + --node https://rpc.safrochain.network:443 +``` + +:::tip Paste the literal address, don't nest a lookup +You'll see examples elsewhere that write `"$(safrochaind keys show validator -a)"` +instead of an address. That only works if that exact key name is in your +currently-active keyring at that moment — it breaks across machines, sessions, +and keyring backends, usually while you're stressed. Paste the actual address +string. +::: + +## Part 11 — Get your consensus pubkey + +This is your node's "employee ID number", so you can register it as your +validator's block-signer in the next step. It's a **public** key — safe to +show anyone. It reads from your node's local config; no network call, no risk. + +```bash +safrochaind comet show-validator --home ~/.safrochain +# {"@type":"/cosmos.crypto.ed25519.PubKey","key":"XXXXXXXXXXXX..."} +``` + +Copy the whole thing, braces included. + +:::warning Use *this* node's key +This must be the consensus key of the node **you're actually going to run**. +Don't paste in one from a different or already-registered validator — the +chain rejects duplicates. +::: + +## Part 12 — Build `validator.json` + +Safrochain takes the validator details as a JSON file rather than a pile of +command-line flags. Think of it as your validator's business registration +form: name, contact details, and the terms you're offering delegators. + +```bash +cat > validator.json <"}, + "amount": "10000000usaf", + "moniker": "your-validator-name", + "identity": "", + "website": "", + "security": "", + "details": "", + "commission-rate": "0.05", + "commission-max-rate": "0.20", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" +} +EOF +``` + +Field by field: + +| Field | What it means in practice | +| --- | --- | +| `pubkey` | The consensus pubkey from Part 11 — links this validator to the machine that signs. | +| `amount` | Your self-delegation. Minimum 1 SAF (`1000000usaf`); the example stakes 10 SAF. Comes straight out of your wallet. | +| `moniker` | Your public display name. Renameable later. | +| `identity` | Keybase fingerprint — makes a logo appear next to your name in explorers. Cosmetic, optional. | +| `website` | Optional public link. | +| `security` | Optional contact email for security researchers. | +| `details` | Short public bio — who you are, why people should trust you with their stake. | +| `commission-rate` | Your cut of delegators' rewards. `0.05` = 5%. | +| `commission-max-rate` | The highest you're *ever* allowed to raise it. **Cannot be changed later.** Choose deliberately. | +| `commission-max-change-rate` | How much you can raise it per day — protects delegators from sudden spikes. | +| `min-self-delegation` | The floor you promise to keep staked. Drop below it and your validator automatically unbonds. | + +### Choosing your commission + +Roughly 90% of network rewards get spread across active validators, with the +rest going to the community pool. Of what your validator earns, your +`commission-rate` comes off the top and the remainder splits among your +delegators proportionally. + +Lower commission attracts delegators but earns you less per unit of stake. +Most established validators sit in the **5–10%** range. Note that +`commission-max-rate` is permanent — pick a ceiling you can live with for the +life of the validator. + +### About the active set + +Imagine a room with a fixed number of chairs. The validators with the most +total stake (yours + everyone delegating to you) get one; everyone else is +registered but idle — no rewards, no work — until they gain enough stake to +take a chair. + +Check the current cap and your standing: + +```bash +safrochaind query staking params --node https://rpc.safrochain.network:443 +``` + +Being in the set means you earn *and* your governance vote carries weight. + +## Part 13 — Go live (the irreversible step) + +:::danger This is the point of no return +Everything so far has been local. This command writes your validator to the +chain permanently. There is **no delete command** for a validator record — +unbonding to zero only marks it inactive, it never disappears. + +Read `validator.json` one more time. Check the commission ceiling and the +zeros on `amount`. +::: + +```bash +safrochaind tx staking create-validator ./validator.json \ + --from validator \ + --chain-id safrochain-1 \ + --gas 250000 --gas-prices 0.05usaf \ + --keyring-backend file \ + --node https://rpc.safrochain.network:443 --yes +``` + +:::note Why a flat `--gas` and not `--gas auto` +`--gas auto --gas-adjustment 1.3` is a common default, but the estimate can +land slightly under what the transaction actually needs, and the tx fails with +`out of gas` — **while still charging you the fee**, because the fee is taken +before execution. A flat, generous `--gas` avoids this. If you prefer auto, +use `--gas-adjustment 1.8` or higher. +::: + +### Verify it actually landed + +**Do not trust the broadcast response.** It tells you the transaction was +accepted for processing, not that it succeeded. Look it up: + +```bash +safrochaind query tx --node https://rpc.safrochain.network:443 +``` + +Check the `code` field specifically: + +- `code: 0` — success. +- `code: 11` — out of gas. Raise `--gas` and retry. +- anything else — it failed. Read the `raw_log` for the reason. + +Then confirm the validator exists on-chain: + +```bash +safrochaind query staking validator \ + --node https://rpc.safrochain.network:443 +``` + +You want `status: BOND_STATUS_BONDED` and your moniker. Your `addr_safrovaloper1…` +address is the validator form of your operator address: + +```bash +safrochaind keys show validator --bech val -a --keyring-backend file +``` + +### Confirm you're signing + +```bash +journalctl -u safrochaind -f | grep -i 'signed.*vote' +``` + +Blocks scrolling past with your signature means you're a working validator. + +Now go set up [monitoring and alerting](./monitoring) — **before** you rely on +this. The chain penalises downtime and it will not call you. See +[Alerting & runbooks](./alerting). + +## Part 14 — Security: why two keys + +You now hold two very different keys, and the split is deliberate: + +| | Operator key | Consensus key | +| --- | --- | --- | +| **Represents** | You, the person/business | The node itself | +| **Signs** | Transactions, staking, rewards | Every block, automatically | +| **Lives in** | Your keyring (`file` backend) | `priv_validator_key.json` on the node | +| **Recoverable?** | Yes — 24-word mnemonic | No mnemonic; not portable the same way | +| **Should live on the always-online server?** | **No** — keep it off, ideally on a separate machine or hardware wallet | Yes, it has to | + +**Why this matters:** if your server gets compromised, the attacker gets +block-signing power — bad, and they can get you slashed by misusing it — but +they **don't get your funds**, because those need the operator key they don't +have. + +### If your server is ever compromised + +1. **Stop the validator process immediately** to prevent double-signing. +2. **Don't** reuse the same consensus key on a new machine while the old one + might still be running anywhere. That is exactly what causes slashing. +3. Rotate to a fresh `priv_validator_key.json` (new `--home`, fresh `init`) + and submit the new consensus pubkey once you're sure the old environment is + fully decommissioned. +4. Your funds are fine **as long as the operator key was never on that machine.** + +### Hardening checklist + +- Back up `priv_validator_key.json` and your wallet keys **offline**, encrypted. +- Never store keys in plaintext files, shell history, or public repos. +- Open only the ports you need: `26656` (P2P), `26657` (RPC), `1317` (API), + `9090` (gRPC). Block everything else — especially `26658` and `6060`. +- Don't expose RPC/REST publicly without a VPN or reverse proxy. +- SSH: key-based auth only, no root login, non-default port. +- **One unique `priv_validator_key.json` per node.** Never run the same one on + two machines simultaneously. + +Once you're stable, move signing off the validator host entirely — see +[Remote signing](./remote-signing) and [Sentry architecture](./sentry-architecture). +A single sentry already hides your validator's address; the foundation +recommends **at least two**, in different regions, so one failure doesn't take +your signing path offline. + +## Part 15 — Already on testnet? Moving to mainnet + +Treat mainnet as a **parallel setup, not an upgrade**. Testnet and mainnet are +entirely separate chains with separate state, genesis, keys, and funds. +Nothing carries over automatically. + +| | Testnet | Mainnet | +| --- | --- | --- | +| Chain ID | `safro-testnet-1` | `safrochain-1` | +| RPC | `https://rpc.testnet.safrochain.com` | `https://rpc.safrochain.network` | +| Funds | Free from [faucet](https://faucet.safrochain.com/) | Real SAF — real money | +| Endpoints | [Testnet endpoints](../networks/testnet-endpoints) | [Mainnet endpoints](../networks/mainnet-endpoints) | + +**What you can reuse:** your general workflow and muscle memory — every command +here works the same, just pointed at different values. Check whether your +binary version matches what mainnet currently requires; testnet may be running +a different tag. + +**What you must not reuse:** + +- **Your testnet operator key.** There's no technical reason you *can't* — it's + the same key format — but testnet keys are habitually generated with sloppy + security precisely because the funds are worthless. Generate a fresh key for + mainnet and treat it like production from minute one. +- **A consensus key across two simultaneously-running nodes.** Never point an + existing running node at a different chain-id to "switch". Use a completely + separate `--home` directory so there's zero chance of cross-contamination or + accidental double-signing. + +To run both side by side, give mainnet its own home (e.g. `~/.safrochain` +alongside your existing `~/.safrochain-testnet`), pull the mainnet genesis, sync +independently, and use a fresh key. They coexist happily on separate homes and +keyrings. + +## Troubleshooting + +Things that actually go wrong, and what to do: + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `safrochaind: command not found` | Go's bin dir isn't on your `PATH` | `source $HOME/.bashrc` | +| Node won't sync, no obvious error | Genesis file doesn't match | Re-download and verify the SHA-256 ([Part 4](#part-4--get-the-real-genesis-file)) | +| `Discovering snapshots` forever | Peers aren't serving snapshots, or trust height is stale | Refresh trust height; else disable statesync and full-sync | +| Statesync silently never starts | `trust_period` missing, or same RPC listed twice | Set all five `[statesync]` values; use `rpc1` **and** `rpc2` | +| Peer connection times out | Using an RPC hostname as a P2P peer | Use the official seeds — RPC is behind Cloudflare, no P2P | +| Peer IP doesn't respond | Hardcoded IP from an old guide is dead | Use the official seeds; don't trust static IPs from docs or chat | +| `out of gas`, fee still charged | `--gas auto --gas-adjustment 1.3` under-estimated | Flat `--gas 250000`, or `--gas-adjustment 1.8`+ | +| Tx "succeeded" but nothing happened | You trusted the broadcast response | Always `query tx ` and check `code: 0` | +| Balance check returns nothing | `$(keys show …)` subshell hit the wrong keyring | Paste the literal `addr_safro1…` string | +| `--moniker` flag rejected on `edit-validator` | Wrong flag name | It's `--new-moniker` | +| Validator exists but earns nothing | Outside the active set | Check rank/stake; you need enough to take a chair | + +## Quick reference + +```bash +# Am I synced? +curl -s http://localhost:26657/status | jq -r '.result.sync_info | {catching_up, latest_block_height}' + +# My validator's status +safrochaind query staking validator --node https://rpc.safrochain.network:443 + +# Chain-wide staking params (unbonding time, active set size) +safrochaind query staking params --node https://rpc.safrochain.network:443 + +# My balance +safrochaind query bank balances "" --node https://rpc.safrochain.network:443 + +# Rename / re-commission (note: --new-moniker, not --moniker) +safrochaind tx staking edit-validator \ + --new-moniker "new-name" \ + --from validator --chain-id safrochain-1 \ + --gas 100000 --gas-prices 0.05usaf \ + --keyring-backend file \ + --node https://rpc.safrochain.network:443 --yes + +# Get out of jail after downtime +safrochaind tx slashing unjail \ + --from validator --chain-id safrochain-1 \ + --gas 100000 --gas-prices 0.05usaf \ + --keyring-backend file \ + --node https://rpc.safrochain.network:443 --yes + +# Begin exiting (subject to the unbonding period) +safrochaind tx staking unbond usaf \ + --from validator --chain-id safrochain-1 \ + --gas 100000 --gas-prices 0.05usaf \ + --keyring-backend file \ + --node https://rpc.safrochain.network:443 --yes +``` + +## Where to go next + +| You want to… | Read | +| --- | --- | +| Understand penalties properly | [Slashing & jail](./slashing) | +| Set up Prometheus + Grafana | [Monitoring](./monitoring) | +| Wire up paging so you hear about downtime | [Alerting & runbooks](./alerting) | +| Get the consensus key off the validator host | [Remote signing](./remote-signing) | +| Lay out sentries properly | [Sentry architecture](./sentry-architecture) | +| Run day-to-day | [Operations](./operations) | +| Plan for the worst | [Disaster recovery](./disaster-recovery) | diff --git a/sidebars.ts b/sidebars.ts index 987dc837a..693106800 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -47,6 +47,7 @@ const sidebars: SidebarsConfig = { link: { type: 'doc', id: 'validators/overview' }, items: [ 'validators/overview', + 'validators/validator-walkthrough', 'validators/become-a-validator', { type: 'category', From 5be14dcd8c20a1923547173a5e706778306b8b3c Mon Sep 17 00:00:00 2001 From: samuel lemikan <142722758+atomdbc@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:13:29 +0100 Subject: [PATCH 2/2] docs: restore keys-show lookup, fix the actual keyring bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts replacing "$(safrochaind keys show validator -a)" with a literal placeholder address. Per review, the nested lookup is the right idiom: it is copy-pasteable, whereas a placeholder address is not and silently queries nothing if pasted as-is. The underlying bug was misdiagnosed. The subshell was not the problem — the missing --keyring-backend flag was. The page creates the key with --keyring-backend file, but the balance check omitted it, so the lookup fell back to the default os backend where the key does not exist and failed with `key not found`. It was the only command on the page missing the flag. Adds the flag to the nested lookup in both pages, and reframes the note around keeping --keyring-backend consistent, keeping the literal address only as the narrow fallback it is: checking from a machine with no keyring. Co-Authored-By: Claude Opus 4.8 --- docs/validators/become-a-validator.md | 14 +++++++++----- docs/validators/validator-walkthrough.md | 20 ++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/validators/become-a-validator.md b/docs/validators/become-a-validator.md index 12589b0da..ad237c1a6 100644 --- a/docs/validators/become-a-validator.md +++ b/docs/validators/become-a-validator.md @@ -109,13 +109,17 @@ keyring to a public machine or repository. Verify the balance: ```bash -safrochaind query bank balances "addr_safro1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +safrochaind query bank balances "$(safrochaind keys show validator -a --keyring-backend file)" ``` -:::note Use the literal address -A nested `"$(safrochaind keys show validator -a)"` only resolves if that key -name exists in the keyring that is active at that moment — it breaks across -machines, sessions, and keyring backends. Paste the actual address string. +:::note Keep `--keyring-backend` consistent +You created the key with `--keyring-backend file` in step 2, so the nested +lookup needs the same flag. Without it the CLI falls back to the default `os` +backend, where the key doesn't exist, and the command fails with a confusing +`key not found` even though the key is fine. + +If you're checking a balance from a machine that has no keyring at all, pass +the `addr_safro1…` address literally instead. ::: ## 4 · Get your consensus pubkey diff --git a/docs/validators/validator-walkthrough.md b/docs/validators/validator-walkthrough.md index e67b1c04f..1bc356d4a 100644 --- a/docs/validators/validator-walkthrough.md +++ b/docs/validators/validator-walkthrough.md @@ -398,16 +398,20 @@ easy and expensive mistake — count the zeros. Now check the balance actually arrived: ```bash -safrochaind query bank balances "addr_safro1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ +safrochaind query bank balances "$(safrochaind keys show validator -a --keyring-backend file)" \ --node https://rpc.safrochain.network:443 ``` -:::tip Paste the literal address, don't nest a lookup -You'll see examples elsewhere that write `"$(safrochaind keys show validator -a)"` -instead of an address. That only works if that exact key name is in your -currently-active keyring at that moment — it breaks across machines, sessions, -and keyring backends, usually while you're stressed. Paste the actual address -string. +The inner command looks up the address of the key you made in Part 9, so you +don't have to paste it by hand. + +:::tip If it says `key not found` +The key is almost certainly fine. `--keyring-backend file` has to match what +you used to create it — leave the flag off and the CLI looks in the default +`os` backend instead, where nothing exists. + +Checking from a different machine that has no keyring? Pass your +`addr_safro1…` address literally instead of the nested lookup. ::: ## Part 11 — Get your consensus pubkey @@ -657,7 +661,7 @@ Things that actually go wrong, and what to do: | Peer IP doesn't respond | Hardcoded IP from an old guide is dead | Use the official seeds; don't trust static IPs from docs or chat | | `out of gas`, fee still charged | `--gas auto --gas-adjustment 1.3` under-estimated | Flat `--gas 250000`, or `--gas-adjustment 1.8`+ | | Tx "succeeded" but nothing happened | You trusted the broadcast response | Always `query tx ` and check `code: 0` | -| Balance check returns nothing | `$(keys show …)` subshell hit the wrong keyring | Paste the literal `addr_safro1…` string | +| Balance check says `key not found` | `--keyring-backend file` missing from the nested `keys show` | Add the flag so it matches how you created the key | | `--moniker` flag rejected on `edit-validator` | Wrong flag name | It's `--new-moniker` | | Validator exists but earns nothing | Outside the active set | Check rank/stake; you need enough to take a chair |