Manages token staking with reward accumulation and timelocked withdrawal. Integrates with admin tier system and multisig for governance participation. Supports time-weighted loyalty calculations for DAO governance voting power.
initialize(env: Env, admin: Address, stake_token: Address, reward_token: Address, reward_rate: i128, lock_period: u64) -> Result<(), ContractError>
Bootstrap the staking contract with token addresses and parameters.
Parameters:
admin- Administrator address (becomes SuperAdmin)stake_token- Token contract address for depositsreward_token- Token contract address for reward distributionreward_rate- Tokens emitted per second across all stakers (must be ≥ 0)lock_period- Withdrawal wait period in seconds (typical: 604800 for 1 week)
Returns: Result<(), ContractError>
Errors:
AlreadyInitialized- Contract already initializedInvalidInput- Negative reward rateTokensIdentical- Stake and reward tokens are the same
Example:
const LOCK_PERIOD: u64 = 604800; // 1 week in seconds
client.initialize(
&env,
&admin,
&stake_token_address,
&reward_token_address,
1000i128, // 1000 tokens per second
LOCK_PERIOD
)?;Deposit tokens into the staking pool.
Parameters:
staker- Address depositing tokens (must authenticate)amount- Quantity of stake tokens (must be > 0)
Returns: Result<(), ContractError>
Behavior:
- Requires
stakerauthentication - Updates global reward accumulator (prevents retroactive reward on new deposit)
- Transfers
amountstake tokens fromstakerto contract - Records first-stake timestamp for loyalty age tracking (one-time only)
- Increases user and global staked balance
Errors:
NotInitialized- Contract not initializedInvalidInput- Amount ≤ 0Paused- Contract is paused- Token transfer failures
Events:
staked(staker, amount, new_total_staked)
Storage Updates:
USER_STAKE- User's staked balanceTOTAL_STAKED- Global staked totalUSER_SINCE- Timestamp of first stake (written once)USER_RPT_PAID- User's reward-per-token snapshotUSER_EARNED- User's accumulated rewards
Example:
client.stake(&env, &user_address, 1000i128)?;Queue tokens for withdrawal after the timelock expires.
Parameters:
staker- Address requesting withdrawal (must authenticate)amount- Quantity of tokens to unstake (must be > 0, ≤ staked balance)
Returns: Result<u64, ContractError> — ID of the unstake request
Behavior:
- Updates global rewards before reducing stake
- Reduces user's staked balance immediately (prevents reward accrual on queued amount)
- Creates timelock request with expiration timestamp
- Queued amount is no longer eligible for rewards
Errors:
NotInitialized- Contract not initializedInvalidInput- Amount ≤ 0InsufficientBalance- Amount exceeds staked balancePaused- Contract is paused
Events:
unstake_requested(staker, amount, request_id, expires_at)
Storage Updates:
USER_STAKE- Reduced by amount- Unstake requests stored with expiration
Example:
let request_id = client.request_unstake(&env, &user_address, 500i128)?;
println!("Unstake request created with ID: {}", request_id);Withdraw tokens after the timelock has expired.
Parameters:
staker- Address withdrawing (must authenticate)request_id- ID fromrequest_unstake
Returns: Result<(), ContractError>
Behavior:
- Verifies timelock expired
- Transfers queued tokens + accrued rewards to staker
- Marks request as withdrawn
Errors:
NotInitialized- Contract not initializedRequestNotFound- Invalid request_idTimelockNotExpired- Must wait longerAlreadyWithdrawn- Request already processed- Token transfer failures
Events:
withdrawn(staker, amount, rewards_claimed)
Example:
client.withdraw(&env, &user_address, request_id)?;Claim accumulated rewards without unstaking.
Parameters:
staker- Address claiming rewards (must authenticate)
Returns: Result<(), ContractError>
Behavior:
- Updates global reward accumulator
- Transfers earned rewards to staker
- Resets user's earned rewards to zero
Errors:
NotInitialized- Contract not initializedInsufficientBalance- No rewards to claim- Token transfer failures
Events:
rewards_claimed(staker, amount)
Example:
client.claim_rewards(&env, &user_address)?;Retrieve current staking position and pending rewards.
Parameters:
staker- Address to query
Returns: StakerInfo { staked: i128, pending_rewards: i128 }
Example:
let info = client.get_staker_info(&env, &user_address);
println!("Staked: {}, Pending Rewards: {}", info.staked, info.pending_rewards);Get the timestamp of a staker's first deposit (for loyalty age calculation).
Parameters:
staker- Address to query
Returns: Option<u64> — Timestamp or None if never staked
Update the per-second reward emission rate (admin only).
Parameters:
caller- Admin address (must authenticate & have admin tier)new_rate- New reward rate (tokens per second)
Returns: Result<(), ContractError>
Errors:
Unauthorized- Caller lacks admin privilegesInvalidInput- Negative rate
Events:
reward_rate_changed(old_rate, new_rate)
Propose a reward rate change (multisig path).
Parameters:
caller- MultiSig proposernew_rate- Proposed reward rate
Returns: Result<(), ContractError>
Errors:
MultisigRequired- Insufficient approvalsInvalidInput- Invalid rate
Approve a pending rate change (multisig voting).
Parameters:
caller- MultiSig signer (must authenticate)
Returns: Result<(), ContractError>
Execute a rate change after multisig threshold met.
Parameters:
caller- Executer address
Returns: Result<(), ContractError>
Errors:
NoPendingRateChange- No rate change pendingRateChangeNotReady- Threshold not met or delay not expired
Retrieve total tokens staked across all users.
Returns: Total staked amount
Get the current per-second reward emission rate.
Returns: Reward rate (tokens per second)
Get the unstake timelock duration in seconds.
Returns: Lock period duration
Pause staking/unstaking operations (admin only).
Parameters:
caller- Admin address
Returns: Result<(), ContractError>
Resume staking/unstaking operations.
Parameters:
caller- Admin address
Returns: Result<(), ContractError>
Check if contract is paused.
Returns: bool
pub struct StakerInfo {
pub staked: i128, // Currently staked tokens
pub pending_rewards: i128, // Accrued but unclaimed rewards
}pub struct UnstakeRequest {
pub id: u64,
pub staker: Address,
pub amount: i128,
pub created_at: u64,
pub expires_at: u64, // Timelock expiration
}pub struct RateChangeProposal {
pub old_rate: i128,
pub new_rate: i128,
pub proposer: Address,
pub approvals: Vec<Address>,
pub created_at: u64,
pub delay_until: u64, // Execution delay
}| Key | Symbol | Purpose |
|---|---|---|
ADMIN |
"ADMIN" |
Admin/SuperAdmin address |
INITIALIZED |
"INIT" |
Initialization flag |
STAKE_TOKEN |
"STK_TOK" |
Stake token address |
REWARD_TOKEN |
"RWD_TOK" |
Reward token address |
REWARD_RATE |
"RWD_RATE" |
Per-second emission rate |
TOTAL_STAKED |
"TOT_STK" |
Global staked total |
REWARD_PER_TOKEN |
"RPT" |
Accumulated reward-per-token |
LAST_UPDATE |
"LAST_UPD" |
Last accumulator update time |
LOCK_PERIOD |
"LOCK_PER" |
Unstake timelock duration |
| User stake | (USER_STAKE, address) |
Per-user staked balance |
| User rewards | (USER_EARNED, address) |
Per-user earned rewards |
| User loyalty marker | (USER_SINCE, address) |
First-stake timestamp |
| Error | Code | Description |
|---|---|---|
NotInitialized |
1 | Contract not initialized |
AlreadyInitialized |
2 | Contract already initialized |
Unauthorized |
3 | Caller lacks permissions |
InvalidInput |
4 | Invalid parameter value |
InsufficientBalance |
5 | Insufficient tokens |
TimelockNotExpired |
6 | Withdrawal timelock active |
AlreadyWithdrawn |
7 | Request already processed |
RequestNotFound |
8 | Invalid request ID |
TokensIdentical |
9 | Stake and reward tokens same |
RateChangeNotReady |
10 | Rate change not executable |
NoPendingRateChange |
11 | No rate change in progress |
MultisigRequired |
12 | Multisig threshold not met |
MultisigError |
13 | Multisig operations failed |
Paused |
14 | Contract is paused |
| Event | Parameters | Description |
|---|---|---|
initialized |
(admin, stake_token, reward_token, rate, lock_period) |
Contract initialized |
staked |
(staker, amount, new_total) |
Tokens staked |
unstake_requested |
(staker, amount, request_id, expires_at) |
Unstake queued |
withdrawn |
(staker, amount, rewards_claimed) |
Withdrawal executed |
rewards_claimed |
(staker, amount) |
Rewards claimed |
reward_rate_changed |
(old_rate, new_rate) |
Emission rate updated |
access_violation |
(caller, action, required_permission) |
Authorization failure |
- Admin Tier System
- MultiSig Pattern
- Treasury Integration — connected via Governor