Mining
Proof of work with RandomX, on the processor already in your computer. No ASIC farm to compete with and no premine — coins come from mining and nowhere else.
The whole of it#
# A payout address. The node never sees the key behind it.
zcd wallet new --out miner.json
# Mine to it.
zycordd --testnet --dir ./testnet \
--mine --payout $(zcd wallet address --key miner.json)
On the public testnet and on mainnet those are the -randomx binaries —
zcd-randomx and zycordd-randomx. A binary built without the tag refuses
to start rather than falling back to the development engine. See
Installation.
Start it whenever you like#
A node started before its network's first block is due does not mine early and does not need to be started again at the hour. It refuses to build a block whose timestamp its own clock has not reached, waits, and begins on its own. It says so while it waits, with the time it is waiting for and how long is left:
waiting to mine: the next block cannot be dated before 2026-10-01T00:00:01Z
(unix 1790812801), and this node's clock reads 2026-09-30T20:12:31Z - 3h47m30s
to wait. Leave this running: mining starts on its own and there is nothing to
restart or reconfigure.
That is a refusal to mine, not a refusal to run: the node peers, syncs and serves its RPC throughout. The message repeats every ten minutes so a long wait does not look like a hang.
The wait is computed against this machine's clock, so a clock set days behind waits days. Check it before you check anything else.
That refusal is also why it is safe to publish a start time at all. Without it the miner dated
its header forward to the median floor instead, which before genesis is genesis_time
itself — so an early start produced a private chain nobody else could judge and, because the
difficulty rule measures declared solve times, one whose target hardened every block
against intervals that had nothing to do with real elapsed time. Nobody had to be dishonest for
that; it is what leaving it running overnight used to do.
The payout address must be persistent#
It must be a persistent (0x02) address — zcd wallet
address prints one by default — and zycordd refuses anything
else rather than warning. The node never sees the key that controls it, and could not
spend the reward if it wanted to.
A one-shot (0x01) payout mines correctly right up until you spend from it once.
That debit marks the address spent forever, and the fold burns every maturing reward addressed to
it from that same block on — the maturity ring rolls after the block's certificates land, so
the block carrying the spend is already the first block that loses money, and whatever of your
last coinbase_maturity blocks is still in the ring goes with it.
Nothing errors. The only trace is on that node's own block line:
matured=0, which is also what an ordinary empty ring slot prints, and a
burned= that has quietly absorbed the whole producer share alongside the base and
skip fees it normally reports. Neither field says "coinbase". A persistent address can never enter
the spent registry at all, so the failure is removed rather than narrowed — see
wallet rule 3.
Coinbase maturity#
Rewards mature after coinbase_maturity blocks — 100 on both
mainnet and the public testnet. Until then they are visible in state and unspendable, which is
what funds the first deposits on a chain with no premine: for roughly the first hundred
blocks, only miners can transact.
Mainnet has no faucet and will not have one, so on mainnet that ramp is the whole of the distribution: mine to play. The public testnet has a faucet, funded by mining like everything else on it, because a network whose coins are worth nothing loses nothing by letting somebody rehearse a wallet before they rehearse a miner.
What it costs to run#
The work function is a consensus parameter, not a setting. zcd
params prints it:
proof of work randomx-v1 (re-keyed every 2048 blocks, 64-block lag)
Memory#
Verifying needs about 256 MiB per key epoch held, and the table holds two so that a reorg across a key boundary does not rebuild one per block. Budget more than that table, because the table is not the bound. A cache is resident from the moment it is allocated and the Argon2 fill that makes it expensive comes after — so up to two more are live while they are being built — and an entry evicted while something is still using it keeps everything it has until that user lets go, which is what a miner does across a dataset fill.
| Role | Budget | Why |
|---|---|---|
| Verifying node | ~1 GiB for the engine | Peak cache against the two-entry table was measured at 1280 MiB with both effects at once — not the 512 MiB the table suggests. |
| Mining node | ~3.3 GiB | Additionally allocates the ~2 GiB dataset, and is the case that pins a borrower across the fill. |
A machine that can verify comfortably cannot necessarily mine.
Threads#
--mine-threads defaults to one per core. It changes nothing about validity; it is
how fast this node looks for a solution.
What happens at a key change#
The work function is re-keyed every randomx_key_interval blocks. A mining node
rebuilds the ~2 GiB dataset then and stops mining while it does — order of
ten seconds, machine-dependent; measure yours from the first boundary in the log.
It keeps verifying throughout, and a rebuild cannot stall it: a header from any
key epoch other than the one being mined is verified on that epoch's own 256 MiB cache, which the
dataset does not touch. The two compute identical digests, so the only difference a rebuild makes
to verification is speed. The next epoch's cache is warmed over the last
randomx_key_lag blocks of the epoch.
| Network | randomx_key_interval | Mining pause, roughly |
|---|---|---|
| mainnet | 2048 blocks | one every ~17 hours |
| public testnet | 512 blocks | one every ~4 hours |
The testnet's shorter interval is deliberate: rehearsing that boundary more often is part of what the network exists to measure. Building the next epoch's dataset in advance is possible — the key comes from the height, so it is known well before the boundary — and is deliberately not done: it means holding two of them, 4 GiB, to save a pause that costs a miner a few seconds a day.
Stopping a mining node#
SIGTERM (or Ctrl-C) stops it, and it stops promptly: one signal
reaches every loop, not whichever one happens to be waiting on the channel.
A stop that lands during a key change waits for the dataset fill to finish
— the engine will not release a ~2 GiB buffer while hash calls are still reading it, which
is a use-after-free in C that no Go tooling would report. It is a stop waiting for a fixed event
rather than one racing it: measured over ten runs, the exit instant did not move however early the
signal arrived, while the wait shrank from 4.5 s to 0.7 s, with no SIGSEGV.
So budget a stop timeout above your machine's fill time. A second SIGTERM
does not wait — the first one hands the signal back to the operating system, so
pressing Ctrl-C again during that pause kills the process immediately.
Pools, and why there are none#
None exist at launch and none are official. The node solo-mines out of the box, and at genesis difficulty that is the sensible way in. If pools appear they are third parties; apply the scepticism you would anywhere.
Botnets, named rather than denied#
CPU mining invites them. Every CPU-minable coin has fought them and this project does not expect to be the exception. The trade is taken with open eyes: a distribution skewed by botnets is still broader than one decided in a sale, and keeping the laptop you already own competitive is the entire reason RandomX exists.
Watching it work#
The testnet explorer renders every block and every certificate,
including the page no other chain's explorer can render: whether each certificate was applied, or
skipped and billed, and which declared read stopped holding. Your own node answers the same
questions on /status, /head and /metrics — see
the read-only surface.