Skip to content

How the ImpactZero restake bot works, from the code

Last updated Written by the ImpactZero validator team

In short

ImpactZero's restake bot runs every minute on our server: it reads the validator wallet's balance, splits it across every staker record in proportion to stake, and sends each share back as an add-stake transaction. Every distribution is an on-chain transaction you can look up, and the code is ours to explain.

What "restake" means when a validator says it

Nimiq has no protocol-level restaking switch. A validator that declares a restake payout is describing its own off-chain policy: when rewards arrive, it sends them back into staker records instead of paying them out as plain transfers. Each distribution is an ordinary add-stake transaction signed by the validator, so the policy is visible in the chain even though the code behind it is not. This article explains the code behind our policy, from the actual script that runs on our server.

The bot is one file, one cron line, and no databases. Everything it knows comes from the chain: the validator wallet's balance, the validator record's total stake, and the list of staker records with their balances. Every minute it asks those three questions and, when there is something to move, moves it.

The pipeline, minute by minute

The script runs on a one-minute cron schedule. A fired cycle does this:

  1. Take the lock. The first line of business is a file lock, so two cycles can never overlap even if cron double-fires.
  2. Run the sentinel. The bot reads the validator's recent inbound transactions and looks for anything that is not a coinbase reward and not a staking contract transfer. Such a transfer is presumed to be an accidental send and is held, never distributed. Details in the sentinel section below.
  3. Read the balance. The validator wallet's balance at the validator address. If it is below 0.02 NIM the cycle stops before spending anything.
  4. Read the validator total. The validator record's balance: the 100,000 NIM deposit plus every NIM delegated to the pool.
  5. Compute the available amount. The wallet balance minus anything held by the sentinel. There is no reserve: transaction fees on Nimiq are zero today.
  6. Split it proportionally. For every staker record, the share is elected stake x available / validator total, where the elected stake is the smaller of the record's balance at the start of the epoch and its balance now (the epoch-start balances are written to a snapshot file at the first cycle of each epoch, before anything is paid). A share that rounds down to zero lunas is the only one skipped.
  7. Send one add-stake per staker. Each share is sent from the validator wallet to the staker record with a stake transaction, so the NIM lands back in the staker's active balance.
  8. Log and flag. Every transaction is logged with its hash, and a flag file marks the cycle as done so the monitoring bridge can report it.

The constants, from the code

The thresholds are not buried: they sit at the top of the script as named constants, and this is the whole set:

MIN_TRIGGER         = 2000      # 0.02 NIM: below this, a cycle sends nothing
RESERVE             = 0         # no wallet floor: transaction fees are zero
HOLD_SINCE          = 61600000  # only inbound transfers after this block count
ELECTION_FROM_EPOCH = 1432      # pay on elected stake from this epoch on

Two of those matter to you directly. RESERVE is zero, so every luna the validator earns goes back to stakers; the lunas an integer split rounds away simply ride the next cycle. ELECTION_FROM_EPOCH is the switch to election pricing: from epoch 1,432 on, stake added mid-epoch starts earning at the next epoch, when the protocol counts it, and deactivated stake stops earning immediately.

A real cycle, worked through

The ledger logged a typical cycle at 20:24:02 UTC on 16 September 2026, under the live balance rule then in force; when no stake changes during an epoch, the election rule gives the same numbers. These are the numbers it read, and the shares it sent (stakes are the cycle-time balances to the nearest whole NIM; at this precision the arithmetic reproduces every logged share exactly):

Staker recordStake (NIM)Share that minute (NIM)
NQ12 A0LU ...25,000,5424.92833
NQ27 NCB1 ...3,412,3860.67268
NQ30 7D50 ...2,000,1130.39428
NQ87 4C1K ...57,1710.01127

The validator total that cycle was 30,574,558.30 NIM and the available amount was 6.02713 NIM. Check the largest row: 25,000,542 x 6.02713 / 30,574,558.30 is 4.92833, which is exactly what the ledger logged. The same arithmetic reproduces every other row, and the same formula runs every minute. Bigger cycles happen whenever a slot pays out; the 19:12 UTC cycle the same evening swept a 181.83 NIM payout in one go, 20.29391 NIM of it to NQ27, the pool's first large staker.

Every share is a real transaction. The 0.67268 NIM to NQ27 NCB1 ... went out as a stake transaction with hash e474484b6365f53064a373a909d47f5aad9085b1302bf69d17e968d92e3b352d, signed by the validator wallet. Paste that hash into the explorer and you can watch the amount land on the staker record.

The 100,000 NIM deposit: its rewards go to stakers too

The validator total includes the deposit, and the split divides by that total, so in any single cycle the deposit's slice is paid to nobody: there is no staker record to send it to. It stays in the validator wallet, and the next cycle splits it across the stakers along with the new rewards. Follow that loop and nothing is left behind. Stakers receive every NIM the validator earns, the deposit's share included, and the operator keeps none of it.

The ledger shows it. In epochs 1,424 to 1,430 (23 to 26 October 2026) the validator mined 40,384.56 NIM and the bot restaked 40,381.60 NIM; the small gap is the last minute's rewards waiting for the next sweep. Had the deposit's slice been kept back, stakers would have received about 0.21% less, roughly 86 NIM over those seven epochs.

So the deposit adds a little on top of your own share: about 0.2% more than your stake alone would earn, with 100,000 NIM beside roughly 46.6 million NIM of stake in October 2026. That is 0.2% of your rewards, not 0.2 percentage points of yield, and it shrinks as the pool grows (it was about 0.33% at 30.6 million NIM in September). The deposit also adds its weight to the pool's election chances, and it is skin in the game: 100,000 NIM that the protocol releases only through a slow, multi-step exit.

The sentinel: what the bot will not touch

Rewards arrive from the coinbase address and delegations move through the staking contract. Anything else landing in the validator wallet is, by definition, not a reward: likely an accidental send to the validator address. The bot treats that class of transfer as held: it records the transaction in a hold ledger, subtracts it from what a cycle may distribute, and raises a flag so a human reads it. It is fail-closed by design. If the chain read fails twice in a row, the cycle is skipped and flagged rather than run on stale guesses. A silent skip is never invisible, and a mis-send is never swept.

Read the source, then check the receipts

The script runs on our server, and the exact code is published on GitHub, under MIT, together with the watchdog that guards the node. You can read the constants, the sentinel, the retry wrapper, even the bug that taught us not to pass large RPC payloads through environment variables. And the honest test still stands: every distribution is an on-chain transaction, every hash is public, and the arithmetic above reproduces from the same two chain numbers the bot reads. The code is open; the receipts stay on the chain.

Questions people ask

How often are rewards restaked?
The bot runs once a minute on a cron schedule, and every cycle re-reads the chain itself. A cycle only sends anything when the validator wallet holds at least 0.02 NIM, so a quiet week means many cycles do nothing. When a cycle does fire it takes a few seconds: one read of the balance, one read of the validator record, one read of the staker list, then one transaction per staker.
How is my share of a cycle computed?
Proportionally to your elected stake: the smaller of your staker record's balance at the start of the epoch and its balance now, divided by the validator total, multiplied by whatever the cycle distributes. This election rule applies from epoch 1,432 (27 October 2026); before that the bot used your live balance. Every staker with a share of at least one luna is paid every cycle. The inputs are all on chain, so you can reproduce any distribution yourself.
Does the validator deposit earn rewards for stakers too?
Yes. The deposit earns like any other stake, but it has no staker record, so its slice stays in the validator wallet and the next cycle restakes it to stakers in proportion. Over time stakers receive everything the validator earns: in epochs 1,424 to 1,430 (23 to 26 October 2026) the validator mined 40,384.56 NIM and the bot restaked 40,381.60 NIM to stakers. With 100,000 NIM of deposit beside about 46.6 million NIM of stake, that is about 0.2% more than your stake share alone would earn: 0.2% of your rewards, not 0.2 percentage points of yield, and it shrinks as the pool grows.
What happens if the bot fails?
Two built-in protections. If an inbound transfer to the validator looks like a mis-send rather than a reward, it is held, ledged and flagged instead of being distributed. If a chain read fails twice in a row, the cycle is skipped and a flag is raised, so a silent skip is never invisible. Rewards are never at risk: they sit in the validator wallet until a later cycle moves them.
How do I verify that a restake happened to my staker record?
Every distribution is a real add-stake transaction on the Nimiq chain, signed by the validator and moving from its wallet to your staker record. Each log line carries the transaction hash. Paste that hash into the explorer on this site or any Nimiq block explorer and you will see the amount, the sender and your record as the recipient.

Sources