Staking Contracts

Panda ships a first-class staking primitive. Two contracts anchor it:

ContractRole
StakingRegistryThe canonical system contract. Owns validators, delegations, the reward pool, and the reward accumulator.
StakingVaultAn example application contract that pools user deposits and delegates them to a single validator.

Both are exercised from the Staking SDK flow and the Staking Vault / Staking Registry playground templates. This page is the on-chain contract reference.

Contract code lives in the playground template picker under Staking. Open the playground, pick a template, and click Deploy to try it live.


StakingVault

A StakingVault lets many users pool tokens and delegate them to one validator, sharing rewards proportionally. It is a thin wrapper over the panda.staking module.

State

FieldTypeMeaning
validatorstrThe validator this vault delegates to
ownerstrDeployer address
depositorsdictaddress -> deposited amount
total_depositedintSum of all deposits

Methods

MethodKindDescription
deploy(validator)constructorTargets a validator. Reverts unless staking.validator(validator).status == "active". Emits VaultDeployed.
deposit(amount)callDelegates amount via staking.delegate(...), tracks the depositor. Emits Deposited.
claim()callClaims rewards via staking.claim_rewards(...). Returns the claimed amount. Emits RewardsClaimed.
withdraw(amount)callBegins unbonding via staking.undelegate(...). Returns an unbonding record_id. Emits WithdrawalStarted.
complete_withdrawal(record_id)callFinalizes an unbonding after the waiting period via staking.complete_unbonding(...). Returns released amount. Emits WithdrawalCompleted.
get_validator_info()queryLive validator info (moniker, commission, total delegated, uptime, status).
get_depositor(address)queryHow much an address has deposited.
get_staking_stats()queryUnbonding period, slashing bps, reward pool, and total deposited.

The staking lifecycle

deposit  -> staking.delegate      -> stake is live, rewards accrue
claim    -> staking.claim_rewards -> pull rewards (no waiting period)
withdraw -> staking.undelegate    -> start unbonding timer, get record_id
complete_withdrawal -> staking.complete_unbonding -> tokens released

Claiming is independent of unbonding, so it is safe to call at any time.


StakingRegistry (system contract)

The StakingRegistry is the contract panda.staking actually talks to. In production it is seeded at the well-known address staking-registry on chain boot; the playground template deploys a standalone instance so you can see the mechanics.

State highlights

FieldMeaning
validatorsaddress -> validator record
delegations`"{delegator}
authorized_validatorsAdmin allow-list gating registration
reward_poolUndistributed rewards
acc_rewards_per_shareMasterChef-style accumulator, scaled by REWARD_SCALE = 10**18
total_stakedGlobal staked amount
paramsunbonding_period_blocks, commission_max_bps, min_self_delegation, slashing_bps_dishonesty
adminThe only account that can authorize validators, accrue rewards, slash, and set params

Key methods

MethodKindDescription
deploy(admin, unbonding_period_blocks, commission_max_bps, min_self_delegation, slashing_bps_dishonesty, initial_authorized_validators)constructorInitializes params and the admin.
set_authorized_validator(addr, authorized)callAdmin adds/removes an address from the validator allow-list. Emits ValidatorAuthorized.
register_validator(commission_bps, moniker, self_delegation)callAllow-listed caller registers; self-delegation is staked. Emits ValidatorRegistered.
delegate(validator, amount)callDelegates to a validator; returns the delegation key. Emits Delegated.
undelegate(validator, amount)callBegins unbonding; returns a record_id. Emits Undelegated.
complete_unbonding(record_id)callAnyone-can-call; releases funds to the recorded delegator. Emits UnbondingCompleted.
claim_rewards(validator)callPull-based reward claim, capped at the reward pool. Emits RewardsClaimed.
accrue_block_reward(amount, block_height)callAdmin/consensus only; bumps the accumulator. Emits BlockRewardAccrued.
slash_validator(validator, bps, reason, block_height)callAdmin only; burns stake (self first, then delegators pro-rata) and jails the validator. Emits ValidatorSlashed.
set_param(name, value)callAdmin only; restricted to the mutable-param allow-list. Emits ParamUpdated.
pause() / unpause()callAdmin only.

Read methods: get_validator(address), get_delegation(delegator, validator) (includes pending_rewards), get_delegations_for(delegator), list_validators(active_only), get_params(), get_unbonding_record(record_id), stats().

Why registration is gated

Without the allow-list, any address could self-register as a "validator" and start earning a pro-rata slice of block rewards without producing any blocks. set_authorized_validator is the cheapest gate that closes that sybil/grief vector. The admin (typically a Safe) controls membership, and register_validator refuses callers who are not on the list.

Reward accounting

Rewards use a pull-based MasterChef accumulator. Each block, accrue_block_reward increases acc_rewards_per_share by amount * REWARD_SCALE // total_staked. A delegator's pending reward is derived from the difference between the current accumulator and the snapshot taken when their stake last changed. This makes both accrual and claiming O(1) regardless of the number of delegators, and multiplication always happens before division to preserve precision.


Try it

  • Playground: open the Playground, choose the Staking Vault or Staking Registry template, and follow the in-editor tutorial.
  • SDK flow: see the Staking guide for the off-chain client view.
  • SDK types: the panda.staking module reference is in SDK Types.