X PrimeFlow

Safrochain

Safrochain
Chain ID: safro-testnet-1
Block Height:
Network Status: Connecting
Explorer

Manual node installation

Official operator documentationProject websiteSource repositoryExplorer

BonyNode setup profile

Configure once โ€” generate every command

These values update the Safrochain installation, wallet, validator, sync and firewall commands below.

No seed phrases or private keys
Recommended hardware: 4 Cores, 8GB RAM, 200GB NVMeProfile is valid โ€” commands are ready

Network: Testnet

Chain ID: safro-testnet-1

Guide review: 2026-07-14 ยท 4 source references

Recommended hardware: 4 Cores, 8GB RAM, 200GB NVMe

Go version: 1.23.9

Binary: safrochaind

Node home: ~/.safrochain-testnet

1. Prepare the node host

set -euo pipefail
sudo apt-get update
sudo apt-get install -y ca-certificates curl git jq make build-essential

GO_VERSION="1.23.9"
case "$(uname -m)" in
  x86_64) GO_ARCH="amd64" ;;
  aarch64|arm64) GO_ARCH="arm64" ;;
  *) echo "Unsupported CPU architecture: $(uname -m)" >&2; exit 1 ;;
esac

WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT
GO_FILE="go1.23.9.linux-$GO_ARCH.tar.gz"
GO_META_URL="https://go.dev/dl/?mode=json&include=all"
GO_SHA256=$(curl --fail --silent --show-error --proto '=https'   --max-filesize 5242880 --max-time 30 "$GO_META_URL"   | jq -er --arg file "$GO_FILE" '.[] | .files[] | select(.filename == $file) | .sha256'   | head -n 1)
test -n "$GO_SHA256"

curl --fail --location --show-error --retry 3   --proto '=https' --proto-redir '=https'   --max-filesize 268435456 --max-time 900   "https://go.dev/dl/$GO_FILE" -o "$WORK_DIR/$GO_FILE"
printf '%s  %s\n' "$GO_SHA256" "$WORK_DIR/$GO_FILE" | sha256sum --check -
tar -tzf "$WORK_DIR/$GO_FILE" >/dev/null

if [ -d /usr/local/go ]; then
  sudo mv /usr/local/go "/usr/local/go.backup.$(date -u +%Y%m%dT%H%M%SZ)"
fi
sudo tar -C /usr/local -xzf "$WORK_DIR/$GO_FILE"
grep -qxF 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' "$HOME/.profile" 2>/dev/null   || printf '%s\n' 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> "$HOME/.profile"
export PATH="$PATH:/usr/local/go/bin:$HOME/go/bin"
mkdir -p "$HOME/go/bin"
go version | grep -F "go1.23.9"

2. Build the pinned node binary

set -euo pipefail
REPOSITORY="https://github.com/Safrochain-Org/safrochain-node"
COMMIT="fbb961b7ec4c448b0c6aeb1c4532da6f51694ff6"
cd "$HOME"
test ! -e safrochain-node || { echo 'Stop: $HOME/safrochain-node already exists; review it before continuing.' >&2; exit 1; }
git clone --filter=blob:none --no-checkout "$REPOSITORY" safrochain-node
cd safrochain-node
git fetch --depth 1 origin "$COMMIT"
git checkout --detach "$COMMIT"
test "$(git rev-parse HEAD)" = "$COMMIT"
make install

# Verify which binary will be used before initialization.
command -v "safrochaind"
"safrochaind" version

3. Initialize a new node home

set -euo pipefail
BIN="safrochaind"
CHAIN_ID="safro-testnet-1"
MONIKER='validator-name'
NODE_HOME="$HOME/.safrochain-testnet"

command -v "$BIN"
test ! -e "$NODE_HOME" || { echo "Stop: $NODE_HOME already exists; back it up and review it first." >&2; exit 1; }
"$BIN" init "$MONIKER" --chain-id "$CHAIN_ID" --home "$NODE_HOME"
test -f "$NODE_HOME/config/genesis.json"

4. Install and validate genesis

set -euo pipefail
CHAIN_ID="safro-testnet-1"
NODE_HOME="$HOME/.safrochain-testnet"
GENESIS_URL='https://genesis.safrochain.com/testnet/genesis.json'
GENESIS_DOWNLOAD="$NODE_HOME/config/genesis.json.download"

test -d "$NODE_HOME/config"
test ! -e "$GENESIS_DOWNLOAD" || { echo "Stop: review the existing $GENESIS_DOWNLOAD first." >&2; exit 1; }
curl --fail --show-error --location --proto "=https" --max-time 120   --output "$GENESIS_DOWNLOAD" "$GENESIS_URL"
jq -e --arg chain "$CHAIN_ID" '.chain_id == $chain' "$GENESIS_DOWNLOAD" >/dev/null
printf '%s  %s
' 'a168fab2e9a82ad1f45f943688218254f87e5717c01c0e79efb713295de142d9' "$GENESIS_DOWNLOAD" | sha256sum --check -
cp --no-clobber "$NODE_HOME/config/genesis.json" "$NODE_HOME/config/genesis.json.init-backup" || true
install -m 0644 "$GENESIS_DOWNLOAD" "$NODE_HOME/config/genesis.json"
jq -r '.chain_id' "$NODE_HOME/config/genesis.json"

5. Configure client and selected RPC port

set -euo pipefail
CHAIN_ID="safro-testnet-1"
NODE_HOME="$HOME/.safrochain-testnet"
CLIENT_TOML="$NODE_HOME/config/client.toml"
LOCAL_RPC="tcp://127.0.0.1:51657"

test -f "$CLIENT_TOML"
cp --no-clobber "$CLIENT_TOML" "$CLIENT_TOML.bonynode-backup" || true
sed -i -E   -e "s|^chain-id[[:space:]]*=.*|chain-id = \"$CHAIN_ID\"|"   -e 's|^keyring-backend[[:space:]]*=.*|keyring-backend = "os"|'   -e "s|^node[[:space:]]*=.*|node = \"$LOCAL_RPC\"|"   "$CLIENT_TOML"
grep -E '^(chain-id|keyring-backend|node)[[:space:]]*=' "$CLIENT_TOML"

6. Apply ports, pruning, indexer and Prometheus settings

set -euo pipefail
NODE_HOME="$HOME/.safrochain-testnet"
APP_TOML="$NODE_HOME/config/app.toml"
CONFIG_TOML="$NODE_HOME/config/config.toml"

test -f "$APP_TOML"
test -f "$CONFIG_TOML"
cp --no-clobber "$APP_TOML" "$APP_TOML.bonynode-backup" || true
cp --no-clobber "$CONFIG_TOML" "$CONFIG_TOML.bonynode-backup" || true

# Application API, gRPC and optional EVM ports.
sed -i   -e 's|:1317|:51317|g'   -e 's|:8080|:51080|g'   -e 's|:9090|:51090|g'   -e 's|:9091|:51091|g'   -e 's|:8545|:51545|g'   -e 's|:8546|:51546|g'   -e 's|:6065|:51065|g'   "$APP_TOML"

# CometBFT RPC, P2P, proxy-app, pprof and Prometheus ports.
sed -i   -e 's|:26658|:51658|g'   -e 's|:26657|:51657|g'   -e 's|:6060|:51060|g'   -e 's|:26656|:51656|g'   -e 's|:26660|:51660|g'   "$CONFIG_TOML"

sed -i -E   -e 's|^pruning[[:space:]]*=.*|pruning = "custom"|'   -e 's|^pruning-keep-recent[[:space:]]*=.*|pruning-keep-recent = "100"|'   -e 's|^pruning-keep-every[[:space:]]*=.*|pruning-keep-every = "0"|'   -e 's|^pruning-interval[[:space:]]*=.*|pruning-interval = "19"|'   "$APP_TOML"
sed -i -E 's|^indexer[[:space:]]*=.*|indexer = "null"|' "$CONFIG_TOML"
sed -i -E 's|^prometheus[[:space:]]*=.*|prometheus = true|' "$CONFIG_TOML"

grep -E '^(pruning|pruning-keep-recent|pruning-interval)[[:space:]]*=' "$APP_TOML"
grep -E '^(indexer|prometheus)[[:space:]]*=' "$CONFIG_TOML"
printf 'RPC=%s P2P=%s API=%s gRPC=%s Prometheus=%s\n'   '51657' '51656' '51317'   '51090' '51660'

7. Configure minimum gas prices

set -euo pipefail
APP_TOML="$HOME/.safrochain-testnet/config/app.toml"
test -f "$APP_TOML"
cp --no-clobber "$APP_TOML" "$APP_TOML.bonynode-backup" || true

if grep -q '^minimum-gas-prices' "$APP_TOML"; then
  sed -i 's|^minimum-gas-prices *=.*|minimum-gas-prices = "0.05usaf"|' "$APP_TOML"
else
  printf '
minimum-gas-prices = "0.05usaf"
' >> "$APP_TOML"
fi
grep '^minimum-gas-prices' "$APP_TOML"

8. Configure reviewed seeds

set -euo pipefail
CONFIG_TOML="$HOME/.safrochain-testnet/config/config.toml"
SEEDS='fe71717889a8a2ee528ada4558dd9b03f578157c@seed.testnet.safrochain.com:26656'
test -f "$CONFIG_TOML"
cp --no-clobber "$CONFIG_TOML" "$CONFIG_TOML.bonynode-backup" || true
sed -i "s|^seeds *=.*|seeds = "$SEEDS"|" "$CONFIG_TOML"
grep '^seeds' "$CONFIG_TOML"

9. Create the service, start and verify the node

set -euo pipefail
BIN="safrochaind"
CHAIN_ID="safro-testnet-1"
NODE_HOME="$HOME/.safrochain-testnet"
SERVICE="safrochaind.service"
BIN_PATH=$(command -v "$BIN")
SERVICE_USER="$USER"
LOCAL_RPC_PORT="51657"

test -x "$BIN_PATH"
test -s "$NODE_HOME/config/genesis.json"
sudo tee "/etc/systemd/system/$SERVICE" >/dev/null <<EOF
[Unit]
Description=Safrochain node
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=${SERVICE_USER}
ExecStart=${BIN_PATH} start --home ${NODE_HOME}
Restart=on-failure
RestartSec=5
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now "$SERVICE"
sudo systemctl status "$SERVICE" --no-pager

# Wait briefly for the local RPC listener and require the configured chain ID.
for attempt in {1..30}; do
  if STATUS=$(curl --fail --silent "http://127.0.0.1:$LOCAL_RPC_PORT/status"); then
    jq -e --arg chain "$CHAIN_ID" '.result.node_info.network == $chain' <<<"$STATUS"
    exit 0
  fi
  sleep 2
done
echo 'Local RPC did not become ready; inspect the service journal.' >&2
sudo journalctl -u "$SERVICE" -n 100 --no-pager
exit 1

Node sync checker

Compare the selected local RPC height with the chain-verified BonyNode reference. Stop with Ctrl+C.

#!/usr/bin/env bash
set -euo pipefail

CHAIN_ID="safro-testnet-1"
LOCAL_RPC="http://127.0.0.1:51657"
REFERENCE_RPC="https://rpc.testnet.safrochain.com"

read_height() {
  local endpoint="$1"
  curl --fail --silent --show-error --max-time 10 "$endpoint/status"     | jq -er --arg chain "$CHAIN_ID" '
        select(.result.node_info.network == $chain)
        | .result.sync_info.latest_block_height
        | select(test("^[0-9]+$"))
      '
}

while true; do
  if LOCAL_HEIGHT=$(read_height "$LOCAL_RPC") && NETWORK_HEIGHT=$(read_height "$REFERENCE_RPC"); then
    BLOCKS_LEFT=$((NETWORK_HEIGHT - LOCAL_HEIGHT))
    [ "$BLOCKS_LEFT" -ge 0 ] || BLOCKS_LEFT=0
    printf 'Local: %s | Network: %s | Blocks left: %s\n'       "$LOCAL_HEIGHT" "$NETWORK_HEIGHT" "$BLOCKS_LEFT"
  else
    printf 'Unable to validate one of the chain-ID checked RPC heights; retrying...\n' >&2
  fi
  sleep 5
done

Create or restore wallet

set -euo pipefail
BIN="safrochaind"
WALLET='wallet'
[[ "$WALLET" =~ ^[a-zA-Z0-9_-]{1,64}$ ]] || { echo "Invalid wallet name" >&2; exit 1; }

# Create a new key. Store the mnemonic offline; it cannot be recovered by BonyNode.
"$BIN" keys add "$WALLET"

# To restore an existing key, run this separately:
# "$BIN" keys add "$WALLET" --recover

WALLET_ADDRESS=$("$BIN" keys show "$WALLET" -a)
"$BIN" query bank balances "$WALLET_ADDRESS"

Create validator

BIN="safrochaind"
CHAIN_ID="safro-testnet-1"
WALLET='wallet'
MONIKER='validator-name'
IDENTITY=''
WEBSITE=''
DETAILS='Independent validator'
AMOUNT="1000000usaf"
MIN_SELF_DELEGATION="1"
COMMISSION_RATE="0.10"
COMMISSION_MAX_RATE="0.20"
COMMISSION_MAX_CHANGE="0.01"

"$BIN" tx staking create-validator   --amount "$AMOUNT"   --from "$WALLET"   --commission-rate "$COMMISSION_RATE"   --commission-max-rate "$COMMISSION_MAX_RATE"   --commission-max-change-rate "$COMMISSION_MAX_CHANGE"   --min-self-delegation "$MIN_SELF_DELEGATION"   --pubkey "$("$BIN" tendermint show-validator)"   --moniker "$MONIKER"   --identity "$IDENTITY"   --website "$WEBSITE"   --details "$DETAILS"   --chain-id "$CHAIN_ID"   --gas auto --gas-adjustment 1.5 --gas-prices 0.05usaf   -y

Basic host firewall

set -euo pipefail

P2P_PORT="51656"
sudo ufw default allow outgoing
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow "$P2P_PORT/tcp"
sudo ufw enable
sudo ufw status verbose

# RPC/API/gRPC remain private unless you deliberately add authenticated
# reverse-proxy rules. Never expose validator signing services publicly.

Reversible decommission

This procedure stops and disables the service but retains the node home, binary and service file. Verify the backup before any later deletion.

set -euo pipefail

SERVICE="safrochaind"
NODE_HOME="$HOME/.safrochain-testnet"
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
RETIRED_HOME="$NODE_HOME.retired.$STAMP"
KEY_BACKUP="$HOME/safrochaind-validator-keys-$STAMP.tar.gz"
NODE_STOPPED=0

rollback() {
  trap - ERR INT TERM
  if [ "$NODE_STOPPED" -eq 1 ]; then sudo systemctl start "$SERVICE" || true; fi
  exit 1
}
trap rollback ERR INT TERM

test -d "$NODE_HOME"
test ! -e "$RETIRED_HOME"
test -s "$NODE_HOME/config/priv_validator_key.json"
test -s "$NODE_HOME/data/priv_validator_state.json"
sudo systemctl stop "$SERVICE"
NODE_STOPPED=1

# Back up validator identity/state before moving anything.
tar -czf "$KEY_BACKUP" -C "$NODE_HOME"   config/priv_validator_key.json config/node_key.json data/priv_validator_state.json
test -s "$KEY_BACKUP"
mv "$NODE_HOME" "$RETIRED_HOME"
sudo systemctl disable "$SERVICE"
NODE_STOPPED=0
trap - ERR INT TERM

echo "Node home was retained at: $RETIRED_HOME"
echo "Critical key backup: $KEY_BACKUP"
echo "No binary, service file or node data was deleted."