Staking 6.1.5

29 July 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

CRITICALMONEYSECURITYKYCSCHEDULINGANALYTICSADMINFRONTENDDESIGNBREAKING

Staking v6.1.5

Release Date: July 29, 2026 Tags: CRITICAL, MONEY, SECURITY, KYC, SCHEDULING, ANALYTICS, ADMIN, FRONTEND, DESIGN, BREAKING

Overview

Version 6.1.5 is a correctness release for the staking addon, following a full review of the money flow, reward mathematics, scheduling, position lifecycle, admin operations and both the user and admin screens.

The review found that staking did not function end to end. Rewards never accrued, matured positions never returned their principal, and several money paths silently produced nonsense.

It also enforces verification requirements on staking for the first time. The levels configured for staking investment and staking withdrawals were only ever checked in the browser; they are now enforced on every stake, every claim and every withdrawal. Users below them are blocked from the moment you update — including users who staked earlier and now need to claim or withdraw. Review your staking verification levels before updating.

Every staking screen has also been rebuilt on the platform design system, so staking follows your site theme instead of a fixed violet-and-indigo palette. Expect a materially different look after updating.

Requires Core v6.5.8.

Update Instructions

pnpm updator

Post-update: rebuild the backend so the model and route changes take effect, then restart.

One nullable column is added to the staking positions table, holding the early-withdrawal fee. Models auto-sync, so no migration file is needed, but run npm run types:generate in the backend if you build from source. Existing rows are backfilled lazily on their next accrual.

No other migration is required. The external-pool performance table already existed and is unchanged. If the new Performance history card returns empty for an external pool, verify the admin permission for /api/admin/staking/performance is seeded.


Upgrade Notes

  • A pool's available capacity is no longer editable directly. Use the new capacity adjustment field instead. Setting it directly is now ignored rather than silently corrupting the accounting.
  • Automatic earnings distribution now defaults to on. Operators who intend to distribute manually must switch it off explicitly. Maturity settlement runs regardless of this setting.
  • Staking verification levels are now enforced everywhere. Users below the level you configured for staking investment or staking withdrawals can no longer stake, claim or withdraw — including users who staked before the update. Review those levels before updating.
  • Admin manual distributions are recorded as a bonus, separately from automatic rewards. No historical data needs editing.
  • The admin position list is now capped and returned a page at a time. It returns at most 100 positions by default, and up to 500 when more are asked for, as a page rather than as one complete list. The admin staking overview still works its figures out from what comes back, so on an installation with more than 100 positions the Active Positions counter, the Total Earnings tile and the Withdrawal Requests figure now describe only the 100 most recent positions instead of all of them. Anything you have built outside the product that reads that list also needs updating, because it now receives a page rather than a plain list.
  • The bulk earnings distribution action needs a different permission. The bulk APR accrual catch-up — the admin action that brings a pool's active positions up to date on earned APR — now requires the staking earnings create permission instead of the general staking management permission, matching the one-off distribution action beside it. Both write staking earning records, so they should never have been reachable through different permissions. A role granted staking management but not staking-earnings creation loses this action and will see a permission error; re-grant it after updating if any of your admin roles rely on it.
  • Existing analytics figures keep their names and meanings; only their values become accurate.

Added

Features that were built but never connected

Three pieces of the staking product were fully built and never shown on any page.

Token showcase on the staking landing page

The landing page already calculated per-token statistics — pool count, best and average APR — and already fetched them, then never displayed them. The per-token grid now appears beneath the pool-variety section.

The token cards link to a filtered pool list, but the pool list ignored the link entirely — and even if it had not, it was comparing the wrong two things. Both halves are fixed.

Performance history for external pools

External pools are staked off-platform, so the platform cannot observe their real APR or profit; an admin records each period by hand. The storage and both admin actions already existed, and nothing anywhere could read or write them.

The pool analytics tab now has a Performance history card with a record list and an entry form (date, APR, total staked, profit, notes). It appears only for external pools: internal pools calculate performance from real activity, and a manual form there would let an admin record figures that contradict it.

Period-over-period comparison

The admin dashboard now compares each metric against the immediately preceding window of equal length:

  • Staked Volume: total staked per period.
  • Position Count: positions opened per period.
  • Distinct Participants: users who opened at least one position.
  • Rewards Credited: rewards paid out per period.

Changed

Every staking screen was rebuilt on the platform design system

The staking area looks materially different after this update.

  • Public landing page: rewritten from the generic page-builder sections onto the shared landing layout, with new artwork, a new section order, and rewritten trust, feature and step copy.
  • Admin pool analytics: the charts moved to the shared chart components, with a new visual language and proper legends.
  • Admin tiles and badges: statistic tiles and status badges now use the shared components, so they match the rest of the platform.
  • Colour: the fixed violet, indigo and grey palette was replaced with theme colours throughout — over 500 hardcoded colour values removed — so staking now follows your site theme, including a custom palette.

Apart from the rewritten landing-page copy, no figures, controls or page addresses changed. This is an appearance change, not a behaviour change.


Fixed

Critical

Auto-compound maturity destroyed the staked principal

Money values were being handled as text rather than as numbers, so adding a reward to a principal produced nonsense instead of a total.

At auto-compound maturity that nonsense flowed into the payout. The credit was skipped, so the principal was never returned — while the position was still marked complete. In other cases the payment failed and retried forever, leaving the position stuck as active.

Fixed at the source: every money value in the staking addon is now handled as a number.

This single fix resolves an entire class of defects across the addon: concatenated totals, capacity bars stuck near 0%, nonsense distribution shares, and crashes on the dashboard, positions page and public landing page.

The reward schedule was disabled by a setting nothing creates

The hourly reward run began by checking a setting that no installation ever creates — and treated its absence as "off". So on a stock install the entire reward run did nothing, every hour, forever. The live database confirmed it: positions that had matured months earlier were still active, with nothing ever distributed.

Worse, that same switch also gated maturity settlement — so an operator turning off "automatic earnings distribution" also silently stopped returning principal at the end of the lock period.

Fixed:

  • Maturity settlement now always runs. Once a position reaches its end date the agreed lock has expired; returning the principal is an obligation, not a configuration choice.
  • The setting now governs periodic rewards during the term only, and defaults to on.
  • The admin screen's default was changed to match, so the interface and the engine no longer disagree about what is configured.

Stale backup files were being served as live pages

Leftover backup copies of source files were being registered as live handlers, racing the real ones — so which version served a request was unpredictable on every restart.

This was platform-wide, not staking-specific. 19 stray files were being registered, including stale versions of money screens: withdrawal approval, deposit approval, transfers, wallet withdrawal rejection, P2P trade cancellation and bulk staking position updates, among others.

Fixed: only real source files are registered. All 1,622 legitimate pages are unaffected; the 19 strays are now ignored.

Security

Staking verification levels were never enforced

The verification level you configured for staking was checked in one place only — the stake form, in the browser. The stake request itself accepted anyone, at any level. Claiming rewards and withdrawing had no check at all, anywhere: not on the screen, not on the server.

Fixed: the configured level is now checked on the server for every stake, every claim and every withdrawal, and a request below it is refused. On the screens, the position cards and the position details dialog now replace their Claim and Withdraw buttons with a verification notice, so a user is told why the action is unavailable instead of being refused after pressing it.

Applies to installations with identity verification enabled. If you configured staking verification levels and never saw them applied, users below those levels are blocked the moment you update — including users who staked before the update and now need to claim rewards or withdraw principal. Review your staking levels before updating.

Money-flow corrections

The books recorded the fee the platform kept, never the yield it paid

Staking revenue only ever recorded the admin fee. A 20% fee on a reward of 100 was booked as +20 profit — while the platform had committed to paying out the whole 100 and keeping 20, a real result of −80. Auto-compound maturity was worse: the entire term's yield had no offsetting entry at all.

Fixed: the gross yield is now booked as a platform payout alongside the fee, at the same moment and against the same period, so staking profit figures net out to what actually left the treasury.

Fixed: cancelling a position now reverses the payout booked for rewards that were forfeited and never paid. Previously that entry stayed on the books forever, understating profit by the forfeited amount.

The early-withdrawal fee was never charged

No path in the product could reach the fee: the user withdrawal screen only settled after the lock expired, and every admin action settled in a way the fee did not apply to. Your configured early-withdrawal fee was dead in all cases.

Fixed. The fee now applies, and is priced from when the user asked to leave, not when an admin happened to process it. Previously an early request processed after the end date paid nothing — the slower the review queue, the cheaper the early exit.

Approving an early withdrawal confiscated the staker's rewards

A withdrawal awaiting approval was a dead end. The only action available to an admin was cancellation — which forfeits unclaimed earnings. "Approve this withdrawal" and "confiscate the rewards they already earned" were the same button, and both the approve and reject buttons failed anyway.

Fixed:

  • Approving an early withdrawal now returns the principal, applies the early-withdrawal fee, and leaves earned rewards claimable.
  • Rejecting one is now a real action: it clears the withdrawal request and resumes the original lock, with a notification explaining the decision.

Final settlement destroyed the last reward period

Any reward earned since the last payout was destroyed at settlement — up to a full period, and for end-of-term pools the entire term. A user withdrawing the moment their lock expired lost it every time.

Fixed: the outstanding reward is settled before the position closes, on every path. A repeat cannot double-pay, and a failure here never blocks the principal return.

Pools with a 100% admin fee collected nothing

When the entire period went to the platform, the run treated it as "nothing happened" and discarded the fee it had just booked.

Fixed: a booked fee counts as real work. The "you earned…" notification is suppressed when the user's share is zero, rather than announcing a reward of 0.

Editing a pool re-minted staked capacity

A pool's available capacity is live accounting — every stake reduces it, every settlement restores it — but the admin edit form loaded it as an ordinary field and wrote it straight back. Saving any unrelated change reverted all capacity consumed since the page was opened, and each later settlement then added its principal back on top, compounding the error.

Fixed: available capacity can no longer be overwritten by an edit. Deliberate capacity changes use a new adjustment field that applies safely alongside concurrent stakes and cannot drive capacity negative.

Admin "Distribute Earnings" credited nobody while still booking the fee

Every staker's share calculated as nonsense, so nobody was credited — while the platform fee was still booked and the distribution cycle consumed.

Fixed: the totals are checked before anything is written, each share is validated, and the whole distribution either completes or does nothing.

Separately, the platform's revenue was recorded before the fee was actually collected, and the result was ignored — so the platform recorded revenue it never received. It now collects first and records only on success. Distribution amounts also round to the currency's own precision instead of a fixed 4 decimals.

Manual admin earnings creation validated nothing and left no audit trail; it now does both.

Admin distributions silently stopped a staker's rewards

A one-off admin bonus was recorded the same way as an automatic reward, so the engine treated it as an advance against future rewards — silently suppressing the staker's real accrual until it caught up, turning the bonus into a no-op.

Fixed: manual distributions are additive, as intended, and are recorded separately from automatic rewards.

Locked positions could be repriced after the fact

The early-withdrawal fee was read live rather than locked in at stake time, so raising it repriced the exit cost of positions whose holders had already committed their funds. It is now locked in with the rest of the terms.

Separately, positions created before terms were locked in fall through to the current pool values, so any pool edit retroactively repriced their whole term — and an APR cut could freeze that staker's rewards permanently. Those positions are now pinned to the terms in force at their next reward, which stops any further drift.

Editing a pool with a 0% fee reset it to the platform default

A pool deliberately configured with a 0% admin fee, or a 0% early-withdrawal fee, opened in the admin edit form showing 10% and 5% — the platform defaults. Saving any unrelated change on that pool then wrote those defaults back, silently repricing the terms for everyone staked in it.

Fixed: a zero stays zero. This matters more from this release on, because the early-withdrawal fee is now actually charged.

Deleting an in-flight position stranded the principal

Bulk delete had no guard, so removing an active position destroyed the only record of money the user had staked — never returned, capacity never restored. The single-position delete already prevented this; the bulk one did not.

Fixed: positions holding staked principal must be settled first. Separately, bulk pool deletion required only edit permission; it now requires delete permission, matching the single-pool action.

Archiving a pool made earned rewards unclaimable

Archiving a pool made every claim against it fail permanently — including rewards already earned and owed.

Fixed: already-earned rewards remain claimable after a pool is archived.

Smaller corrections

  • A long pool name could abort an entire earnings distribution. The description generated for each position during a distribution includes the pool name, and a pool named at roughly 150 characters or more pushed that text past the space reserved for it — the whole distribution then failed, with no staker credited and no fee booked, every time, on every attempt. The generated description is now trimmed to fit, and such pools distribute normally.
  • Manual admin earnings now say plainly that they move no money. Recording one confirmed that the earning "has been added" and was "created successfully", which an admin could reasonably read as the platform having actually been paid. It is a bookkeeping entry: no wallet is credited and no money moves. The confirmation now reads "Bookkeeping entry only — no wallet was credited by this record", the created record is explicitly marked as having credited no wallet, and the action's own description states it.

Correctness and disclosure

The reward calculator overstated every quote

The estimator used simple interest regardless of your configured method, ignored the pool's admin fee entirely, and projected over whatever period the user typed rather than the pool's actual lock period.

Fixed: it now quotes through the same calculation the payout uses, and shows the gross reward, admin fee, net reward, term and method separately — so the figure shown is the figure paid.

Internal admin notes were shown to the position holder

Internal moderation notes were included in what the user could see. Removed.

Settings that did nothing

Six of the eight staking settings were saved by the admin screen and read by nothing. They are now wired up:

SettingNow does
Default admin feeSets a new pool's admin fee
Default early-withdrawal feeSets a new pool's early-withdrawal fee
Auto-compound defaultSets a new pool's auto-compound option
Minimum withdrawal amountEnforced when a withdrawal is requested
Require withdrawal approvalWhen off, early exits settle immediately with the fee instead of queuing for review
Earnings distribution timeRestricts periodic distribution to the configured hour

A missing or malformed value falls back to the previous behaviour rather than halting distribution. The Settings page is now reachable from the admin staking menu — previously it existed but was unlinked, so the reward schedule could not be switched on through the interface at all.

Every staking notification and its "View Position" button linked to a page that does not exist. All ten links across rewards, claims, withdrawals and admin updates were broken.

Fixed: they now open the right position — switching to the tab containing it (a completed or cancelled position is hidden on the default Active tab) and opening its details.

A zero-day lock produced a permanently unstakeable pool

The pool accepted the configuration and then every user stake failed with an opaque error. The admin form already required at least one day; the underlying rule did not. They now agree.

Analytics

Fabricated data removed

  • Pool performance charts were generated at random and presented to admins as measured performance. They now show real data; where the data genuinely cannot support a chart, the card says so rather than inventing one.
  • Retention rate and early-withdrawal rate were hardcoded at 0% and displayed as measurements. Both are now calculated. The early-withdrawal rate uses the same rule as settlement, so the figure agrees with the fee actually charged. "Retention" is renamed to the repeat-staker rate — the share of stakers with more than one position — because that is what the data can actually support.
  • The public landing page invented marketing numbers (a hardcoded headline APR) whenever its data failed to load. It now shows the real rate or omits it.
  • The admin earnings page showed zeroes as fact when its data failed to load; it now shows the error.

A staker's per-token earnings were overwritten instead of added up

A user staking the same token in more than one pool saw only one pool's earnings in the per-token totals on their staking dashboard — every other pool's figure was silently discarded, under-reporting both the total and the unclaimed amount for that token.

Fixed: every pool now contributes to the token's totals.

Average APR on the admin earnings page was inflated

The Average APR tile summed the APR of every pool but divided by the number of active pools only, so every inactive, archived or coming-soon pool pushed the figure up. A platform with 5 active pools and 5 archived ones reported roughly double the true average.

Fixed: it now divides by the real pool count. The average admin fee shown beside it had the same fault and was corrected the same way.

The admin pool detail tab reported the wrong available capacity

The Available figure subtracted the staked amount from a number that is already the remaining capacity, so it understated the pool's headroom and turned negative once a pool filled past halfway. The utilisation bar on the same tab divided staked by remaining rather than by total capacity, so it could read well over 100%.

Fixed: both now match the pool's real accounting. This is a separate place from the utilisation divisor corrected in the admin analytics figures.

Other analytics corrections

  • Fixed a percentage change against an empty previous period being shown as "0%", which reads as "no change" when it actually means "no prior data".
  • Excluded the active-position count from the comparison, because the historical figure cannot be reconstructed — a reconstructed figure would be a guess, not a measurement.
  • Fixed several totals arriving in a form the screens could not display, which crashed the admin overview.
  • Fixed chart data sorting incorrectly, which broke the axis scaling.
  • Fixed the recent-activity feed being sorted by its "time ago" label rather than the actual time.
  • Fixed daily buckets being shifted by the server's time zone.
  • Fixed total value locked counting principal that had already been returned, and meaning something different on every screen. It now consistently means currently-locked principal.
  • Fixed pool efficiency counting deleted and cancelled positions, and utilisation being divided by a per-position limit rather than pool capacity.
  • Fixed the previous-month comparison silently dropping the last day of the month.
  • Fixed admin activity search by name or email doing nothing.
  • Fixed several very heavy queries on the admin position list, the statistics page and user earnings.
  • Fixed earnings totals adding different currencies into one meaningless number, and earnings history double-counting when a pool had several admin earnings in a 24-hour window.
  • Renamed the Total Users tile on the pool analytics Users tab to Total positions: it counted positions rather than participants, so a pool where stakers hold several positions each looked like it had more participants than it does. The number itself is unchanged.

Frontend

Crashes

Three pages showed a blank screen because money values arrived in a form they could not display: the positions summary, the staking dashboard, and the public landing page activity feed. The admin overview crashed the same way. All are fixed.

Broken interactions

  • Dashboard position cards never appeared. The page looked for pool information it had never loaded, and its loading state never resolved — leaving a skeleton forever. Cards now use the information already available, so the common case loads instantly.
  • Failed withdrawals and claims were reported as success. Both actions reloaded the page unconditionally, so a rejected action looked identical to a completed one. They now report the actual result, and a rejected claim no longer blanks the list the user was looking at.
  • Admin approve/reject withdrawal always failed and closed silently as if it had worked. Both now work, and keep the dialog open with the reason on failure.
  • Admin pool pages spun forever on any failure, showing no error and never finishing loading.

Token quantities on My Positions were shown as dollar amounts

The My Positions hero printed staked amounts and pending rewards with a dollar sign, so 5 BTC staked read as "$5.00". Worse, a portfolio holding several different tokens was added into a single figure and presented as money.

Fixed: when every active position is in the same token, the real quantity is shown with its token symbol. When they are in different tokens, the hero reports the number of assets rather than a meaningless total.

Disclosure

  • The stake form now distinguishes "balance failed to load" from "balance is zero" (it previously blocked staking with a false insufficient balance), checks pool capacity and precision before submitting rather than failing afterwards, caps MAX to what is actually stakeable, labels its estimate as simple interest, and cannot be double-submitted with the Enter key.
  • Auto-compound pools no longer advertise a payout schedule or show a Claim button — the reward is returned with the principal at maturity, so claiming was never possible.
  • The withdrawal dialog now states whether this is a normal post-lock withdrawal or an early exit, and shows the early-withdrawal fee before the user confirms.
  • Pool capacity bars showed near 0% on nearly-full pools, and a nearby label read "42 Filled" with no percent sign.
  • Raw 18-decimal values are no longer shown as prices and limits.
  • The positions list was silently cut off at 20 rows with no way to see the rest.
  • The Settings page is now linked from the admin staking menu, and remaining hardcoded English text is now translatable.