MMashDiv

Staking

The Staking addon ships two products behind one switch — a fixed-rate product you fund out of your own treasury, and custodial on-chain staking where the network pays — what each one requires, and where to start.

10 min readUpdated 15 September 2026staking, pools, rewards, addon, on-chain

Staking gives your users a place to commit funds from their platform wallet and earn on them. Which product they are buying when they do that is a setting, and the two are different businesses: in one you publish a rate and pay it out of your own treasury, in the other coins are delegated on a real network and the reward is whatever the network paid. Read the next section before any other page in this addon — almost every screen, field and failure mode below forks on it.

It is an addon on top of Bicrypto core. There is no separate database and no ScyllaDB.

Two products, one switch

The addon carries a mode, stakingMode, edited at Staking → Settings.

SYNTHETIC is the fixed-rate product: a rate you set, paid from your own funds. It is the default, it is what an install that never opened the settings screen is running, and it is what the Guides section below describes.

REAL is custodial on-chain staking, and it is built and shipping. Coins are gathered from the user's own deposit address into a staking wallet the platform holds, delegated on the network — Solana natively, Ethereum through Lido — and the rewards are whatever the protocol paid, observed on-chain and shared by holders in proportion to their stake. You set no rate, you guarantee nothing, and your income is a commission on rewards that actually arrived.

Turning it on is not free of dependencies — it needs two other addons and an unlocked key vault. The five conditions are listed under What each product requires, below.

Every pool and every position records the product it was opened under, so a later switch never touches anything already open: both engines keep running for the rows they own, and only NEW stakes follow the switch. A pool of the product you are not currently selling is not hidden or settled differently — it is closed to new deposits and runs to its last position under its own rules, and the refusal at the stake door says exactly that.

The mode itself is documented on Settings. The whole on-chain product has its own section in the sidebar: How a stake moves, Creating on-chain pools, Chains & validators and Operations.

The one thing to understand before you enable the fixed-rate product

This is not delegated staking. No validator is involved, nothing is bonded, and no external yield reaches the platform. When a user stakes, the amount is debited from their wallet and held as a database row. When a reward accrues, the platform writes an earning row the user can claim into their wallet, and the backend simultaneously books the gross amount as a platform loss against the Super Admin treasury.

Every percentage point of APR you publish is a percentage point you pay out of your own float. Your real yield source — an exchange account, an external staking provider, a lending desk, or nothing at all — lives entirely outside this product. The pool's profitSource and fundAllocation fields are prose you show users; the software never reads them.

That single fact drives everything else on these pages: capacity limits are how you cap your liability, the admin fee is how you keep a slice of the yield you are paying, and the admin overview is a solvency console, not a marketing dashboard.

What each product requires

The two answers are different, and the difference is the single most common misreading of this addon. The fixed-rate product depends on no other addon. The on-chain product cannot be turned on without two.

The fixed-rate product

Requirement Why
Bicrypto core Wallets, users, KYC, roles, settings, the cron process and the fee collector all come from core. Staking adds no infrastructure of its own.
The cron process running Rewards accrue and matured principal is returned by an hourly scheduled task. With the cron down, users' money stays locked past the term they agreed to.
A Super Admin user with wallets Platform fees are credited to the oldest Super Admin's wallet. With no Super Admin configured, every staking fee is silently dropped and logged as [CRITICAL].
A funded Super Admin treasury A new fixed-rate stake is refused outright when that wallet cannot cover what the live fixed-rate book in the same currency and wallet type already promises plus what this stake would add — see the solvency gate on Settings. Nothing already open is affected and an exit is never blocked.
Ecosystem addon — only for ECO pools A pool set to the ECO wallet type needs the currency to exist as an active ecosystem token so the user's ECO wallet and its chain addresses can be created. Pools on SPOT or FIAT need nothing extra.

Nothing else. In fixed-rate mode staking does not depend on Ecosystem, Web3 Trading, Futures, P2P or any other addon unless you deliberately build ECO pools.

The on-chain product

Five conditions, all of them checked before stakingMode will accept REAL, and the settings screen names whichever is outstanding rather than greying the option out:

  • the Ecosystem extension installed, enabled and licensed;
  • the Web3 Trading addon installed, enabled and licensed — sold as Web3 Wallet & On-Chain Trading, and listed in the admin as Web3 Trading;
  • the ecosystem key vault unlocked, so a staking wallet can be created and signed with;
  • at least one chain ACTIVE, with its staking wallet, its completed activation record and its accepted operator statement;
  • the on-chain engine present, which it always is — it ships in the addon.

Settings covers the switch and Chains & validators covers the activation.

What ships

Eighteen tables, created automatically on boot, and the split runs along the product line exactly.

Seven for the fixed-rate product, every one of them soft-deleted: staking_pools, staking_positions, staking_durations, staking_earning_records, staking_admin_earnings, staking_external_pool_performances and staking_admin_activities.

Eleven for the on-chain product, and none of them is soft-deleted: staking_chain_wallets, staking_chain_activations, staking_validator_sets, staking_validators, staking_tranches, staking_batches, staking_observations, staking_commission_exits, staking_consents, staking_statements and staking_incidents. A row here is a record of coins that moved on a public network or of a declaration somebody signed, and neither is a thing to hide behind a deletedAt.

Eleven admin screens under /admin/staking — Overview, Pools, Positions, Earnings and Settings for the fixed-rate product; Chains, Wallets, Validators, Batches, Incidents and Compliance for the on-chain one (see Chains & validators). The first four are shared: Overview, Pools and Earnings each render a different page depending on the mode, and Positions forks per row, so a platform that has switched modes shows both products in one table. See The admin screens.

Six user screens under /staking — Dashboard, Staking Pools, My Positions, Statements, Staking Guide, and the public landing page. The landing page renders without a login; everything else requires one.

Seven scheduled tasks. processStakingPositions, hourly, accrues due fixed-rate rewards and settles matured positions — the only automatic money mover in the fixed-rate product. For the on-chain product: processRealStakingBatches (every two minutes) signs and broadcasts due batches and polls the ones in flight; observeRealStaking (every fifteen minutes) reads what the network paid; reconcileRealStaking (hourly) compares the chain with the book and opens incidents; refreshStakingValidatorSets (every six hours) re-screens every set against the policy; alertRealStaking (every ten minutes) raises overdue unbonding, stale delegation and commission alerts; generateRealStakingStatements writes each month's statements. Each of the six is a no-op until a chain is activated.

processStakingPositions and processRealStakingBatches are both flagged returnsCustomerMoney, and a job carrying that flag is re-scheduled on the disabled branch. Switching the addon off does not stop either of them, and it is not meant to: both return principal somebody is owed. What does stop them is the module failing to load — and when that happens the cron rows read REFUSED, not green and not COMPLETED.

Thirty-nine permission keys under *.staking.* — one of which, access.staking, has no third segment. Two KYC feature gates, invest_staking and withdraw_staking. view_staking exists in no route: it was removed rather than wired, and the comment in backend/src/utils/kyc.ts says so. Setting it gates nothing.

Thirteen platform settings prefixed staking*: eleven the settings console writes, listed in STAKING_OPERATOR_SETTING_KEYS, plus the two compliance keys the Compliance tab writes — the fixed-rate geo-block list and the recorded acceptance of its risk statement. See Settings.

How the money moves

The fixed-rate product

Follow one 1,000 USDT stake in a pool paying 12% APR with a 20% admin fee, a 30-day lock and daily earnings:

  1. The user stakes. 1,000 USDT leaves their SPOT wallet as a STAKING debit. The pool's remaining capacity drops by 1,000. A position row is created with startDate, endDate 30 days out, and a snapshot of the pool's APR, admin fee and early-withdrawal fee — so editing the pool later cannot reprice a lock that has already been agreed.

  2. Rewards accrue. Once a day the cron computes the reward owed since the position started, subtracts what has already been credited, and writes the difference as an unclaimed earning row. The 20% admin fee is taken off the top: the user's rows add up to 9.6% APR, and the fee leg is credited to the Super Admin wallet.

  3. The user claims. Claiming credits every unclaimed row for that position into their wallet as a STAKING_REWARD transaction and marks the rows claimed. Rewards do not appear in the wallet until they are claimed.

  4. The position matures. On the first hourly run after endDate, the final outstanding reward is accrued, the 1,000 USDT principal is returned to the wallet, the pool's capacity is restored by the original 1,000, and the position is marked COMPLETED. Unclaimed rewards survive completion and stay claimable.

An early exit changes step 4: the user requests a withdrawal, and depending on your approval policy the position either settles immediately or waits in a queue for you. Either way the pool's early-withdrawal fee is taken out of the principal, priced from the moment the user asked to leave.

The on-chain product

Two venues ship: Solana native delegation and Ethereum through Lido stETH.

When the mode is REAL the flow is different in kind: a stake is a quote, a consent, a ledger debit and a gather signed with the customer's own key; the coins are delegated from platform-owned stake accounts; the reward is what the network paid, observed per epoch, and it can be negative when a validator is slashed; an exit waits the protocol's unbonding and the ledger is credited only when the coins have landed back in the customer's own address. There is no maturity date — endDate is null on an on-chain position, and the only clock is the unbonding one, which starts when the exit is requested. There is no claim either: rewards are already in the pool's on-chain value and come out with the shares, which is why the customer's control reads Unstake rewards. How a stake moves follows one stake end to end; Chains & validators is where a chain is switched on.

Where to start

Read in the order of the product you are selling. A page written for the other one describes a form you do not have.

Whichever product you sell

Install

The extension flag, the seven scheduled tasks, the thirty-nine permission keys, the two KYC gates and the Super Admin the fee collector depends on. Nothing works until the extension flag is on.

Settings

Thirteen keys, starting with stakingMode — the switch that decides which of the two products is open for new stakes, and the five conditions it checks before it will accept REAL.

The admin screens

What each of the eleven consoles answers, which of them fork by mode, and which headline numbers are whole-table aggregates rather than a sample of page one.

If you are selling the fixed-rate product

You publish a rate and you pay it. Start with the funding warning above, then:

Creating pools

Every field on the pool form, what "locked" and "flexible" actually mean here, and the capacity field the edit form cannot change.

Duration tiers

Several lock terms on one pool, which one the pool advertises, and the durationId a multi-term pool starts requiring from your integrations.

Rewards

The formula, what earningFrequency and autoCompound change, why the cron cannot double-pay, and the two distribution doors.

Positions and withdrawals

The four fixed-rate states, what an early-exit request does, and why cancelling is not the same as approving.

The per-pool console

The Details, Positions and Analytics tabs behind one pool, and the two figures there that are computed differently from the same-named figures on Overview.

If you are selling the on-chain product

You publish no rate. The network pays, you take a commission, and a page that mentions APR, a lock period or a claim is describing the other product.

How a stake moves

One Solana stake from quote to return, which scheduled task drives each step, and why no coin is credited before it has landed.

Creating on-chain pools

The activated chain a pool inherits, the two numbers that are its only terms, and the fixed-rate fields the form refuses by name.

Chains & validators

Activating a chain, the staking key, the validator policy, the batch ledger, the incident desk and the compliance record.

Operations

The commission exit, the slashing reimbursement, the alerts, the monthly statements, and the drills to run before the first customer stakes.

Reference material sits behind all of them: API and data model lists every endpoint with its permission key and every table with its columns. Troubleshooting covers the failures that look like nothing is wrong.