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.
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:
-
The user stakes. 1,000 USDT leaves their SPOT wallet as a
STAKINGdebit. The pool's remaining capacity drops by 1,000. A position row is created withstartDate,endDate30 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. -
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.
-
The user claims. Claiming credits every unclaimed row for that position into their wallet as a
STAKING_REWARDtransaction and marks the rows claimed. Rewards do not appear in the wallet until they are claimed. -
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 markedCOMPLETED. 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
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.
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.
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:
Every field on the pool form, what "locked" and "flexible" actually mean here, and the capacity field the edit form cannot change.
Several lock terms on one pool, which one the pool advertises, and the
durationId a multi-term pool starts requiring from your integrations.
The formula, what earningFrequency and autoCompound change, why the cron
cannot double-pay, and the two distribution doors.
The four fixed-rate states, what an early-exit request does, and why cancelling is not the same as approving.
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.
One Solana stake from quote to return, which scheduled task drives each step, and why no coin is credited before it has landed.
The activated chain a pool inherits, the two numbers that are its only terms, and the fixed-rate fields the form refuses by name.
Activating a chain, the staking key, the validator policy, the batch ledger, the incident desk and the compliance record.
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.