Staking settings

The platform settings that govern staking, starting with the mode switch that decides which product you sell, what each one actually changes, and which of them behave differently when never saved than the screen suggests.

10 min readUpdated 3 September 2026settings, configuration, distribution, approval, mode

Every staking setting is stored in the platform settings table with a staking prefix and edited at Extensions → Staking Services → Settings (/admin/staking/settings, permission access.staking.settings). Two tabs: Platform and Earnings. The keys are the ones listed in STAKING_SETTINGS_KEYS in backend/src/api/(ext)/staking/utils/settings.ts, and every reader in the addon — the cron, settlement, the withdraw door, the calculator — takes them from that one module.

They are platform-wide. None of them can be varied per pool.

The console writes to PUT /api/admin/staking/settings, not the platform settings endpoint, and the platform endpoint refuses every staking* key by name and points back here. The addon endpoint validates each value and refuses one it disagrees with, naming it — an out-of-range percentage, a switch value that is not a switch, a distribution time that is not a time. The mode key is Super-Admin only; the endpoint re-checks the role on every request.

The settings screen shows a display default for every field, and the save only sends keys whose value actually changed. On a fresh install nothing has been saved, so most of these keys do not exist in the database at all — and each backend reader decides for itself what an absent key means.

Most of them agree. The exceptions are called out where they appear, and there are two kinds:

  • Three pre-fill defaults — admin fee, early-withdrawal fee and auto-compound — where the new-pool form falls back to a different figure than the settings screen displays. These only affect what the creation form suggests; no live pool or position reads them.
  • One live gate — the earnings distribution hour, where an absent or malformed value runs the distribution every hour. The screen now shows it blank for exactly that reason; it used to show 00:00.

stakingRequireWithdrawalApproval used to be a third case and is no longer — see the note on it below.

If you care about any of these, change the value, save, change it back and save again. That writes a real row and removes the ambiguity.

Platform tab

The mode: which product you sell

stakingModetype: selectdefault: SYNTHETIC
Which product is open for new stakes. SYNTHETIC is the fixed-rate product this documentation describes; REAL is custodial on-chain staking through the Ecosystem extension.

stakingMode is a product switch, not an engine switch. Every pool and every position carries its own mode, fixed when it is created, and the cron, settlement and every exit door read the row — never the live setting. Switching therefore changes three things and only three: which product a new pool is created in, which pools accept new stakes (a pool of the other product is closed to new deposits and runs to its last position under its own rules), and which vocabulary the aggregate pages use. Nothing already open is migrated, re-priced or settled differently.

REAL is refused unless the Ecosystem extension is installed, enabled and licensed, the key vault is unlocked, and the build ships the on-chain engine. This version does not ship the engine, so the option reads "not available yet" and the endpoint refuses it with that sentence. Switching back to SYNTHETIC is refused while any on-chain position still holds coins.

An unsaved, empty or unrecognised value is SYNTHETIC: an install that never opened this screen runs the fixed-rate product exactly as before.

stakingDefaultAdminFeetype: rangedefault: 0
Pre-fills the admin fee on the new-pool form. Not applied to existing pools.

A convenience default only. The pool form falls back to 10% when the key is absent, not to the 0 the settings screen displays. Changing it never touches a pool that already exists, and never touches a position — positions snapshot the fee at stake time.

stakingDefaultEarlyWithdrawalFeetype: rangedefault: 0
Pre-fills the early-withdrawal fee on the new-pool form.

Same shape. The pool form's own fallback when the key is absent is 5%.

stakingAutoCompoundDefaulttype: switchdefault: false
Pre-fills the auto-compound switch on the new-pool form.

Same shape again. All three of these are form conveniences and nothing else — no reward calculation reads them.

Earnings tab

stakingDefaultAprCalculationMethodtype: selectdefault: SIMPLE
Method used to convert APR into a reward. Applies to every pool and every position.

SIMPLE or COMPOUND. This is the one setting that changes what people are paid. Absent or unreadable resolves to SIMPLE.

Switching it mid-flight is safe in the sense that nothing breaks, but it changes the target curve for every live position, including ones already part-paid. Because accrual pays the difference between the target and what has been credited, moving from COMPOUND to SIMPLE can put a position temporarily ahead of its curve and freeze its rewards until wall-clock time catches up. Decide before you launch.

stakingAutomaticEarningsDistributiontype: switchdefault: true
Automatically credit periodic rewards on the hourly cron run.

Governs intra-term accrual only. Maturity settlement — returning principal at the end of a lock — always runs regardless, because holding capital past an agreed term is not a configuration option.

An absent key is treated as on. That matches the switch's display default, so this one is not a trap: a fresh install does distribute rewards. Turn it off only if you intend to distribute by hand from the Earnings screen; positions will accrue nothing until you do.

stakingEarningsDistributionTimetype: textdefault: midnight
Restricts periodic distribution to one hour of the day. Written as a 24-hour clock time, server-local.

The field takes an HH:MM string. Only the hour is compared — the minutes are ignored, so 14:30 and 14:00 behave identically. Outside that hour the cron settles matured positions and does nothing else. The screen's display default is 00:00.

Absent, blank, or malformed values fall back to running every hour, so a typo degrades to more frequent distribution rather than silently halting it. Skipping hours never shortchanges anyone: accrual is delta-based, so the next qualifying run credits the full outstanding amount.

stakingMinimumWithdrawalAmounttype: numberdefault: 0
Smallest position a user may withdraw. Compared against the whole position.

There is no partial withdrawal in this product, so this is not a minimum withdrawal amount in the usual sense — it is a floor on which positions their owner is allowed to exit at all.

The setting is a bare number with no currency attached, and it is checked against the position's amount in whatever the pool is denominated. A value of 100 means 100 USDT in a USDT pool and 100 BTC in a BTC pool. Across a mixed book, any non-zero value is almost certainly wrong for at least one pool. Leave it at 0 unless every pool shares a denomination.

stakingRequireWithdrawalApprovaltype: switchdefault: true
Route early exits to an admin queue instead of settling them immediately.

The withdraw endpoint has always treated a missing key as approval required, but the settings screen used to draw the same key as off. Because the settings form saves only the keys you actually change, an operator who opened this tab, agreed with what they saw and saved wrote no row at all — so the switch read "off" while the platform behaved as "on", and early exits piled up in PENDING_WITHDRAWAL waiting for a decision nobody knew they owed.

The screen was corrected to on, which is what the platform was already doing. Nothing changed about how any existing installation behaves.

If you want early exits to settle without review, turn this switch off and save — or leave it on and use the auto-approve thresholds below, which release the small or the long-waiting ones for you.

What the two positions mean:

  • On — an exit requested before endDate becomes a PENDING_WITHDRAWAL position and waits for an admin to approve or reject it. Rewards keep accruing while it waits. The exit fee is priced from the request date, not from your decision.
  • Off — an early exit settles in the same request, charging the pool's early-withdrawal fee. This is what turns a short-lock pool into a flexible product.

Either way, a withdrawal requested after the lock has expired settles immediately with no fee and never enters the queue.

Auto-approve: the middle ground

Reviewing every early exit and reviewing none are not the only two options. Both keys below are numbers, and 0 means off — including an absent row. They are read as numbers rather than tested for truthiness on purpose: settings are stored as text, and the string "0" is truthy.

stakingAutoApproveWithdrawalMaxAmounttype: numberdefault: 0
Settle an early exit without review when the position is below this amount. 0 disables it.
stakingAutoApproveWithdrawalAfterHourstype: numberdefault: 0
Release an early-exit request nobody has answered after this many hours. 0 disables it.

Below the amount. An early exit for strictly less than the threshold settles in the same request. A position exactly at the threshold still queues. The figure is compared against the position's amount in whatever the pool is denominated — so on a mixed book, any non-zero value is almost certainly wrong for at least one pool, the same caveat that applies to the minimum-withdrawal amount above.

After the hours. A request that has sat unanswered for longer than this is released by the earnings cron, so a queue you stop working drains itself instead of stranding principal. It is the safety net for the operator going on holiday, not a substitute for working the queue.

Both paths settle the position as an early WITHDRAW, so the pool's earlyWithdrawalFee is charged exactly as it would be if you had approved the request by hand. If you want early exits to be free, set the pool's fee to zero — do not reach for auto-approve expecting it to do that.

A position whose lock has already expired is never an early exit and is never charged, whichever of these is set.

Compliance tab

The fixed-rate product ships blocked for residents of the United States, the United Kingdom, the 27 EU member states, Singapore and Hong Kong, on existing installs as well as fresh ones. The block applies at the stake door only: a position that exists keeps running, and an exit is never blocked. It is the same gate, the same console and the same rule as Finance → Investment → Compliance, because the two products are the same kind of thing — a return the operator sets and pays.

stakingSyntheticGeoBlockListtype: listdefault: US, GB, EU-27, SG, HK
ISO-3166 country codes whose residents cannot open a fixed-rate stake. Widening never needs permission; removing a default entry has no effect until a Super Admin accepts the risk statement.
stakingSyntheticRiskAcknowledgementtype: recorddefault: none
The recorded acceptance of the fixed-rate risk statement: who, when, and the words accepted, all resolved from the session. Written only by this console.

Both keys are refused by the platform settings endpoint by name. The statement is versioned; revising it invalidates every earlier acceptance and the default territories come back until the new words are accepted.

The treasury solvency gate

A fixed-rate reward is paid from the Super Admin's wallet in the pool's currency. A new stake is refused when that wallet cannot cover what every live fixed-rate position in the currency still promises plus what the new stake would add; the refusal names the shortfall. Nothing already open is affected. There is no setting for this: fund the wallet.

What no setting controls

Worth stating explicitly, because operators look for these:

  • Per-pool overrides. APR, fees, lock period, earning frequency and auto-compound are pool fields, not settings. The settings above only pre-fill the creation form.
  • A reward budget or cap. Nothing limits total rewards paid. Your exposure ceiling is each pool's availableToStake, expressed in principal.
  • Where the yield comes from, in fixed-rate mode. There is no external provider integration to configure for the fixed-rate product. The platform pays from the Super Admin treasury and records each payout as a platform loss. The on-chain mode, when it ships, is the opposite: rewards are observed on the network and the treasury never funds one.
  • Claim expiry. Unclaimed rewards never expire. They survive completion of the position and remain a liability on the Overview screen until claimed.
  • Partial withdrawals. Not configurable because not implemented.

Verifying a change took effect

Settings are cached and broadcast to every process, so a save propagates without a restart. To confirm behaviour rather than display:

  1. Save the tab deliberately — toggle a field, save, toggle it back, save again. This forces a real database row for every key on the tab.

  2. Watch the next cron run at /admin/system/cron. The staking task logs which mode it chose: "Automatic earnings distribution is disabled" or "Outside the configured earnings distribution hour" appear in its output when either gate suppresses periodic accrual.

  3. Test a withdrawal with a throwaway position in a short-lock pool. If it lands in PENDING_WITHDRAWAL, approval is on; if the principal appears in the wallet immediately, it is off.

See Platform settings for how settings are stored and which keys are Super-Admin protected.