Staking 6.2.4
Latest15 September 2026
This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.
Staking v6.2.4
Release Date: September 15, 2026 Tags: ON-CHAIN STAKING, CRON, RELIABILITY, FEES, SETTLEMENT, TREASURY
Overview
The six on-chain scheduled tasks could be dead while the cron console reported them completed at a 100% success rate. Both engines imported a helper module that ships with the Ecosystem extension rather than with this one, so on an install that does not carry that file the on-chain cron module never loaded — and a module that fails to import was recorded as a run that succeeded. Nothing was gathered, delegated, exited, claimed, returned or refunded, no reward was observed, and the reconciler and the alerting that would have noticed were inside the same dead module.
A fixed-rate early-withdrawal fee the platform could not collect was still taken out of the principal. The fee collector declines a charge when the payer is the Super Admin and when no Super Admin is configured. The deduction ran regardless, so the money left the staker and reached nobody.
Backend only; no schema change. Update Core first — the modules the engines now import live there.
Update Instructions
pnpm updatorEverything in this release is server-side and needs the backend restart. There is no new table, no seeder, no setting and no browser bundle to rebuild.
- Update Core first, in the same window. The two helper modules the engines
import —
utils/network-fee-tracker.tsandutils/withdrawal-outcome.ts— move into Core in the release that follows v6.7.8, and this release is what points the engines at their new path. On an older Core they are not at that path, the engine module will not load, and all six on-chain tasks refuse. Core also carries the refusal machinery itself, which is the half that makes the failure visible at all. - Then open Admin → System → Cron and read the six rows whose titles begin
On-chain staking:. Whatever they said before this release is not evidence that anything ran. See Upgrade Notes. - If you run the fixed-rate product, look for exits that were charged a fee nobody received before you reconcile this month's figures. What to look for is in Upgrade Notes. This release stops it happening again and restores nothing.
Upgrade Notes
Your on-chain cron rows were not evidence that anything ran
getStakingRealCronUtils() returned the same null for "this customer never
bought the addon" and for "the addon is installed and its module threw on
import". The six wrappers then returned undefined without throwing, the run
was recorded with no error, and the admin cron table showed six green rows with
a 100% success rate and a recent last-run time. A week of that table is
indistinguishable from a week of the engine working.
The one place the truth was written down is the backend log, once per import attempt:
CRON [IMPORT] Failed to load module @b/api/(ext)/staking/utils/real/cron:
Cannot find module '.../backend/dist/src/blockchains/network-fee-tracker'From this release a module that is installed and did not load sets a standing refusal on each of the six jobs. The rows read REFUSED, they stay refused between ticks because it is a state and not a run result, and one URGENT alert goes out per broken module — not per job. A module that is absent stays silent, because a customer who never bought the addon is not suffering a fault; six green rows on an install without the addon still mean nothing ran. The rest is under "The on-chain cron rows read REFUSED" in Troubleshooting.
Work that was skipped is not lost, and it is not replayed by this release
either. The batch runner takes gathers, delegations, exits, claims, returns
and refunds oldest first, so a queue that built up while the module was dead
drains on its own once it loads. The Solana observer reads every epoch since the
pool was last observed and then squares the book against the chain, writing any
residual the epoch figures do not explain as its own adjustment window. What you
should not assume is that the position states you are looking at are current:
run the reconciler by hand once the rows are green
(POST /api/admin/staking/reconcile, permission edit.staking.incident) and
read the incidents it opens rather than waiting for the hourly pass.
An exit may have been charged a fee that reached nobody
On a live install a Super Admin exited a 1,000,000 PBX position early and was
returned 900,000. There is no EARLY_WITHDRAWAL_FEE transaction for the missing
100,000 and no stakingAdminEarning row against that position, because the fee
was never collected — it was deducted from the principal and credited nowhere. A
second position the same minute lost a further 100.
That is the fingerprint to look for: a settled position whose principal was reduced by an exit fee, with no fee transaction and no admin-earning row carrying its id. It happens in exactly the two cases the collector refuses — the staker is the Super Admin, and no Super Admin is configured at all. The first is the one that was observed; the second would apply to every staker on an install in that state.
The reward path carried the mirror of the same mistake, against the treasury
rather than the staker: it funded the yield as though the fee had been collected
when it had not. Neither is reversed here. Both are corrected going forward only,
so any adminProfit figure you have already published for an affected period is
out by the fee slice, and the wallet balances behind it are what they are.
The 6.2.3 note was wrong about switching the addon off
The v6.2.3 notes said that if you acknowledged the warning and switched the
addon off, exits really would stop until you switched it back on. They do not.
processRealStakingBatches is flagged returnsCustomerMoney, and a job with
that flag is scheduled on the disabled branch as well as the enabled one, so
it keeps claiming unbonded coins and sending them home while the addon's screens
are shut. That was already true when 6.2.3 published; the note has been
corrected, and the in-app dialog says it plainly.
Two of this addon's seven jobs carry the flag — the batch runner and the
fixed-rate processStakingPositions. The observer, the reconciler, the
validator refresh, the alerting and the monthly statements all still stop, so a
disabled addon completes exits and tells you nothing about them. What does bring
the batch runner down is stopEverything on the addon toggle, which is a
deliberate hard stop and strands anything in UNBONDING until you lift it. The
eleven jobs across the platform that survive a disable are listed in
Core v6.7.6.
Fixed
All six on-chain tasks were dead and the cron console reported success
Both engines carried a top-level
import { recordBurnedNetworkFee } from "@b/blockchains/network-fee-tracker".
That file is not core: it arrives with the Ecosystem extension, which shipped
it in 6.5.0. This addon declares a dependency on the core product only, so on an
install without Ecosystem — or with a copy of it older than 6.5.0 — the file is
not on disk at all. The import chain has no slack in it:
cron/index.ts safe-imports @b/api/(ext)/staking/utils/real/cron, which
imports ./register, which imports both engines. One missing file took all six
jobs down, and the collapse of "not installed" into "failed to load" at the
safe-import boundary is what let them report success anyway.
- Fixed by moving both shared helpers into Core, beside the five chain
helpers already there, and pointing the Solana and Lido engines at
@b/utils/network-fee-tracker. Nothing in this addon now imports a path that belongs to another product. A re-export is left behind at the old location for one release cycle, so an install that takes a rebuilt Core alongside an un-rebuilt chain zip still resolves. - Fixed, in Core, the reporting: a job whose module is installed and failed to load is now announced as a refusal instead of recorded as a completed run.
- The static imports in
real/register.tsare deliberate and are documented as such in the file. A per-enginetry/catchwould let a broken Lido leave Solana's exits running while all six rows reported success again — the exact failure this release removes. Per-engine isolation is only correct together with a degraded notice naming the missing engine, and that does not exist yet.
An early-withdrawal fee the platform could not collect was still deducted
Settlement credited principal - fee and only then asked the collector to take
the fee. The collector refuses two cases outright — the payer is the Super Admin,
which is a circular charge, and there is no Super Admin to pay — and returned
without taking anything, while the deduction had already happened. The fee did
not stay with the house; it was destroyed.
- Fixed so settlement asks
decideFeeCollectionfirst, with the same inputs the collector uses, and waives the fee when the answer is no. The full principal is returned instead, and a line naming the position and the reason is written to the log. - Both sides ask the one shared decision function, so they cannot drift apart and decide differently.
- Fixed-rate only. An on-chain exit has no early-withdrawal fee; it settles at
min(request price, final price)and its commission is minted as treasury shares, not charged here.
An uncollected fee was booked as money the treasury had paid
The reward path has two legs: the fee the platform keeps, and the platform loss that funds the yield. The loss leg added the fee back into the gross unconditionally, so when the collector declined the fee the treasury was debited for money it never received — on every accrual, for as long as the position lived. What the platform actually parts with is the net paid to the staker plus only a fee it really kept.
- Fixed
bookStakingPlatformFeeto return whether the fee actually reached the Super Admin wallet, and both callers — the per-period accrual and the auto-compound maturity booking — to add the fee into the loss leg only when it did. - The fee leg was always right. It is the loss leg that assumed a fee it had not checked, which is why the two figures could disagree.