A phone-mineable, fair-launch, post-quantum, lightweight blockchain.
NADO is built around a seamless, one-click experience: every node serves a single zero-install browser page — wallet, block explorer, miner, and alias manager in one — at its root URL, so full interaction with the chain is one tap away from any device, with no app, no sync, and no account. Under that surface, NADO lets an ordinary phone — running nothing but that browser tab — take part in block production for zero capital, on a fair launch with no premine, secured by post-quantum signatures. It replaces the Proof-of-Work hash race with a deterministic, beacon-keyed weighted draw: one hash decides each block's producer, so faster hardware (ASICs, GPUs) confers no advantage and there is nothing to grind. Coins enter circulation only as block rewards.
Status: testnet-stage alpha, NOT yet mainnet-launched. The fair-mining economics and the full consensus-security hardening plan (objective fork-choice, enforced finality, grind-proof chain weight, detached winner signatures + equivocation slashing (block-authorship + FFG attestation), enforced FFG stake-attested finality, commit-reveal RANDAO) are now implemented; the multi-node, epoch-crossing behaviour of the last three is still only lightly exercised empirically (see Security). What genuinely remains is a subset of eclipse hardening (ASN-level peer diversity, pinned multi-seed bootstrap, snapshot- bootstrap binding to a finalized signed checkpoint). On top of consensus, a STARK-proven execution layer (a field-native zkVM) is live and carries real dApps — ~20 on-chain games, shielded transfers, and end-to-end-encrypted on-chain messaging. Run it on testnet / at your own risk; do not secure value of consequence with it yet. The alphanet rerolls often (fresh genesis, balances carried forward) as consensus changes land strictly with no backward-compat; the current chain id is
alphanet-6.
Most "anyone-can-mine" coins fail in one of four ways: mining gets captured by specialized hardware; the launch isn't fair (premines, insider allocations); the cryptography isn't quantum-resistant; or a "light" client still leans on trusted infrastructure. NADO targets all four at once, and adds a fifth goal — that re-joining the network should never get harder as more people join.
It is inspired by NANO, IDENA, NYZO and Vertcoin, and pushes the barrier to entry lower than any of them: no puzzles to keep solving, no efficient rig to keep running, and no requirement to own coins.
-
Seamless — one client, any device, one tap. Every node serves a single browser page (
/) that is at once the wallet, block explorer, miner, and alias manager — and, via a Rollup tab, a window onto the execution layer that browses, deploys and calls contracts. No install, no browser extension, no full node, no signup, no seed-phrase ceremony. Unlike a MetaMask-style extension wallet — which only holds keys, can't mine, and means "install the extension, back up a seed, buy gas, connect to a dApp" before you do anything — NADO is just a URL. Open it and you're already a full participant: generate a post-quantum wallet, mine, send/receive, register a human-readable alias, and browse the chain, all from one link on any phone or laptop. -
A real execution layer, already live. Beyond payments, NADO runs a field-native zkVM whose state is settled to L1 under STARK proofs (objective, stake-backed finality — L1 verifies a Merkle proof against a bonded-quorum-settled root, not a re-execution). On it today: ~20 provably-fair on-chain games (dice, roulette, poker/hold'em, blackjack, mines, an original settlers-genre strategy game, an auto-battler, a deck-builder, tamagotchi-style NFT pets, and more — each with its own subdomain), shielded transfers (a private payments pool), and end-to-end-encrypted on-chain messaging (ML-KEM-768) — so you can DM any address with no off-chain server. Randomness for games is pinned to future block hashes (nobody can rig a roll); hidden information (hole cards, fog-of-war) uses commit-reveal. Games self-serve from the wallet's background signer — no per-move wallet round-trip.
-
Everyone mining earns — a presence dividend, not a lottery. Winner-take-all blocks mean most miners see nothing for long stretches. NADO redistributes most of the open lane's block reward to everyone present, weighted by how steadily they show up — a steady stream instead of a rare jackpot. It accrues off-chain while you mine (so a million miners cost the chain nothing and there's no dust bloat), and you sweep it into your spendable balance with one Collect tap — claimed trust-minimised against a bonded-quorum-settled state root. Many people getting a little, continuously: what an open, populace-scale chain should actually feel like. (Design + mechanism: doc/presence-dividend.md.)
-
Mine from your pocket — and forever if you keep it open. Presence is a PoSW lease: one ~1 s proof buys ~a day of eligibility, so a locked, asleep phone keeps mining on its own — no relay, no per-epoch traffic — and the wallet shows the exact "mining while locked" countdown. Leave the page open and mining never stops: it auto-renews the proof just before it lapses, auto-bonds your rewards if you want, and auto-resumes across a browser refresh — so direct mining runs indefinitely with zero babysitting. Open the link once and walk away.
Share the link, and the barrier to entry collapses. Because the whole experience is one page with no install step, onboarding is sending a link. Drop it in a school group chat and whoever opens it is instantly mining — and each of them shares it again. No app store, no wallet setup, no gas to buy first: the distance between "hears about NADO" and "is mining and transacting on NADO" is a single tap. Lower the barrier to entry to a shared URL and let it spread — one classroom becomes the whole school. That is the growth thesis.
- Phone-mineable. Block production is one hash per slot over a public beacon, not a race — a phone competes on equal terms with a datacenter. Winners are credited by address, so a phone can win a block while its tab is closed and a relay assembles the block on its behalf.
- Fair launch, no premine. Genesis mints zero coins (
TREASURY_GENESIS = 0). Every coin in existence was minted as a block reward. A flat base subsidy lets a brand-new, zero-coin miner earn spendable coins from block 1. - Governed, self-burning treasury. The 10% treasury is a keyless reserved account — no founder
key, no multisig — spent only by a 2/3 bonded-stake vote in the wallet's Quorum tab, and any idle
balance is burned each period. Emission holders would get anyway is forced into the ecosystem by
their own vote, or destroyed — never hoarded (
doc/treasury.md). Even the maintainer's reward is a votable, revocable quorum grant (guideline ~1% of treasury inflow) — not a hard-coded founder cut. - Consensus anti-Sybil registration. Registration is a non-parallelizable sequential PoSW whose
difficulty scales with recent registration volume (enforced in validation off the finalized anchor
epoch), so an identity flood gets progressively more expensive while a normal network stays at 1×
(
doc/registration-difficulty.md). - Two-lane "diligence" mining. A free OPEN lane anyone can win with no coins (capped at ~30% of blocks, a population-independent Sybil ceiling) plus a BONDED lane won with refundable, whale-capped stake. Bonding is optional and only boosts the bonded lane — never required.
- Post-quantum signatures. ML-DSA-44 (NIST FIPS 204 / Dilithium) via pure-Python
dilithium-py— no native build, in keeping with the lightweight goal. Cross-validated against the browser's@noble/post-quantumso a phone and a full node verify each other. - Lightweight & reproducible. Consensus hashing is over canonical JSON, so a browser client reproduces every address, transaction id, and verification byte-for-byte. State is a single memory-mapped key-value store; block bodies are compact zstd-compressed blobs.
- First-party clients. A browser/mobile NADO Interface that is also a full wallet, a PySide6 desktop wallet, and browsable explorer endpoints on every node.
Time is divided into epochs of EPOCH_LENGTH = 60 slots, each keyed by a per-epoch randomness
beacon. For each slot the protocol deterministically draws exactly one producer.
For a given slot the winner is a single computation over the public beacon:
draw = int( blake2b([beacon, slot]) ) % total_weight
winner = the address whose cumulative-weight band contains `draw`
(walking eligible addresses in canonical sorted order)
There is no multi-attempt hash race and no nonce grinding — one hash decides each slot. Faster hashing hardware therefore confers no advantage, and any full node or browser client reproduces the same winner from public chain state. Because the winner is chosen by address, an offline phone can win a slot and a relay can build and broadcast the crediting block for it.
Each epoch's 60 slots are split by a beacon-keyed permutation of slot indices into two lanes:
- OPEN lane —
K_OPEN = 18slots (~30%,OPEN_BPS = 3000), winnable by any registered, present identity for zero coins. - BONDED lane — the remaining 48 slots, won in proportion to locked, refundable stake.
The split is over slot indices, not per-identity weight, so there are always exactly K_OPEN open
slots no matter how many identities register. A zero-capital botnet of a million identities still
cannot win more than OPEN_BPS (30%) of blocks. This population-independent structural ceiling —
not a puzzle difficulty or an economic cost — is NADO's central Sybil defense. (Empty-lane policy is
one-directional and fail-closed: an empty open slot falls back to the bonded lane, but an empty
bonded slot is skipped, never the reverse, so the free lane can never absorb bonded slots.)
- Register by computing a sequential Proof-of-Work (PoSW) — a non-parallelizable hash chain
(
POSW_Tsteps, ~1 s in-browser, fee-exempt, post-quantum: it assumes only blake2b — no trusted setup, no elliptic curve, nothing Shor-breakable). Unlike the old parallelizable hashcash, a GPU/ASIC can't mint identities in bulk, and the proof is validated by every node in consensus (validate_transaction, the block-validation path) — not just the relay you connect to, so a bogus registration is rejected network-wide. Registration is a renewable presence lease (POSW_LEASE_EPOCHS, ≈ 1 day): to stay in the open lane you renew with a fresh PoSW, turning "pay once, farm forever" into "pay continuously per identity." The structural ~30 % lane cap is still the hard Sybil bound; the PoSW lease prices identity creation and upkeep in real sequential time on top. The recert is the single presence signal — there is no separate heartbeat. You're eligible iff you have a recert withinPOSW_LEASE_EPOCHS, so AFK mining is trivial: one ~1 s PoSW buys a full lease of eligibility, locked phone or not — no relay, no pre-signed heartbeats, no per-epoch traffic. The miner auto-renews at ~80 % of the lease; kept open, it mines forever.
Why no separate per-epoch heartbeat? An earlier design had one. But once the lease covers the whole ~1-day AFK window (and can be pre-signed), a per-epoch heartbeat is co-terminal with the lease and carries no information the recert doesn't — redundant. Collapsing to one signal is strictly simpler: the recert prices the identity and marks presence. The ~30 % lane cap stays the hard Sybil bound regardless.
Open-lane selection weight is capital-free: a flat floor (OPEN_BASE_FLOOR = 2) every present
identity always gets, plus a diligence ramp to OPEN_FID_BONUS = 8 over FIDELITY_CAP = 30 consecutive
recerts (overall range 2..10). Fidelity is continuity over recerts (apply_register,
revert-symmetric): a continuous recert adds a step, a lapse resets the streak — so a rotated/churned
identity can't keep a ramp it stopped paying for. The single most effective thing you can do is stay
present. Mine to one address — splitting across addresses gains nothing, and onboarding many
addresses from one machine is throttled (below).
Progressive IP-diversity onboarding cap. Registering (onboarding) new OPEN-lane addresses is rate-limited per source IP by subnet proximity: a new address's "crowding cost" is full for a same-exact-IP peer and halves for each broader shared prefix (same /24 = ½, /16 = ¼, /8 = ⅛), unrelated networks cost nothing (IPv4 /32·/24·/16·/8; IPv6 /128·/64·/48·/32). So a datacenter /24 gets one bounded shared budget while distinct networks aren't penalised — stopping "one box scripts 10 000 miners" at the entry point. This is relay admission control, not consensus (an IP can't be a consensus input without forking); the hard Sybil bound is still the structural ~30 % lane cap. Budget is
max_registrations_per_ip(default 64/hr,NADO_MAX_REG_PER_IP,0= off).
A bond transaction moves spendable balance into a non-spendable bonded column; an unbond/withdraw
pair moves it back out after a timelock (see below). Bonded selection weight is
min(bonded, BOND_CAP) // B_MIN, capped at MAX_SHARES = 100:
- Split-neutral — weight depends only on total bonded capital, so sharding across many addresses gains nothing.
- Whale-capped — a single identity tops out at
BOND_CAP = 1,000 NADO(B_MIN = 10 NADOper share), so no whale can monopolise the lane. The bond is refundable — you keep your coins.
Bonded lane + FFG finality — now active. At the 10-NADO entry the bonded registry is populated and blocks began producing on the bonded lane the moment
B_MINdropped. At the old 1,000-NADO entry nobody on a fair launch could grind enough to bond, so the registry sat empty and every block fell back to the zero-stake OPEN lane — no economic weight behind blocks. Now each lane carries distinct security: the OPEN lane is Sybil-capped presence, the BONDED lane is stake-weighted capital that both draws its share of producers and backs finality. A validator that signs two blocks at the same height+parent — or two conflicting FFG attestations for one epoch — is slashable via a portable proof (below), and FFG (Casper-style stake-attested epoch checkpoints, justifying at a strict >2/3 quorum) now enforces finality: a justified→finalized checkpoint is folded into the persisted rollback floor, so a >2/3-attested checkpoint is objectively un-reorgable (remote sync can't adopt a conflicting heavier chain) — it is no longer merely observed. Honest scope: FFG does not speed up confirmations — confirmation finality is still the depth-based floor attip − FINALITY_DEPTH(~12 blocks), and FFG (epoch-granular, 60 blocks) normally trails that floor. FFG is the objective, accountable, long-range finality layer on top of the fast subjective depth floor, never a latency reduction, and it can never stall the chain: the quorum denominator is the active bonded set (validators that attested withinINACTIVITY_WINDOW = 3epochs), so a bonded validator that goes dark is leaked from the finality quorum (its vote lapses, its bond untouched) and a live attesting majority can always finalize.
Bonded mining is passive — no work, no need to be online. Once you hold enough to bond (
B_MIN = 10 NADO), you can stop actively mining: the bonded lane is staking, not proof-of-work. There is no PoW/PoSW to compute, no periodic recert, and no requirement to keep the app or a node open — the beacon draws you in proportion to your stake, and because winners are credited by address, a relay builds your winning block even while you're offline. With auto-bond on, rewards compound straight back into stake, so it grows hands-free. Two honest caveats: (1) a freshly bonded stake ramps to full selection weight overBOND_RAMP_EPOCHS(~30 epochs) — an automatic anti-sudden-whale delay, no action needed — after which it earns at full rate; (2) your share is competitive (proportional to your slice of total bonded stake), and this lane is rich-get-richer by design (stake = yield, as in any Proof-of-Stake). The capital-free path — the OPEN lane + presence dividend — is the counterweight for anyone without coins, and it's the part that actually requires (light, phone-doable) mining.
Unbond is now timelocked (enforced).
unbondis a release request, not an instant refund: the stake stays in thebondedcolumn — still slashable — and a maturity blockrelease_block = current + BOND_UNLOCK_DELAY (1440)is recorded. A separate fee-exemptwithdrawtransaction moves the matured amount to spendable balance only at/afterrelease_block. Keeping the stake bonded through the delay is what keeps a caught equivocator's stake slashable while the unbond is in flight. One unbond may be pending at a time.
protocol.py is the economic source of truth. All on-chain amounts are integers in raw units, where
1 NADO = DENOMINATION = 10,000,000,000 raw (the smallest unit is 0.0000000001 NADO).
- No premine. Genesis mints zero coins; the chain bootstraps purely through the open mining lane.
- Per-block reward = a FLAT base subsidy scaled by bonding —
reward = BASE_SUBSIDY (0.1 NADO) · m(r). No fee-weighted term and no ceiling (the oldREWARD_CAPis removed): fees are destroyed, so minting more when fees rise would only soften the deflation. Sincem(r) ≤ 1, 0.1 NADO is the max emission per block (~1,440 NADO/day at the 6 sblock_time), andm·BASE ≈ 0.0166the min (perpetual tail, ~87,000 NADO/yr forever). Emission is per-block, soblock_time(default 6 s) is the emission-rate lever. - Bond-elastic emission → super hard money (
doc/bond-elastic-emission.md).m(r) = 0.15 + 0.85·e^(−4r)(tuned), whereris the bonded ratio (bonded ÷ total supply): the more the network locks up, the less it mints. Combined with fee destruction this makes NADO net-deflationary under real usage, while the perpetual tail (mnever reaches 0) means block production is always rewarded — no hard cap, no security cliff (Monero's reasoning). It self-limits: the open lane siphonsOPEN_BPS=30 %, so bonding past ~70 % is real-negative and the ratio settles ~40 %.m(r)is a hardcoded integer table inprotocol.py(never a runtime float — a last-ULP difference could fork consensus), read from committed parent state exactly likecumulative_weight. - 90 / 10 split. The producer keeps 90 %; 10 % accrues to the treasury (
TREASURY_BPS = 1000). The treasury is a reserved, keylesstreasuryaccount (no private key exists for it) that starts empty and fills only from this per-block cut. - Quorum-governed, self-burning treasury (
doc/treasury.md). The treasury is spent only by a 2/3 bonded-stake vote — no founder key, no multisig; the bonded lane is the multisig, reusing the samesettlement_justifiedquorum as finality. Atreasury_spendproposal is voted with fee-bearingtreasury_votes (each approval's weight snapshotted at vote time; newly-bonded stake must age before it counts), capped atTREASURY_MAX_SPEND_BPS(25 %) of the balance per proposal, and paid by atreasury_executeonce the quorum is met. Idle treasury above a floor is burned everyTREASURY_SPEND_PERIOD(TREASURY_BURN_BPS= 1 %/period), so emission that holders would receive anyway is forced into the ecosystem by their own vote — or destroyed, never hoarded. Stakers propose and vote from the wallet's Quorum tab. - Fees are destroyed, not paid to producers — that is what drives the elastic reward (it is a fee
mechanic, not a "burn"; the old burn-to-bribe mechanic was removed entirely). A deterministic floor
MIN_TX_FEE = 1000raw applies to ordinary transfers andbond;register,unbond, andwithdraware fee-exempt (they move no coins out —unbond/withdrawonly retime the sender's own stake). - No free→capital faucet. Open-lane presence can never mint bonded stake; the only path from free
to capital is the block subsidy an open miner actually earns — itself capped at
OPEN_BPS(30 %).
NADO runs on Python 3.10+. The entrypoint is nado.py; the node serves its API and web UI on port
9173.
There is no manual setup, no hand-rolled venv, no
pip install -r requirements.txt, nonohup, no Docker image. Those paths are unsupported — a node installed any other way falls off the update path (/updaterefuses a directory that is not a git checkout), drifts out of consensus as protocol changes ship, and will not be helped. If you already run a node that was installed some other way, run this command on it: it repairs the install in place.
curl -sSfL https://raw.githubusercontent.com/hclivess/nado/main/scripts/install.sh | sudo bash -s -- --service --user nadoNo checkout needed — piped standalone, the script installs git if missing, clones the repo itself,
builds the venv, installs the node dependencies, and registers a systemd service that boots on start
and restarts on failure.
--user nado runs the node as a dedicated non-root system account instead of root: the checkout and
chain data live at /srv/nado-home/nado, the units get a hardening block (NoNewPrivileges,
ProtectSystem=strict, no capabilities), and a root-owned restart bridge keeps self-update working end to
end. An existing root install is migrated in place — services stopped cleanly, directory moved, a
compatibility symlink left at the old path — and later re-runs auto-adopt the account, so nothing ever
quietly hands the node back to root. Omit the flag only if you have a reason to keep the node running as
the invoking user.
It is also the universal upgrade / repair command. Re-running is always safe (idempotent). If it finds
a node laid down by an older, git-less installer it converts that directory into a real checkout in
place and fast-forwards it to the latest code — your private/ keys and blocks/ / index/ chain data
are gitignored, so nothing it does can touch them. Any node, however it was first installed, gets current
by running this again.
Check the path first. If your node lives somewhere non-standard, pass
--dir /path/to/your/nado— otherwise you install a second node and the original stays stale. (With--user nadothe standard locations are found automatically: an install at~/nadois migrated to/srv/nado-home/nadoand a symlink keeps the old path working.)
Flags: --user <name> runs everything as a dedicated non-root account (recommended, see above); --exec
also runs the execution / shielded-pool node on :9273; --wallet adds the desktop-wallet deps;
--auto-bond <pct> auto-compounds mined rewards (see below); --home <dir> keeps chain data under
<dir>/nado instead of ~/nado. Run scripts/install.sh --help for all options.
Once running, open http://127.0.0.1:9173 for the node's web interface and JSON endpoints. To join a network, announce your node to a peer:
http://127.0.0.1:9173/announce_peer?ip=<peer-ip>
For public reachability and rewards, forward port 9173. Always stop the node cleanly — systemctl stop nado, or http://127.0.0.1:9173/terminate — never kill -9, which can corrupt the database. To wipe
local data and resync from scratch, stop the service and run nado_venv/bin/python purge.py from the
checkout: it clears the chain state (blocks/, index/, logs/) under your data home while keeping your
keys and peers; on the next start the node rebuilds genesis (re-seeding the public bootstrap) and resyncs
from the network.
The install above already runs the node unattended — it mines on its own, starts on boot, restarts on crash, and needs no open terminal. To manage it:
systemctl status nado # health
journalctl -u nado -f # live logs
systemctl restart nado # pick up new code after a manual pull
systemctl stop nado # clean shutdown (never kill -9 — it can corrupt the DB)Common variants (re-run the one-liner with these appended, or sudo scripts/install.sh from the checkout):
--service --user nado # run as a dedicated non-root account (recommended; migrates in place)
--service --auto-bond 25 # auto-bond 25% of mined rewards
--service --home /srv/nado-data # chain data in /srv/nado-data/nado
--service --exec # also run the execution / shielded-pool node on :9273A miner routes a percentage of newly-mined rewards straight into bonded stake, auto-compounding
their weight in the bonded lane without any manual bond transactions. It is on by default at
AUTO_BOND_DEFAULT_PERCENT = 80% (a fresh node / browser / wallet with no saved preference joins the
bonded lane hands-free) and fully overridable — set 0 to keep all rewards spendable; an explicit
0 is remembered and never reverts to the default. It is throttled to at most one bond per epoch,
only fires once the accrued amount clears a small dust floor (so each bond dwarfs its tiny fee), and
stops automatically at BOND_CAP (1,000 NADO — bonding past it buys no extra selection weight, so
it never needlessly freezes coins). It is available in all three clients:
- Node (unattended): set
auto_bond_percentinprivate/config.json, or theNADO_AUTO_BOND_PERCENTenvironment variable (which the--serviceinstaller wires into the unit). The node bonds the configured share of its own block rewards each epoch — ideal for a headless miner. - Browser interface: the Stake tab has an "Auto-bond mining rewards" field; while the tab is mining it compounds that share of your rewards (persisted in the browser).
- Desktop wallet: the Mining tab has an "Auto-bond mining rewards" control (persisted in
~/.nado_wallet/wallet.json).
It is a client/operator convenience and is never validated on-chain — every auto-bond is just an
ordinary signed bond transaction.
NADO nodes update themselves. Two mechanisms:
-
Integrated updater (
/update). Any node running current code exposes a harmless/updateendpoint — hitting it makes the node fast-forward-pull the officialmain(pinned togithub.com/hclivess/nado, ff-only, never an arbitrary branch), then relaunch its services. It also waves the request to its peers, checks on its own every 15 minutes, and — the part that makes fleet versioning near-real-time — reacts to peer hints: the moment any peer's status advertises a commit a node does not recognize, that node checks origin immediately. So the first node to pick up a push (timer or nudge) pulls the entire mesh current within seconds; a hostile peer controls only when a node glances at the canonical repo, never what it pulls. A node's latest applied commit is visible in/status(running_commit). -
Self-healing updatability (startup check + auto-repair). A node that cannot update is not merely stale — since consensus changes ship with no backward compatibility, it eventually diverges and forks. So at boot (and again daily) the node checks whether it could update at all: is
gitinstalled, is this a real checkout, doesoriginpoint at the official repo, and is there a systemd unit so an applied update actually restarts the process. If any of those fail it logs the defect loudly and repairs itself by running its ownscripts/install.sh --service— the local installer that ships with the node, never a download. The verdict is published in/statusasupdate_capable, so a node that will drift is visible from the outside instead of discoverable only by calling/update. An unreachable origin is a warning, never a failure — treating a GitHub outage as fatal would take the whole fleet down at once.Why this exists: a fleet sweep found 21 of 25 peers reporting
running_commit: null— installed by hand or by an old installer, so they could never self-update and could not even be version-checked. Three more had current repos but stale running processes, because nothing existed to restart them. Note the limit: a node already in that state cannot receive this fix (that is the defect). It stops the fleet rotting into it again; the stranded ones still need their operator to run the one-liner once. -
The installer one-liner (above) is the fallback for a node too old to have
/update, or one laid down by an old git-less installer: re-running it installsgit, converts the directory to a real checkout, and fast-forwards it — keys and chain data preserved. Old nodes with no/updateand nogitcannot be reached remotely; their operator must run this one line once to rejoin the update path. -
Fork resolution — one measurement, one decision. Recovery used to be inferred from chain weights, donor behaviour and peer benching across five overlapping paths. Those mis-fired together on 2026-07-20: a node that was simply ~500 blocks behind on the correct chain read that as "forked", rolled back into a snapshot with no history beneath it, and looped for 40+ minutes. The inference is gone, replaced by a single measured number — the highest height where our block hash equals the majority's:
measurement state action ancestor == our tip BEHINDordinary forward sync — never a rollback ancestor ≥ finalized REORGroll back to the ancestor, then sync ancestor < finalized DEAD_FORKfinality forbids the rollback → purge chain data + resync no majority / peers silent UNKNOWNdo nothing Hash equality at a height is a fact, not a heuristic, and the binary search costs ~log₂(depth) probes. It asks peers directly, so it cannot be blinded by a collapsed peer set or a wrong bench — both of which happened.
DEAD_FORKis the case that previously required a human: it is now automatic, but deliberately hard to trigger (frozen tip for 15 min, two independent probes must agree, any peer agreeing with us vetoes it), because the remedy destroys chain-derived data.private/is never touched.
Genesis rerolls. Because consensus changes ship strictly, with no backward compatibility, the
alphanet occasionally rerolls to a fresh genesis (balances carried forward from the prior chain).
This is signalled in the code by a bumped CHAIN_GENERATION: on the next update a current node
automatically wipes its old chain data and boots the new genesis — no manual step. (Keys in
private/ are never touched.) A node that has fallen too far behind to auto-detect the reroll clears
its old state with scripts/purge_resync.sh and rejoins.
Two more unattended behaviors round out a hands-free headless node (both best-effort, once per epoch, never disrupting consensus):
- Auto-collect the presence dividend — on by default (
auto_collect_dividend/NADO_AUTO_COLLECT): sweeps your accrued dividend into a provable collection. Skipped unless the node is an open-lane member (a bonded-only node accrues none, so it never burns a wasted fee). - Auto-register the open lane — opt-in (
auto_register/NADO_AUTO_REGISTER=1): keeps the PoSW presence lease alive (registers when absent, renews inside the lease tail), so a server can mine the free lane 24/7 unattended. Off by default so a headless node never silently joins — and Sybil-loads — the open lane. Full reference: doc/cli.md.
A self-contained harness spins up N nodes on 127.0.0.x loopback IPs, meshes them, and reports
whether they converge and produce blocks:
python scripts/testnet/run_testnet.py [num_nodes=3] [run_seconds=240]It uses throwaway temp dirs and sets NADO_TESTNET=1 per child (relaxes the SSRF guard for loopback).
Never set NADO_TESTNET on a real node.
For a production-style node, raise the open-file limit (/etc/security/limits.conf:
root soft/hard nofile 65535, fs.file-max = 100000 via sysctl). The installer handles Python and the
venv itself; update with /update or by re-running the one-liner — never by hand-pulling, which leaves the
running process on the old code until you systemctl restart nado.
Not a supported node platform — the installer targets Linux with systemd. To mine from Windows, open any running node's light-miner in a browser (see Mine from a phone); it needs no install at all. For a full node, use a Linux VM or WSL2 and run the one-liner inside it.
Open the running node's light-miner in any browser:
http://<node-ip>:9173/static/interface.html
The NADO Interface (static/interface.html + static/interface.js) is also a full wallet: it generates or
imports a key, computes the sequential registration PoSW in pure JS (byte-identical to the node's
verifier), registers/renews its PoSW lease against the node (no heartbeats), and keeps winning blocks even while the
phone is locked — presence is a ~1-day PoSW lease (no per-epoch traffic), and a relay assembles the
crediting block. It can send/receive with QR payment links and #pay deep links,
bond/unbond, browse the chain, and runs in 16 languages (browser-locale default) — all from a phone. It also shows
how busy each lane is right now — live OPEN and BONDED participant counts (from
/mining_status open_registry_size / bonded_registry_size) alongside your own bonded shares — so a
miner can see the field it is competing against. Crypto is vendored (static/vendor/nado-crypto.js:
blake2b + ML-DSA-44) so it works offline, and an in-page self-test asserts byte-equality of its canonical
encoding against the live repo on boot.
The NADO Interface keeps its private key in browser
localStoragein plaintext (disclosed in the UI). Treat it like a hot wallet.
- Command line (
scripts/nado_cli.py) — every interface operation from the terminal, signed by your localkeys.dat:info,send,register(computes the sequential PoSW),bond/unbond,alias,propose/vote/execute(treasury governance),collect(presence dividend),bridge-deposit. It builds the same signed transaction the browser does and POSTs it to the node's existing/submit_transaction— no new signing endpoint, no new trust surface. Full reference: doc/cli.md. - Browser / mobile NADO Interface (wallet) —
static/interface.html(see above). - Desktop wallet —
python3.10 pyside_wallet.py(PySide6): overview, send, bond/unbond, register & mine, expected-time-to-mine, an auto-bond control (compound a % of mined rewards into stake), and a live selection-lane visualization. PySide6 is wallet-only; the node itself does not need it. - Block explorer — folded into the NADO Interface as an Explore tab (
static/interface.html+static/interface.js): search by address / alias / block number / block hash / txid, browse recent blocks, and see live network + mining-lane stats — all reading the node's own public JSON API in the browser. The node serves the wallet/explorer at/. (The raw JSON endpoints —/get_account,/get_block,/get_transaction,/get_supply,/status,/resolve_alias, … withreadable=true— remain available directly.)
NADO's security rests on the two-lane selection design plus anti-DoS/anti-Sybil hygiene. The split between implemented and planned below is the difference between testnet-safe and mainnet-safe — read it before running anything of value.
A deep adversarial audit was run across six surfaces (fork-choice/51%/rollback/finality;
Sybil/two-lane/selection; slashing/equivocation/unbond; RANDAO/FFG/beacon; tx-validation/pubkey-once;
KV atomicity/eclipse/DoS), against a chain that was testnet-stage alpha with no value at stake.
Every exploitable finding it surfaced is now fixed and unit-tested — full writeup in
doc/security-audit.md. In brief:
- In-block duplicate reserved-tx bugs (CRITICAL/HIGH). Uniqueness was checked only against
parent state and block assembly did no dedup, so duplicates of a reserved tx in one block could
drain a single unbond via repeated
withdraws (slash-escape / chain-halt), over-burn on a duplicateslash(which two honest reporters trigger organically), or collapse duplicateheartbeat/revealrows so a reorg over-deletes the shared row → registry/beacon desync fork. Fixed by per-reserved-tx in-block uniqueness (reserved_uniqueness_key+dedupe_reservedin assembly +assert_unique_reservedinverify_block), plus cross-blockheartbeat/reveal-secret guards. - Same-length fork-choice wedge (CRITICAL, liveness). Two equal-weight honest tips at one height
could wedge forever because the switch was strictly-greater-weight only. Fixed by the deterministic
lowest-hash tie-break: every node now switches to the global-best tip by
(weight DESC, hash ASC), so they converge. quick_syncvalidation bypass (HIGH). Old-block sync skipped signature + spending checks.verify_blocknow always runsvalidate_transactions_in_block— the bypass is gone.- Unauthenticated advertised-weight DoS (HIGH). A single Sybil peer advertising a huge
latest_block_weightforced honest nodes into emergency rollbacks. Fixed by a bounded, auto-clearingrejected_tipsexclusion so a bogus weight can't loop a node. - Plus: per-IP rate limits on the heavy unauthenticated read endpoints (
/mining_status,/get_transactions_of_account,/get_blocks_after//get_blocks_before); an honest-signer guard (a node only ever signs a strictly higher height, so an honest re-signer can't be slashed for its own reorg); the per-/16 subnet cap now also gates the disk-reload path; and a dead/get_blocks_beforewas fixed.
The audit also confirmed the safety core sound with no change needed: the atomic
incorporate/rollback window, the monotonic finality floor, equivocation-proof unforgeability (and the
no-innocent-victim address binding), the detached-signature-outside-the-hash property, and pubkey-once
key→sender binding. The remaining items are documented residuals / future hardening (below and in
doc/security-audit.md), none of which is a theft or fork vector in the current
code.
- Structural Sybil bound — the open lane is exactly
K_OPENslots/epoch regardless of identity count, so a free botnet can never exceed 30 % of blocks. One-directional fail-closed empty-lane policy preserves the ceiling. - Fail-closed deterministic authorship —
validate_block_producer, called insideverify_blockbefore incorporation, recomputes the two-lane winner from parent state + the epoch beacon and rejects any block whose producer isn't that winner (block integrity is by deterministic recomputation, optionally authenticated by the detached winner signature below). - Objective stake-weighted heaviest-chain fork-choice — the canonical tip is
argmax cumulative_weightamong tips whose chain contains the node's finalized block, switching only on strictly-greater weight (lowest-hash tie-break). Peer IPs, trust, and uptime carry exactly zero weight, so a Sybil fleet of zero-bond IPs cannot reorg honest nodes. Replaces the old peer-IP plurality fork-choice. - Grind-proof
cumulative_weightheader — committed inside the block-hash preimage asparent.cumulative_weight + total_bonded_shares(as-of-parent) + 1. It is the total bonded registry weight (not the slot winner's share), so it is beacon-independent: a proposer can't grind the beacon to inflate fork weight. Recomputed inrebuild_blockand verified as-of-parent. The+1height term guarantees the weight is strictly increasing even when the bonded registry is empty: without it, a no-stake network advertises one frozen weight forever, fork-choice degenerates to the lowest-hash tie-break, and a stalled node whose tip hash happens to sort low considers itself canonical and never resyncs (observed live 2026-07-05; fixed at therelaunch-3genesis). Pure longest-chain while nothing is bonded, stake-dominated the moment anything is (shares ≫ 1). - Advertised-tip (weight) DoS hardening — a peer's advertised
latest_block_weightis only ever a hint: acting on it means fetching the blocks, whichverify_blockre-derives and enforces. A tip that was advertised heavier but could not be backed by a valid chain — the peer serves nothing, garbage, or forged blocks — is excluded (rejected_tips, bounded + auto-cleared ~30 s so a real tip that merely blipped is retried). The exclusion is honoured everywhere it matters: the emergency sync loop rejects the tip on every failure path and re-evaluates being-behind each pass, and the caught-up production gate skips rejected advertisements — so two forked-away or lying clients can cost the network ~one failed sync per 30 s, never a production stall or an emergency-mode wedge. Peer admission is additionally gated on an exactchain_idmatch in/status(a missing field counts as a mismatch), so nodes from a different chain/relaunch never enter the consensus pools at all — a foreign chain's frozen weight can't trip the caught-up gate. - Enforced finality floor — a block at height H finalizes everything at/below
H - FINALITY_DEPTH(FINALITY_DEPTH = 12); rollback refuses to cross the persisted, monotonic finalized height (raisesFinalityViolation). The orderingmax_rollbacks (10) < FINALITY_DEPTH (12) < EPOCH_LENGTH (60)means honest reorgs never hit the floor while a long-range reorg is capped below one epoch. - Corroborated finality + escalated re-anchor (partition-wedge recovery) — the depth floor now advances
only while the peer-majority tip lies on our canonical chain (
_depth_floor_corroborated); a node that briefly loses the majority (e.g. under load) and keeps producing on its own branch no longer self-finalizes that minority fork, so partition forks stay reorgable and heal through the normal weight-based reorg (a Sybil can only withhold corroboration — delay the floor, the safe direction). And if a node is ever wedged anyway (its snapshot/finality floor stuck on a minority fork below the heaviest chain's snapshots), afterREANCHOR_ESCALATE = 3cooldown-spaced failed re-anchors it drops the snapshot-above-floor restriction and re-anchors to the strictly-heaviest chain (every tail block still re-verified). Observed live and fixed; both are node-local safety/fork-choice rules (they change only when a node advances its own floor, never block validity). Unit-tested (tests/test_wedge_recovery.py). - Fail-loud epoch beacon —
epoch_beaconchains from the hash of the first block of the previous epoch (a finalized, non-parent anchor), and now raises instead of silently substitutingGENESIS_BEACONwhen the anchor is missing (a missing anchor means this node is under-synced). - Detached winner block signature — when the selected winner is online it attaches an optional ML-DSA signature outside the hash preimage (so it never affects the hash, weight, validity, or reward); verifiers reject a present-but-forged or wrong-signer signature. An offline winner's relay-built block is simply unsigned and still valid — "win-while-offline" is preserved.
- Equivocation slashing (both proof types) — slashing punishes double-signing, and covers two
distinct offences under one
resolve_slashpath: (1) block-authorship equivocation — two valid winner signatures over different blocks at the same height+parent; and (2) FFG-attestation equivocation — the same validator signs two conflictingattesttransactions for onetarget_epoch(same epoch, differenttarget_hash— a finality double-vote). Either forms a portable, unforgeable proof (only the key-holder can sign either message). A fee-exemptslashtransaction carrying the proof burnsSLASH_BOND_PENALTY(=B_MIN, one bonded share) of the offender's bonded stake. Anyone may report it (the proof is the anti-spam); it is replay-guarded to one slash per (offender, height) — attestation slashes are namespaced above real block heights so the two proof types never collide — revert-symmetric on rollback, and the coins are destroyed (the deterrent is the loss, not a bounty). Validation requires the offender still hold the penalty so the dock never floors. This punishes equivocation, not Sybil-ness — Sybil resistance is a separate mechanism (open lane capped atOPEN_BPS = 30%+ PoSW cost; bonded lane split-neutral with 10-NADO-locked shares). - FFG stake-attested finality (enforced) — bonded validators emit one
attesttransaction per epoch for that epoch's checkpoint (its first block). A checkpoint justifies when attesting bonded shares strictly exceed >2/3 (FFG_NUM/FFG_DEN = 2/3) of the active quorum, and finalizes on two-consecutive-justified; on-chainUNIQUE(validator, epoch)prevents on-chain double-voting (and a cross-fork double-vote is now slashable — above). The finalized checkpoint is folded into the persisted rollback floor (finalized_height = max(prev, tip − FINALITY_DEPTH, ffg_finalized)), so a2/3-attested checkpoint is objectively un-reorgable — remote sync cannot adopt a heavier conflicting chain. Inactivity leak: the quorum denominator is
active_shares— the selection shares of bonded validators that attested some checkpoint within the lastINACTIVITY_WINDOW = 3epochs — not all bonded stake. A validator that goes dark is leaked from the finality quorum (its vote lapses; its bond is untouched), so a live attesting majority always finalizes instead of being wedged by bonded-but-absent stake. This is what letffg_finalizedstart advancing despite many bonded-but-non-attesting dust accounts. Honest scope: FFG is epoch-granular and normally trails the depth floor, so confirmation latency remains the depth floor (~12 blocks) — FFG is the objective/long-range finality layer on top of the fast subjective floor, never a speedup, and (being layered on the always-advancing floor) it can never stall the chain. Exposed at/status.ffg_finalized. - Commit-reveal RANDAO (voluntary participation) — bonded validators
commita secret's hash in epoch E−2 andrevealit in E−1's finalized window;epoch_beaconmixes the finalized prior-epoch anchor with the revealed secrets, so no single anchor-producer controls the beacon. With zero reveals it falls back to the anchor-only value (liveness). It keeps the anchor (non-recursive), so the beacon stays snapshot-safe and the reveals are immutable by the time the beacon is needed. Participation is voluntary (RANDAO_ENFORCED = Falseinprotocol.py): every reveal that lands strengthens the beacon, but skipping the duty costs nothing and the bonded-lane draw runs over the full registry. The enforcement machinery (randao_eligible_bonded— no reveal for epoch E, no production rights in E — applied identically at production, relay rebuild, and verification) is implemented, unit-tested, and kept behind the flag; it was switched off because mandatory participation forces O(validators) commit+reveal txs every epoch and ties rewards to tx-inclusion latency, which scales poorly. Fork weight and the FFG/settlement quorums stay on the full registry in either mode, so withholding can't move fork-choice or stall finality. Both the node (maybe_randao) and the browser interface (for bonded wallets, while the tab is open) still contribute automatically. - Pubkey-once — the 1312-byte ML-DSA
public_keyis excluded from the txid and stored once in account state on an address's first tx, so later txs (notably every-epoch heartbeats) omit it; validators recover it from committed state. Store/clear is byte-identically revert-symmetric. - Reward recompute-and-enforce, registration-PoW enforcement, canonical in-block tx ordering (txid-sorted before hashing, so honest nodes selecting the same tx set produce an identical block hash).
- Anti-DoS / eclipse throttles — per-IP sliding-window rate limits on
/submit_transaction(30 req/60 s) and/announce_peer(10 req/60 s), plus the heavy unauthenticated read endpoints (/mining_status,/get_transactions_of_account,/get_blocks_after//get_blocks_before, added in the audit), a per-/16 peer-diversity cap (at mostMAX_PEERS_PER_SUBNET = 4peers per /16 — now enforced on the disk-reload path too, so one network can't fill a victim's peer view), a progressive per-range IP registration cap on OPEN-lane onboarding (crowding cost scales with subnet proximity, so a datacenter /24 gets one bounded budget —max_registrations_per_ip), a hard mempool cap (150,000), heartbeat-index GC, and an SSRF guard (check_iprejects own-IP and all non-globally-routable addresses).
The equivocation slashing (both proof types), FFG finality, and commit-reveal RANDAO above are wired
and unit-tested for correctness, and FFG now finalizes live (ffg_finalized advances past 0 once
the active bonded set attests two consecutive checkpoints). Their multi-node, adversarial,
epoch-crossing behaviour is still only lightly exercised empirically: the core loop's ~6 s/block
cadence makes exercising a full justify→finalize under contention and a complete commit→reveal cycle
slow. Treat their cross-epoch adversarial dynamics as not-yet-battle-tested.
- Broader eclipse hardening — beyond the per-/16 subnet cap and the
/announce_peerrate-limit (both already live): ASN-level (vs /16) peer-diversity caps, pinned anchor outbound slots, a multi-seed bootstrap list (replacing the single genesis seed), and snapshot-bootstrap binding to a finalized signed checkpoint. These are post-launch items.
Objective fork-choice, enforced finality, equivocation slashing (block-authorship + FFG attestation),
enforced FFG stake-attested finality, and the commit-reveal RANDAO make a zero-bond Sybil/IP reorg
ineffective, bound the disagreement window below one epoch, and layer objective finality and a
non-grindable beacon on top — a substantial
hardening over the previous peer-count fork-choice. Beyond the lightly-exercised cross-epoch behaviour
of FFG/RANDAO and the outstanding eclipse hardening above, the documented residuals from the audit
(see doc/security-audit.md) — none a theft or fork vector — are:
- RANDAO withholder penalty: CLOSED (2026-07-05). Reveal-for-the-epoch is now a hard eligibility
condition for the bonded-lane draw (see above), so suppression costs the withholder its entire
epoch of production. The residual is only the classic last-revealer bit: with
mcolluding withholders, up to2^mbeacon outcomes — each priced atmepochs of forfeited rewards, and defeated whenever ≥1 honest secret is revealed after the anchor. - FFG slashable-stake backing: CLOSED. FFG is no longer observational — the finalized checkpoint is
enforced in the rollback floor (objectively un-reorgable), and attestation-equivocation slashing
is live: a validator that signs two conflicting attestations for one epoch loses
SLASH_BOND_PENALTYof bonded stake via the sameslashpath as block-authorship double-signing. On-chain double-voting is still blocked by the per-epochUNIQUE(validator, epoch)marker; cross-fork double-voting is now punished rather than merely prevented. - The bonded
MAX_SHAREScap is per-identity, not aggregate — sharding capital aboveBOND_CAPacross addresses recovers full proportional weight. The bonded lane is capital-proportional by design; the cap only limits single-address variance, not aggregate stake. - Registration / fee-exempt state growth —
registerwrites an account doc; idle-account GC is implemented (ops/gc_ops.py): long-lapsed empty docs and ancient recert rows are swept deterministically in-block at epoch boundaries (revert-safe, snapshot-root-identical on every node). Also bounded by the lane cap, per-IP rate limit, mempool cap, and the in-block one-register-per-sender dedup; idle-account GC is future work. - Fidelity is continuity over recerts —
apply_registeradds a step for each continuous recert (gap ≤ the lease) and resets the streak on a lapse; it ramps the open bonus overFIDELITY_CAPrecerts. - Snapshot bootstrap trusts an 80%-of-peers quorum with no hardcoded finalized checkpoint cross-check (weak-subjectivity); a pinned checkpoint is future eclipse hardening.
All mining/economic parameters are provisional and flagged simulate-before-lock-in in code. NADO remains a testnet-stage alpha, not open-value-mainnet-safe. (No hardfork concern: mainnet is not live.)
- Signatures — ML-DSA-44 (FIPS 204, post-quantum) via
dilithium-py. Keys are a 32-byte seed from which the 1312-byte public key and ~2420-byte signatures are deterministically regenerated. Consensus only ever checksverify(sig, pk, msg) == True, never signature-byte equality, so hedged signatures interoperate across implementations. Signatures authenticate transactions/heartbeats and are deliberately never the randomness source (a malleable signature would be grindable). - Addresses —
"ndo"+ 42-hex public-key prefix + a 4-hexblake2bchecksum (49 chars). The keyless reserved recipients{bond, unbond, withdraw, register, slash, attest, commit, reveal, alias}are valid as a recipient/target only, never as a sender. - Aliases — a human-readable name → owner address, so you can send to a short name instead of the
49-char
ndo…address. Register / transfer / unregister are on-chain ops (reserved recipientalias); an ordinary transfer whose recipient is a registered alias credits the alias's current owner. Seedoc/aliases.md; the NADO Interface's Send field accepts an alias and its Receive tab manages them. - Multisig (opt-in) — native M-of-N shared accounts with no script language and no on-chain
setup: the address is the policy —
make_address(blake2b(["nado-msig-v1", M, members])), the way a P2SH hash commits a script. Fund it like any address; a spend carries the descriptor in the signed body plus a list of member signatures over the txid, so co-signers sign independently in any order and exchange the proposal by any channel (the Interface's Multisig tab and the CLI'smsig-propose/msig-sign/msig-submitimplement the relay). Multisig accounts are payment accounts only — they can't bond, mine or vote, so every one-key-one-identity validator assumption stays intact.ops/multisig_ops.py. - Hashing & serialization — BLAKE2b over
canonical_bytes()(compact, sorted-key, ASCII JSON, float-free). Every consensus integer is a raw integer, so a browser reproduces identical bytes with BigInt-aware serialization. Transaction ids and blocks bindCHAIN_ID = "alphanet-1", blocking cross-chain / pre-relaunch replay. - Wire — transactions submit over HTTP POST + msgpack (an ML-DSA-44 tx is too large for a GET URL); msgpack is wire/transport only and never the hashed preimage.
State lives in a single schemaless, memory-mapped, ACID key-value store (LMDB) — ops/kv_ops.py,
which replaced the prior SQLite index. Account/state records are schemaless msgpack documents
(no columns, no DDL), so adding a field needs no migration. A whole block's mutations (account docs,
tx index, block index, totals, heartbeats) commit in one write transaction, so a crash leaves a
block either fully applied or not at all, and replay is idempotent. Block bodies are zstd(codec)
records in append-only segment files under blocks/ (ops/segment_store.py — ~300 files/year
instead of one per block; crc-guarded, fsynced before their LMDB locator commits, torn tails repaired
at startup), and consensus hashing stays canonical JSON — neither is touched by the index.
Archive vs rolling nodes (opt-in history pruning). By default a node is an archive node
(config.archive = true) that keeps every block body forever. Set archive = false (or NADO_ARCHIVE=0)
to run a rolling/pruned node that drops finalized block bodies older than HISTORY_RETENTION_BLOCKS
(default 100 800 ≈ 1 week) while always keeping state and the number↔hash indexes — so it stays a full
validator and still serves the beacon/FFG lookbacks, with bounded disk. Retention is floored internally at
REWARD_WINDOW + FINALITY_DEPTH so pruning can never corrupt the reward calc or a legal rollback. This
keeps phones viable under adoption; see doc/rolling-mode-and-da.md.
Keys are post-quantum ML-DSA-44 (FIPS 204); what is stored is the 32-byte seed. Your ndo… address
shape is unchanged (49 chars).
- Linux:
~/nado/private/keys.dat - Windows:
C:\Users\<username>\nado\private
- Whitepaper —
doc/whitepaper.md: the authoritative, accuracy-reviewed overview of the mechanism, with a full constants table and an explicit implemented-vs-planned split. - Roadmap —
ROADMAP.md: where the app layer goes next. An honest gap analysis against the chains that actually generate app revenue (assets → AMM → launchpad → router → wallet swap → terminal), the on-ramp/fair-ordering/dev-surface tracks that run alongside it, and the rule that keeps it NADO-shaped: the protocol takes nothing, apps declare their fees on-chain, and the default sink is burn. - Consensus hardening plan —
doc/consensus-hardening-plan.md: the locked, ordered design for the remaining security milestones. - Storage design —
doc/storage-kv-migration.md. - Execution-layer instructions —
doc/exec-instructions.md: every blob op (deploy/call/upgrade/lock withvalueescrow, bridge/dividend, emit, privacy) with params + how to submit and read. Contracts run on a STARK-provable zkVM (the only runtime), authored in zkasm (execnode/zkvmasm.py); every call is provable (doc/zk-execution-proofs.md). Settlement is currently a bonded-stake quorum (settlement_justified, the same >2/3 shape as finality); a validity-proof path exists as capability but is not yet the authoritative settlement rule (see the recursion note below). 15 games ship as live zkVM contracts — coin flip, dice, roulette, slots, mines, blackjack, tic-tac-toe, Connect Four, reversi, chess, farkle, parimutuel sports betting, battleship, tamagotchi-NFT pets, and multiplayer Texas hold'em with an on-chain 7-card hand evaluator — each an ordinary upgradable contract (opt-in immutability vialock) with no game-specific API. - STARK recursion — succinct, K→1, O(1) verify —
doc/zk-recursion.md: the path to verifying an epoch of execution in constant time. Built + tested (off the L1 consensus path): a verifier-authoritative in-circuit STARK verifier over an algebraic hash (alghash2); T-independent ("succinct") verification (structured periodic columns — no per-proof O(T) work); a K→1 collapse (one recursion bundle re-verifies K chained segment proofs from their public parts alone); an in-circuit Fiat-Shamir transcript; and recursion depth (fold-of-folds) whose root verifies in ~0.2 s regardless of tree size — the O(1)-verification goal, realized. Honest status: the verify side is O(1) and the mechanisms are validated at small scale, but proving at the full execution-AIR scale is memory/throughput- bound in pure Python (the native Rust prover is the throughput prerequisite), and the pipeline is capability — not wired as the authoritative settlement verifier (the bonded quorum still governs). None of this touches L1 block validity. - Previous-network snapshot —
doc/previous-network-snapshot.md: the prior NADO network's final account balances (2,801 holders), exported from its ledger index — the dev-fund premine excluded, in keeping with the no-premine relaunch. - Release notes —
RELEASE_NOTES.md. - Project site: https://nadochain.com
protocol.py and the ops/ modules are the source of truth; where an older companion doc disagrees,
the code wins.
New functionality should be driven by the existing routines/loops rather than instant invocation of functions — every function should have its place in the routine responsible for it. Functions should be small, independent, and named after the small task they perform; prefer returning values to mutating objects passed as arguments. Use the existing compounder for multi-target loops rather than synchronous loops.
- Level III —
nado.pyruns all loops and governs API endpoints. - Level II — a central memory element,
memserver.py, holds shared state accessed by the main loops (consensus_loop.py,core_loop.py,message_loop.py,peer_loop.py). - Level I —
*_ops.pymodules (block_ops.py,account_ops.py,transaction_ops.py,mining_ops.py,kv_ops.py,peer_ops.py, …) hold minimal low-level operations.
A block is built with construct_block(), then produced via produce_block() →
verify_block() (with rebuild_block() to recompute hashes/weights for remotely received blocks) →
incorporate_block(). There is ONE mempool (transaction_pool): a submitted tx is validated and
enters it directly, and peers keep pools converged by set reconciliation — when advertised pool
hashes differ, a node fetches the peer's txid list (/transaction_ids, ~64 B/tx) and downloads only
the bodies it is actually missing (POST /transactions_by_id), never the whole pool.
Fork the repository, make your changes, and open a merge request.
Copyright (C) 2022-2026 Jan Kučera (hclivess).
NADO is free software licensed under the GNU Affero General Public License v3.0
(see LICENSE). The AGPL's network clause (§13) means that anyone who
runs a modified version of NADO as a network service must make their modified source
available to its users — you may fork and build on it, but derivative networks must
stay open. This replaces the project's earlier MIT license going forward; copies
already distributed under MIT remain under their MIT grant.
