Install and enable
Turning the Staking addon on — the extension flag, the seven scheduled tasks, the thirty-nine permission keys, the KYC gates, the Super Admin the fee collector depends on, and what the on-chain product additionally requires.
Staking is installed from inside the admin panel. There is no archive to
extract by hand and no migration to run by hand — the Extension Manager
downloads the release and pnpm updator applies it. What you do have to do is
activate the licence, install the release, switch the addon on, confirm the
scheduled tasks exist, and hand out the permissions.
Do core install first. Staking assumes a working platform with wallets and a running cron process.
One install, two products. Everything on this page turns on the addon. The fixed-rate product then works with no further dependency; the on-chain product needs two other addons, an unlocked key vault and an activated chain, and has its own section below.
Turn the extension on
- The Staking Crypto card — click it to open the product page
- Its switch stays disabled until the licence is activated
-
Open the Extension Manager — sign in as an administrator and go to Admin → System → Extension Manager (
/admin/system/extension; the page heading is Add-ons & Integrations). You need theedit.extensionpermission; Super Admin has it. On the Extensions tab, or by searching for Staking, find the Staking Crypto card — product ID37434481— and click it. Until its licence is activated the card's chip reads Activate and its switch is disabled with the tooltip Activate license first. -
Activate the licence. On the product page press Activate License. Paste the purchase code from your MashDiv dashboard, optionally add a Notification Email for update notifications, and press Activate License; the screen confirms License Activated! and returns you to the product page a couple of seconds later. If the server has no outbound access, the License File tab on the same screen takes the licence certificate downloaded from your MashDiv dashboard instead — place it as
license.txtin the/licfolder at the project root, then press the button on that tab.- Paste the purchase code here
- Activate License — then wait for the redirect back to the product page
- License File — the offline route, for a server with no outbound HTTPS
-
Install the release. Back on the product page, now licensed, the Overview tab has a release panel. If it offers Install v…, press it; if it says Up to date on a product you have only just licensed, press Check for Updates once, then install whatever it offers. The Releases tab holds the notes for the version you are being offered — read them first.
Install downloads the release, verifies it and extracts it over the project root. It runs no migrations, builds nothing and restarts nothing, so finish it from a shell on the server:
pnpm updatorThat is the finalise chain — stop, dependencies, schema, seed data, frontend build, start — and it is what makes the staking tables, routes and screens exist. If the panel still says Up to date after the check there is nothing to download; go straight to the next step.
- Check for Updates, then Install v… when it is offered
- Releases — the notes for the version you are about to install
- The Enabled switch — step 4, not yet
-
Switch it on. Turn the Enabled switch on — in the product page heading, or on the card in the Extension Manager, which is usable now that the licence is verified. This is what makes Staking Services appear under Admin → Extensions and Staking Rewards appear in the user navigation; until it is on, neither exists. It takes effect without a restart — reload the admin panel and both menus are there.
If the toggle animates on and then reverts on reload, the update threw. Check
the backend log for EXTENSION. Earlier builds returned a 200 with an error
key in the body, which the frontend read as success — the switch stayed on
screen while the addon never actually came on. That is fixed, but an old
install can still show it.
Confirm the scheduled tasks
Seven tasks are registered under the staking category. One drives the
fixed-rate product, six drive the on-chain one. All of them run on the
dedicated cron process (port 4001), not on the API process.
Fixed-rate. processStakingPositions, on screen as Process Staking
Logs, period one hour. It accrues due rewards and settles matured positions.
It is the only task the fixed-rate product needs; the other six do nothing
while the platform has no on-chain rows. Go to /admin/system/cron and confirm
it is listed and has run.
The cron is the only thing that returns principal at the end of a lock period.
If it is not running, matured positions sit at ACTIVE forever, users' money
stays locked past the term they agreed to, and the admin overview will start
counting them under stale matured after 24 hours. This is a breach of the
agreement the user accepted when they staked, not a cosmetic delay.
The task is safe to run repeatedly and safe to retry. Accrual is delta-based against a per-position watermark and every write is keyed to a distribution period, so a run that overlaps a previous one credits nothing twice.
The six on-chain tasks
These are registered whether or not you ever turn the on-chain product on.
Four of them report skipped until there is on-chain work to do — the mode is
REAL, or a position opened under REAL is still live — and none of them
moves a coin before a chain is activated.
| Registered as | On screen | Period |
|---|---|---|
processRealStakingBatches |
On-chain staking: batches | 2 minutes |
observeRealStaking |
On-chain staking: observe rewards | 15 minutes |
reconcileRealStaking |
On-chain staking: reconcile | 1 hour |
refreshStakingValidatorSets |
On-chain staking: validator policy | 6 hours |
alertRealStaking |
On-chain staking: alerts | 10 minutes |
generateRealStakingStatements |
On-chain staking: monthly statements | 24 hours |
processRealStakingBatches is the one that signs and broadcasts: gathers,
delegations, exits, claims, returns and refunds, and it polls the ones already
in flight. It runs every two minutes because an exit that has finished
unbonding is a customer's coins sitting still. Exits run regardless of the
intake switches — those gate the stake door and only the stake door.
Two of the six key off something other than position rows.
reconcileRealStaking skips while no staking wallet exists, and
refreshStakingValidatorSets works over validator sets that are ACTIVE — so
both start reporting work as soon as you build the activation records, before
any customer has staked.
If the on-chain module cannot be imported at all — a missing dependency, a
syntax error, a throw during module init — the scheduler sets a standing
refusal on each of the six jobs and the rows read refused on
/admin/system/cron, with one URGENT email per broken module rather than one
per job. An install that never bought the addon stays silent instead, which is
the case the refusal is careful to exclude.
Make sure a Super Admin exists
Both staking fees — the admin fee on rewards and the early-withdrawal fee on principal — are collected by crediting the wallet of the oldest user holding the Super Admin role.
If there is no Super Admin role, or the role exists with no users, the fee
collector logs [CRITICAL] No Super Admin ... platform fees are being dropped
and returns nothing. The staking code then deliberately skips writing the
bookkeeping row, so your earnings screen stays honest — but the revenue is gone
and there is no retry.
One more consequence worth knowing: when the actor performing a fee-bearing action is the Super Admin, the fee is skipped entirely rather than credited back to themselves. Testing distributions from the Super Admin account will show zero platform fees. That is correct behaviour, not a bug.
Grant the permissions
- Open the role that will run staking
Thirty-nine keys control the addon: eighteen for the fixed-rate consoles,
eighteen for the on-chain ones, and three — access.staking.settings,
view.staking.settings and edit.staking.settings — that span both. Assign
them at /admin/crm/role.
| Key | Opens |
|---|---|
access.staking |
The admin overview and the solvency dashboard endpoint |
access.staking.pool · view.staking.pool |
The pool list and pool detail |
create.staking.pool · edit.staking.pool · delete.staking.pool |
Creating, editing, reordering and deleting pools |
access.staking.position · view.staking.position |
The position table |
create.staking.position · edit.staking.position · delete.staking.position |
Position edits, approving or rejecting withdrawals, terminal transitions |
access.staking.earning · view.staking.earning |
The earnings desk |
create.staking.earning · edit.staking.earning |
Both distribute endpoints and claiming an admin earning row |
view.staking.performance · create.staking.performance |
External pool performance records |
view.staking.activity |
The staking admin activity log |
access.staking.settings · view.staking.settings · edit.staking.settings |
The settings screen and the whole Compliance console — see below |
access.staking.chain · view.staking.chain · create.staking.chain · edit.staking.chain |
The Chains screen and chain activations (activate and retire also need Super Admin) |
access.staking.wallet · view.staking.wallet · create.staking.wallet · edit.staking.wallet |
The staking wallets (create, freeze and unfreeze also need Super Admin) |
access.staking.validator · view.staking.validator · create.staking.validator · edit.staking.validator |
Validator sets and the screened candidate list |
access.staking.batch · view.staking.batch · edit.staking.batch |
The batch ledger and retry |
access.staking.incident · view.staking.incident · edit.staking.incident |
Incidents and the reconciler |
edit.staking.position is the one that moves money: approving a withdrawal
request settles the position and returns principal. Treat it as a
finance-desk permission, not a support one. See
Permissions for how a key maps to an
admin path.
The Compliance console at /admin/staking/compliance has no permission key of
its own. It is gated by view.staking.settings and edit.staking.settings,
the same two keys that gate the settings screen — nine route files between
them. So a role holding view.staking.settings can read every chain activation
declaration, every customer's consent record with their name, email address, IP
and user agent, every monthly statement, and three CSV exports of the same
(consents, activations, observations). Grade those keys the way you grade
access to the KYC records, not the way you grade access to a settings form.
edit.staking.settings is not enough to change stakingMode. That key is
on the protected list and the route resolves the Super Admin role itself, per
request, from the session. A caller without it is refused with a 403 and
nothing on the page is saved.
Turning on the on-chain product
Everything above installs both products. Which one you sell is a single
platform setting, stakingMode, documented on
Settings.
The fixed-rate product needs nothing further. stakingMode ships
SYNTHETIC, and in that mode the addon has no dependency on any other addon,
no key vault, no RPC endpoint and no chain. If you are selling published-APR
pools funded from your own treasury, stop here and go to
Creating pools. Nothing below applies to you.
REAL is different. Coins are actually delegated on a network, the platform
holds the key, and the switch is refused until the install can do that. The
settings route answers five requirements separately so it can name the one that
is missing rather than say "not available". Fix them in this order:
- The Ecosystem extension — installed, enabled, and its licence activated. Same three steps as above, on its own product card.
- The Web3 Trading addon — sold as Web3 Wallet & On-Chain Trading, listed in the admin as Web3 Trading — installed, enabled, licensed. You need it even if you only ever stake Solana and will never route a trade through it; that consequence was known and accepted when the dependency was taken.
- The ecosystem key vault, unlocked. Without it no staking wallet can be generated or used. See the warning below about which process holds it.
- At least one chain activation record
ACTIVE. This is four things, not one: the staking wallet for that chain and network, the activation record with its declarations, a validator set that passes policy (Solana only), and a Super Admin's acceptance of the operator statement at its current version. Chains and validators walks through all four. - The engine. This build ships it, so this requirement can no longer be the missing one. It remains a separate flag only so that a build without the engine would have one place to say so.
Only a Super Admin can then flip stakingMode. edit.staking.settings is not
enough — see the permissions warning above. And the switch back to SYNTHETIC
is refused while any position opened under REAL still holds coins on-chain.
The environment: network, endpoints and the vault
The network is not a staking setting. It comes from SOL_NETWORK and
ETH_NETWORK, the same environment variables the Ecosystem addon already reads
for those chains, so an install cannot end up taking deposits on mainnet and
staking on devnet. Solana accepts mainnet, devnet and testnet; Ethereum
accepts mainnet and hoodi — Sepolia is deliberately unsupported because
Lido's deployment there is deprecated.
Endpoints follow the platform's generic shape, {CHAIN}_{NETWORK}_RPC:
| Variable | For |
|---|---|
SOL_MAINNET_RPC · SOL_DEVNET_RPC · SOL_TESTNET_RPC |
Solana native delegation |
ETH_MAINNET_RPC · ETH_HOODI_RPC |
Lido stETH |
Each takes a comma-separated list and the pool fails over across it.
These variables are filtered to HTTP(S) only. Every client behind them —
Solana's Connection, the Lido reads — speaks HTTP and nothing else, so a
wss:// endpoint is removed from the list rather than refused, and the list can
end up empty without anything saying so.
When the Solana list is empty the engine falls back to the public
clusterApiUrl() endpoint for that cluster. That endpoint is rate limited and
is not intended to carry a production workload; a batch runner polling it every
two minutes alongside the observer is exactly the traffic it throttles. Set your
own endpoint before you activate a chain.
The vault is unlocked by a passphrase held in memory by whichever process was given it. The KMS screen sets it in the process that served the request — the API process — and the readiness check you can see on screen is answered by that same process. The batch runner that signs gathers, delegations, exits and returns runs on the cron process on port 4001, which has only what was in its environment when it booted.
So set ENCRYPTION_KEY_PASSPHRASE in the environment both processes start
from. A vault unlocked only through the screen can read as ready while nothing
is being signed.
Set the KYC gates
Two feature gates apply, and the server asserts them on five endpoints across both products:
| Feature | Asserted on | Blocks |
|---|---|---|
invest_staking |
POST /api/staking/position · POST /api/staking/real/quote |
Opening a fixed-rate position, and the pre-stake quote — the first door an on-chain customer reaches, before any consent or signature |
withdraw_staking |
POST /api/staking/position/:id/claim · /withdraw · /unstake |
Claiming rewards, requesting a fixed-rate withdrawal, and the on-chain unstake — which is the only exit on-chain, because there is no separate claim |
Configure them with the rest of your verification levels in the KYC settings.
withdraw_staking covers both leaving a position and claiming rewards, so
gating it locks users out of money they have already earned until they verify.
On-chain that is sharper still: the unstake door is the only way out, and a
customer who cannot pass it cannot start a protocol exit at all.
Per-feature KYC needs two switches: the platform-wide kycStatus, and
kycFeatureEnforcement, which ships off. It ships off because these
per-feature toggles sat in the admin UI for a long time while nothing on the
server read them, so no existing install has curated its level feature lists
against real enforcement — turning it on by default would revoke access from
users who have it today.
The consequence for you is that staking's two gates do nothing on a stock
install. Review the level builder first, then turn kycFeatureEnforcement on;
until you do, setting invest_staking on a level changes nothing. The check
also fails open on a settings read error, deliberately: a cache hiccup must
not stand between a user and their own money.
Older install notes told operators to set a view_staking feature. It was
removed rather than wired — the comment in utils/kyc.ts says so in as many
words — and no route reads it. Setting it blocks nothing. If your levels carry
it, it is dead configuration.
Affiliate rewards
Two seeded referral conditions fire from staking, if you run the affiliate system:
STAKING— Staking Commission. Fires when a referred user opens a position, on the staked amount. Seeded at 2% and enabled.STAKING_LOYALTY— Staking Loyalty Bonus. Fires once when a position completes, on the original principal. Seeded at 3% and disabled; enable it in the affiliate conditions screen if you want it.
Both are processed after the money transaction commits and are best-effort — a failure there is logged and never rolls back a stake or a settlement.
Verify the install
Work through this on a staging install before you publish a pool:
- The Staking Crypto card at
/admin/system/extensionreads Verified and its switch is on - Staking Services appears under Extensions in the admin menu
- Process Staking Logs is listed at
/admin/system/cronand has run - None of the seven staking rows at
/admin/system/cronreads refused - A Super Admin user exists and is not the account you test with
- Your admin role holds at least
access.stakingandview.staking.pool -
/admin/stakingloads without a permission error -
/admin/staking/settingsloads and shows the Platform and Earnings tabs - No role holds
view.staking.settingsthat should not also hold access to customer KYC records - A test user can reach
/stakingand see the pool list
If you are also selling the on-chain product, work through these as well before you activate a chain:
- The Ecosystem extension and the Web3 Trading addon are both installed, enabled and licensed
-
ENCRYPTION_KEY_PASSPHRASEis in the environment the cron process boots from, not only the API process -
SOL_NETWORK/ETH_NETWORKname the same network your ecosystem deposits already use - The RPC variables for that network are set to your own endpoints and every entry begins
http://orhttps:// - Every staking wallet has a gas reserve floor above zero — a floor of 0 switches the LOW_GAS incident off entirely, and wallets created before the 0.05 default landed start at 0. Activation now refuses a chain whose wallet is still at zero, so check any wallet created on an earlier version
- The chain activation record is
ACTIVE, with its validator set passing policy and the operator statement accepted at its current version -
/admin/staking/complianceloads for the role you intend to give it, and for no other - The six on-chain cron rows have run at least once and report skipped work rather than an error
Then read Creating pools — a pool has more fields that cannot be changed later than fields that can. If you are selling the on-chain product, read How a stake moves before you publish one: the exit is a protocol wait you do not control, and the pool fields you choose are what a customer is told about it.