API and data model
Every staking endpoint with its method, path and permission key, the six database tables and their columns, and the enums the whole product turns on.
Everything on this page is generated from the shipped route handlers and models. Permission keys are enforced server-side; a frontend screen that hides a button is convenience, the key on the endpoint is the control.
User endpoints
All under /api/staking. Authenticated unless marked public.
/api/staking/stats counts ACTIVE and PENDING_WITHDRAWAL principal only. A
position that has asked to leave but not yet settled is still locked capital;
one that has completed or been cancelled has already been paid back. Every
staking surface uses this definition, so the landing page, the user dashboard
and the admin console agree.
On-chain product
The stake door POST /api/staking/position takes an on-chain pool with
consent: { version, acknowledgements } and answers with the position and the
quote it was placed against. Every on-chain door dispatches on the pool's
venue — SOLANA_NATIVE or LIDO_STETH — so the same requests serve both
chains; a Lido quote additionally reports stakeLimitEth, stakingPaused
and bunkerMode. The claim door answers an on-chain position with
"unstake instead": rewards compound in the pool and are paid on exit.
Admin endpoints
All under /api/admin/staking.
Settings
Compliance
Chains, wallets and validators (on-chain product)
All under /api/admin/staking. See Chains for what each
screen does with them.
Operations (on-chain product)
shares (omitted = all treasury shares). One open request per pool; refused while the wallet is frozen or the chain has no ecosystem master wallet to pay to.limit up to 500.amount (omitted = the whole remaining allowance), pro rata to the holders, from the Super Admin's ECO wallet, refused with the shortfall named when it cannot fund it.period YYYY-MM, omitted = the previous month. Idempotent per user and period.The pool routes understand the product: POST /api/admin/staking/pool in on-chain mode needs walletChain (SOL) with an ACTIVE activation, inherits the wallet, validator set and slashing policy, and refuses every fixed-rate term; PUT /api/admin/staking/pool/:id on an on-chain pool accepts name, description, icon, limits, status, intakeStatus and the commission — a decrease at once, an increase as pending with an effective date a notice period away, every holder notified.
Dashboard and reporting
Pools
Positions
Earnings and performance
/earning/distribute (singular) pays an explicit amount as a BONUS.
/earnings/distribute (plural) runs the APR accrual engine and writes REGULAR
rows. They are deliberately separate namespaces so they can never double-pay a
period. Calling the wrong one is the most likely way to overpay a pool.
Tables
The six fixed-rate tables use UUID primary keys, timestamps and soft deletes. The on-chain product adds nine more, listed after them; those are not soft-deleted, because a key, a consent or an observation is a record that must not vanish from a console.
staking_pools
| Column | Type | Notes |
|---|---|---|
name |
string(191) | 2–100 characters |
token · symbol |
string(50) · string(10) | Symbol drives all wallet routing |
icon · description |
string(191) · text | |
walletType |
enum | FIAT · SPOT · ECO |
walletChain |
string(191) | Required for ECO |
mode |
enum | SYNTHETIC · REAL. Set from stakingMode at creation, immutable afterwards; the engine that settles the pool's positions is chosen by this column. Every pool that existed before the column reads SYNTHETIC. |
apr |
decimal(10,8) | ≥ 0 |
lockPeriod |
integer | ≥ 1 day |
minStake · maxStake |
decimal(36,18) | Max is nullable and must exceed min |
availableToStake |
decimal(36,18) | Live capacity |
earlyWithdrawalFee · adminFeePercentage |
decimal(10,8) | 0–100 |
status |
enum | ACTIVE · INACTIVE · COMING_SOON |
isPromoted · order |
boolean · integer | Presentation |
earningFrequency |
enum | DAILY · WEEKLY · MONTHLY · END_OF_TERM |
autoCompound |
boolean | |
externalPoolUrl · profitSource · fundAllocation · risks · rewards |
url · text x4 | Disclosure copy, never read by logic |
Positions are RESTRICT on delete; admin earnings and performance records
cascade.
staking_positions
| Column | Type | Notes |
|---|---|---|
userId · poolId |
uuid | |
mode |
enum | SYNTHETIC · REAL, snapshotted from the pool at stake time. The cron, settlement and every exit door read this, never the live setting. |
amount |
decimal(36,18) | Must be > 0 |
startDate · endDate |
datetime | Start must precede end |
status |
enum | ACTIVE · COMPLETED · CANCELLED · PENDING_WITHDRAWAL |
withdrawalRequested · withdrawalRequestDate |
boolean · datetime | The date prices an early exit |
adminNotes · completedAt |
text · datetime | completedAt only valid on COMPLETED |
apr · adminFeePercentage · earlyWithdrawalFee |
decimal(16,8), nullable | Terms snapshotted at stake time. Null on legacy rows falls back to the live pool. |
lastDistributionDate |
datetime, nullable | The accrual watermark. Null is treated as startDate. |
staking_earning_records
| Column | Type | Notes |
|---|---|---|
positionId |
uuid | |
amount |
double | ≥ 0 |
type |
enum | REGULAR · BONUS · REFERRAL |
description |
string(191) | Truncated by writers to fit |
isClaimed · claimedAt |
boolean · datetime | |
periodBucket |
string(100), nullable | Distribution-cycle key |
Unique on (positionId, type, periodBucket). NULL buckets on legacy rows never
collide because MySQL treats NULLs as distinct. REGULAR rows written by the
accrual engine carry accrual_YYYY-MM-DD; bonus distributions carry
poolId:frequency:LABEL:cycle. Nothing currently writes REFERRAL.
staking_admin_earnings
| Column | Type | Notes |
|---|---|---|
poolId |
uuid | |
amount · currency |
double · string(10) | Currency mirrors the pool symbol |
isClaimed |
boolean | Bookkeeping acknowledgement only |
type |
enum | PLATFORM_FEE · EARLY_WITHDRAWAL_FEE · PERFORMANCE_FEE · OTHER |
periodBucket |
string(100), nullable | Unique with (poolId, type) |
staking_external_pool_performances
poolId, date (not in the future), apr, totalStaked, profit, notes.
Reference data — no engine reads it.
staking_admin_activities
userId (null for cron-driven actions), action
(create · update · delete · approve · reject · distribute), type
(pool · position · earnings · settings · withdrawal) and relatedId.
On-chain columns on staking_pools and staking_positions
staking_pools gains venue, activationId, stakingWalletId,
validatorSetId, the share ledger (totalShares, sharePrice,
onchainValue, lastObservedAt, lastObservedEpoch, trailingRewardRateBps),
the protocol timings (activationDelaySeconds, unbondingEstimateSeconds,
unbondingBoundSeconds), disclosureVersion, commissionEffectiveAt,
pendingAdminFeePercentage, slashingPolicy, slashingReimburseCap and
intakeStatus (OPEN · PAUSED). All nullable or defaulted; a fixed-rate pool
never writes them.
staking_positions gains the on-chain states — PENDING_DELEGATION,
UNSTAKE_REQUESTED, UNBONDING, WITHDRAWABLE, FAILED — beside the
fixed-rate ones, makes endDate nullable (an on-chain position has no term),
and adds consentId, shares, entrySharePrice, principalOnchain, the
gather and return hashes and fees, the exit fields (unstakeRequestedAt,
unstakeShares, unstakeSharePrice, unbondingEndsAt, unbondingBoundAt,
settledAmount, settledAt), failureReason, forceUnstakedBy,
forceUnstakeReason and the three batch foreign keys.
staking_earning_records gains settlement (CLAIMABLE · COMPOUNDED) and
observationId. staking_admin_earnings.type gains STAKING_COMMISSION.
staking_chain_wallets
One per chain and network: chain, network, currency, address, data
(the AES-256-GCM envelope, never returned), role (STAKING), status
(ACTIVE · FROZEN), balance, gasReserveFloor, lastObservedAt, the
freeze fields and createdBy.
staking_chain_activations
The legal record per chain and network: venue, status (DRAFT · ACTIVE
· PAUSED · RETIRED), wallet and validator-set references,
defaultCommissionPercent, commissionNoticeDays, slashingPolicy,
slashingReimburseCap, the licensing declaration (licensed, regulator,
licenceReference, jurisdictionsServed), the acknowledgements
(ringFenceAcknowledged, noGuaranteeAcknowledged, sfcAttestation),
validatorDueDiligence, and the acceptance (disclosureVersion,
disclosureHash, disclosureText, acceptedBy, acceptedAt, acceptedIp,
acceptedUserAgent), plus the pause and retire stamps.
staking_validator_sets and staking_validators
A set: chain, network, name, status, policy (the thresholds as
JSON), lastEvaluatedAt, lastEvaluation, healthy. A member:
voteAccount, identity, name, weight, commissionPercent,
mevCommissionPercent, asn, status (ACTIVE · SUSPENDED · REMOVED),
lastHealth, lastHealthAt, breach.
staking_tranches
A unit of delegated principal: kind (SOLANA_STAKE_ACCOUNT ·
LIDO_SHARES), stakeAccount, seed, validatorId, status (CREATING ·
ACTIVATING · ACTIVE · DEACTIVATING · INACTIVE · WITHDRAWN ·
FAILED), amount, observedValue, the activation and deactivation epochs,
and the create, exit and withdraw batch references.
staking_batches
One on-chain operation: kind (GATHER · DELEGATE · EXIT · CLAIM ·
RETURN · REFUND · COMMISSION_EXIT · SWEEP), status (PENDING ·
BROADCAST · CONFIRMED · RETRYING · FAILED), stakingWalletId,
intentDigest, intent, txHash (unique), networkFee, amount,
attempts, lastError, broadcastAt, confirmedAt, createdBy.
staking_observations
What the network paid one pool for one window, unique on (poolId, window):
epoch, observedAt, valueBefore, valueAfter, grossReward (signed),
commissionAmount, commissionShares, netReward, sharePriceBefore,
sharePriceAfter, totalShares, positionsCredited, detail.
staking_consents
What a user accepted, verbatim: userId, poolId, activationId,
version, hash, text, acknowledgements, acceptedAt, ip,
userAgent.
staking_statements
One per user per period: period, periodStart, periodEnd, format
(CSV), content, hash, totalStaked, totalRewards, totalCommission,
summary.
staking_commission_exits
The platform's own exit: poolId, chain, network, status (QUEUED ·
UNBONDING · SETTLED · PAID · FAILED), shares, requestSharePrice,
requestedValue, settledAmount, settledAt, destination (the chain's
ecosystem master wallet, named at request time), exitBatchId,
payoutBatchId, txHash, networkFee, failureReason, requestedBy,
requestedAt, paidAt. Settled from staking_pools.treasuryShares after
every user exit ahead of it; the realised commission ledger.
staking_incidents
kind (SLASHING · DRIFT · LOW_GAS · VALIDATOR_BREACH · BATCH_STUCK
· OBSERVER_LAG · UNBONDING_OVERDUE · DELEGATION_STALE · COMMISSION ·
OTHER), severity, status (OPEN · ACKNOWLEDGED · RESOLVED),
title, detail, lossAmount, reimbursedAmount, dedupeKey,
occurrences, firstSeenAt, lastSeenAt, and the acknowledge and resolve
stamps.
Wallet and transaction types
| Type | Written when |
|---|---|
STAKING |
Principal debited at stake time, and principal returned at settlement |
STAKING_REWARD |
A user claims earnings |
Idempotency keys used by the money paths: staking_create_<positionId> for the
stake debit, staking_principal_return_<positionId> for the principal return
(shared across every transition), and a hash of the claimed row IDs for a claim.
Permission keys
| Key | Guards |
|---|---|
access.staking |
Admin overview, dashboard and analytics endpoints |
access.staking.pool · view.staking.pool |
Pool screens and reads |
create.staking.pool · edit.staking.pool · delete.staking.pool |
Pool writes |
access.staking.position · view.staking.position |
Position screens and reads |
create.staking.position · edit.staking.position · delete.staking.position |
Position writes, including withdrawal approval |
access.staking.earning · view.staking.earning |
Earnings screen and reads |
create.staking.earning · edit.staking.earning |
Both distribute endpoints, manual earnings, claiming |
view.staking.performance · create.staking.performance |
External performance records |
view.staking.activity |
Activity log |
access.staking.settings · view.staking.settings · edit.staking.settings |
Staking settings screen, its read and its write. The mode key additionally needs the Super Admin role, re-checked per request. The compliance records and exports reuse view.staking.settings. |
access.staking.chain · view.staking.chain · create.staking.chain · edit.staking.chain |
Chain activations. Activate and retire additionally need the Super Admin role. |
access.staking.wallet · view.staking.wallet · create.staking.wallet · edit.staking.wallet |
Staking wallets. Create, freeze and unfreeze additionally need the Super Admin role. |
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 |
Key derivation and the places a key must exist are covered in Permissions.