Express API behind sysnode.info. Aggregates Syscoin Core RPC + Blockbook + masternode telemetry + a small authenticated subsystem for governance proposal drafts, vote reminders, and "Pay-with-Pali" collateral flows.
The public dashboard is served by sysnode-info; this repo is the backend half of that stack.
Public, unauthenticated routes (cached, read-only):
/mnstats,/masternodes,/mnlist,/mnsearch— masternode data/governance— active and historical governance proposals/csvparser— CSV-ingest helper used by the dashboard
Authenticated routes (cookie + CSRF, same-site):
/auth/*— registration, verification, login, session, delete account/vault/*— encrypted per-user blobs (notification prefs, proposal drafts)/gov/proposals/*— governance proposal wizard, submissions, collateral PSBT, vote receipts
- Node.js 20 LTS (engines in
package.jsongate Node ≥ 20, < 24) - A reachable
syscoindon mainnet or testnet, with RPC enabled - SMTP server for verification emails and vote reminders (or
MAIL_TRANSPORT=logfor dry-run) - SQLite 3 (via
better-sqlite3, no separate install; native module compiles atnpm install)
Optional, used only for the Pali PSBT collateral path:
- A Blockbook instance for the same network as the RPC node (
https://blockbook.syscoin.org/for mainnet,https://blockbook-dev.syscoin.org/for testnet)
git clone https://github.com/syscoin/sysnode-backend.git
cd sysnode-backend
npm ci
cp .env.example .env # then edit — see .env.example for inline docs
npm run dev # nodemon on :3001
npm test # full jest suite (~830 cases)All configuration is via environment variables. .env.example is the source of truth and carries inline rationale for every field. The short form:
| Variable | Purpose |
|---|---|
PORT, BASE_URL |
Where the server listens, and the public URL baked into email links |
CORS_ORIGIN, FRONTEND_URL |
SPA origin for credentialed CORS and verification-link base |
TRUST_PROXY |
Reverse-proxy hop count (or CIDR) so req.ip is the real client |
SYSNODE_DB_PATH |
Path to the SQLite file (auto-created) |
SYSNODE_AUTH_PEPPER |
32-byte hex secret; required in production |
SMTP_*, MAIL_FROM, MAIL_TRANSPORT |
Mail delivery; MAIL_TRANSPORT=log prints to stdout |
SYSCOIN_RPC_HOST, SYSCOIN_RPC_PORT |
RPC endpoint (default 127.0.0.1:8370) |
SYSCOIN_RPC_COOKIE_PATH |
Preferred — absolute path to Core's .cookie for same-host deployments |
SYSCOIN_RPC_USER, SYSCOIN_RPC_PASS |
Fallback static creds for remote RPC nodes |
SYSCOIN_NETWORK, SYSCOIN_BLOCKBOOK_URL |
Enables the Pay-with-Pali collateral PSBT path |
The backend supports both authentication modes and picks cookie over static when both are configured (with a one-line warning at boot). Cookie auth is zero-secret-management: syscoind rewrites the cookie on every restart, and the backend picks up the new token automatically via a 401-driven replay. Use it for any deployment where the backend runs on the same host as syscoind.
For remote RPC nodes, either configure rpcauth= in syscoin.conf and use the static SYSCOIN_RPC_USER / SYSCOIN_RPC_PASS here, or mount the cookie file via a secure channel.
These steps stand up sysnode-backend + sysnode-info on one Ubuntu box that already runs syscoind. HTTP-only; intended for staging and testing, not production. Everything installs into the user's home directory — no sudo required on most steps (a couple of optional hardening steps do need it; they are clearly marked).
Walked end-to-end against Ubuntu 24.04 LTS + Node 22. Port layout: backend :3001, frontend :3000.
Node.js ≥ 20, < 24 (see engines in package.json). If the box already has a Node in that range, skip the nvm block. Ubuntu 24.04 ships a compatible Node in its default repos; many one-click images come with Node 20 or 22 pre-installed.
# Only if you don't already have Node 20–23.x
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 22 && nvm use 22
# Use a user-local npm prefix so global installs don't need sudo
mkdir -p ~/.npm-global ~/.local/bin
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"' >> ~/.bashrc
export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"
npm install -g pm2 serveEmail delivery (account verification, password-change notices, vote reminders, proposal status updates) goes through any SMTP provider you choose — SMTP2GO, SendGrid, Brevo, Postmark, Mailgun, AWS SES, or a corporate mail relay all work identically from the app's perspective. Pick one, verify a sender domain you control on their dashboard, and note its SMTP host / port / username / password. You'll paste those into .env in the next section.
In production the backend refuses to boot unless SMTP_HOST is set (or MAIL_TRANSPORT=log is set explicitly for stdout-only dry-run), so this step is required before the backend will start with NODE_ENV=production.
mkdir -p ~/apps && cd ~/apps
git clone https://github.com/syscoin/sysnode-backend.git
git clone https://github.com/syscoin/sysnode-info.gitcd ~/apps/sysnode-backend
npm ci
# Locate the Core cookie (path depends on the user that runs syscoind)
ls -l ~/.syscoin/.cookie 2>/dev/null || sudo ls -l /root/.syscoin/.cookie
PEPPER=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
COOKIE_PATH=/home/ubuntu/.syscoin/.cookie # adjust to match the ls above
SERVER_IP=$(hostname -I | awk '{print $1}') # or hardcode the public IP
cat > .env <<EOF
PORT=3001
BASE_URL=http://${SERVER_IP}:3001
CORS_ORIGIN=http://${SERVER_IP}:3000
FRONTEND_URL=http://${SERVER_IP}:3000
NODE_ENV=development
TRUST_PROXY=loopback
SYSNODE_DB_PATH=./data/sysnode.db
SYSNODE_AUTH_PEPPER=${PEPPER}
# SMTP — paste your transactional provider's credentials here. Port 465 is
# treated as implicit TLS; any other port (587 is standard) uses STARTTLS.
# MAIL_FROM must be a sender address you have verified at the provider.
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
MAIL_FROM=no-reply@example.com
MAIL_TRANSPORT=smtp
# Syscoin Core RPC (cookie mode, preferred for same-host)
SYSCOIN_RPC_HOST=127.0.0.1
SYSCOIN_RPC_PORT=8370
SYSCOIN_RPC_COOKIE_PATH=${COOKIE_PATH}
SYSCOIN_RPC_LOG_LEVEL=error
# Pay-with-Pali (mainnet)
SYSCOIN_NETWORK=mainnet
SYSCOIN_BLOCKBOOK_URL=https://blockbook.syscoin.org/
EOF
mkdir -p dataThe backend does not load
.envautomatically. It has nodotenvdependency. We load the file with Node's native--env-file=flag (Node 20.6+), which is why every invocation ofnodefor this repo below passes--env-file=.env.
Cookie file permissions. If the backend user can't read
~/.syscoin/.cookie(different uid thansyscoind), addrpccookieperms=groupto~/.syscoin/syscoin.confand restartsyscoind, then add the backend's user to the syscoin group. The backend's boot log prints the exact errno (ENOENT/EACCES) if it can't read the file.
Quick sanity check — confirms .env is loaded and RPC cookie auth works against Core:
node --env-file=.env -e '
const { client, rpcServices } = require("./services/rpcClient");
rpcServices(client.callRpc).getBlockchainInfo().call()
.then(r => console.log(r.chain, r.blocks, "ibd=" + r.initialblockdownload))
.catch(e => { console.error(e.message); process.exit(1); });
'
# expected: "main <height> ibd=false"REACT_APP_API_BASE is a Create React App build-time variable — it must be set before npm run build or the bundle will keep pointing at the default.
cd ~/apps/sysnode-info
npm ci
SERVER_IP=$(hostname -I | awk '{print $1}')
REACT_APP_API_BASE=http://${SERVER_IP}:3001 npm run buildcd ~/apps/sysnode-backend
pm2 start "node --env-file=.env server.js" --name sysnode-backend
cd ~/apps/sysnode-info
pm2 start "serve -s build -l 3000" --name sysnode-info
pm2 save
pm2 listOptional — survive a full reboot. Requires sudo; skip if you don't have it and just run pm2 resurrect after any reboot:
pm2 startup systemd -u $USER --hp $HOME # prints one sudo line; paste itIf ufw isn't active on the host, your cloud security-group / network-layer rules are what matter — adjust those instead.
sudo ufw status
# If active:
sudo ufw allow 3000/tcp # frontend
sudo ufw allow 3001/tcp # backend API# Backend reachable + RPC cookie auth working (real stats from Core)
curl -s http://<server-ip>:3001/mnstats | head -c 200
# Mail pipeline. Replace TEST_RECIPIENT with an inbox you can open. If SMTP is
# wired up correctly a verification email arrives within seconds — check the
# spam folder too, transactional mail from a brand-new sender domain often
# lands there until reputation builds at the receiver.
cd ~/apps/sysnode-backend
TEST_RECIPIENT=you@example.com node --env-file=.env -e '
const { createMailer } = require("./lib/mailer");
createMailer({ transport: "smtp" }).sendVerification({
to: process.env.TEST_RECIPIENT,
link: process.env.BASE_URL + "/auth/verify?t=smoketest",
}).then(() => console.log("sent to " + process.env.TEST_RECIPIENT))
.catch(e => { console.error("FAILED:", e.message); process.exit(1); });
'Then exercise the UI:
- Open
http://<server-ip>:3000— dashboard loads. - Register a user in the UI with an email address you can open — the verification email should arrive within seconds (check the spam folder too). Click the link to activate the account.
- Go into the governance proposal wizard — the Pay with Pali button should be enabled (assuming your browser has Pali installed and the chain guard verified mainnet).
pm2 stop sysnode-backend sysnode-info
cd ~/apps/sysnode-backend
git pull && npm ci
cd ~/apps/sysnode-info
git pull && npm ci
REACT_APP_API_BASE=http://<server-ip>:3001 npm run build
pm2 restart sysnode-backend sysnode-info| Symptom | Likely cause | Check |
|---|---|---|
Backend exits at boot, failed to read rpc cookie at ... ENOENT |
Wrong SYSCOIN_RPC_COOKIE_PATH |
sudo -u <syscoind-user> cat <path> |
Backend exits at boot, ... EACCES |
Backend user can't read the cookie | Use rpccookieperms=group + usermod -aG |
| Backend rejects RPC with 401 after a Core restart once, then recovers | Expected — cookie rotated, backend replayed with the new one | No action |
| Pay with Pali button disabled | paliChainGuard reports pali_path_chain_mismatch or pali_path_rpc_down |
GET /gov/proposals/network returns a paliPathReason |
| Verification emails never arrive | SMTP creds wrong, sender domain not verified at the provider, or mail filtered into spam | Check backend logs for 5xx SMTP responses, the provider's dashboard for bounces/delivery status, and the recipient's spam folder |
Frontend hits https://syscoin.dev instead of the test backend |
REACT_APP_API_BASE not set at build time |
Rebuild with the env var inline |
MIT