Skip to main content

How Staking Works

This page explains the technical details of how the staking system operates.

Architecture Overview

┌─────────────────┐     Performance     ┌─────────────────┐
│  Trading Vaults │ ───── Fees ──────▶ │    Treasury     │
└─────────────────┘                     └────────┬────────┘

                                        Redeem to USDC


                                        ┌─────────────────┐
                                        │ StakingRewards  │
                                        │    Contract     │
                                        └────────┬────────┘

                                        Proportional
                                        Distribution


                                        ┌─────────────────┐
                                        │    Stakers      │
                                        └─────────────────┘

Reward Mechanism

The staking contract uses the Synthetix StakingRewards pattern, which is battle-tested and gas-efficient.

Reward Per Token

Instead of tracking rewards for each user individually, the contract tracks a global rewardPerToken value that accumulates over time:
rewardPerToken = rewardPerToken + (
    (currentTime - lastUpdateTime) × rewardRate / totalStaked
)

User Earnings

When a user wants to know their earned rewards:
earned = stakedBalance × (rewardPerToken - userRewardPerTokenPaid) + storedRewards
This approach means:
  • Staking/unstaking is O(1) gas cost regardless of number of stakers
  • Rewards are calculated on-demand, not stored per-user

Lock Period Implementation

The 3-day lock period is implemented per-stake:
mapping(address => uint256) private _lockEnd;

function stake(uint256 amount) external {
    // ... stake logic ...
    _lockEnd[msg.sender] = block.timestamp + lockDuration;
}

function withdraw(uint256 amount) external {
    require(block.timestamp >= _lockEnd[msg.sender], "Still locked");
    // ... withdraw logic ...
}
Note: Each new stake resets the lock timer. If you stake additional tokens, the full lock period starts over.

Reward Distribution Cycle

  1. Treasury accumulates fees from vault performance
  2. Treasury redeems vault tokens to USDC via EasySwapper
  3. Treasury calls notifyRewardAmount(usdcAmount) on the staking contract
  4. Contract calculates new reward rate: rewardRate = amount / rewardsDuration
  5. Stakers earn rewards proportionally over the 7-day period

Extending Reward Periods

If new rewards are added before the current period ends:
remainingRewards = (periodFinish - now) × rewardRate
newRewardRate = (newRewards + remainingRewards) / rewardsDuration
This ensures smooth transitions between reward periods.

APY Calculation

The displayed APY is calculated as:
APY = (rewardRate × secondsPerYear × stakingTokenPrice) / (totalStaked × rewardTokenPrice) × 100
Important: APY is variable and depends on:
  • Total USDC rewards distributed
  • Total TST staked
  • Token prices

Smart Contract Functions

User Functions

FunctionDescription
stake(amount)Stake TST tokens
withdraw(amount)Withdraw staked tokens (after lock)
getReward()Claim earned USDC rewards
exit()Withdraw all + claim rewards

View Functions

FunctionDescription
balanceOf(address)User’s staked balance
earned(address)User’s claimable rewards
lockEnd(address)Timestamp when lock expires
canWithdraw(address)Whether user can withdraw
totalSupply()Total staked TST
rewardRate()Current reward rate per second

Admin Functions

FunctionDescription
notifyRewardAmount(reward)Add new rewards (REWARDS_DISTRIBUTOR_ROLE)
setRewardsDuration(duration)Change reward period length
setLockDuration(duration)Change lock period length
pause() / unpause()Emergency controls

Security Features

  • UUPS Upgradeable: Contract can be upgraded if needed
  • Access Control: Role-based permissions for admin functions
  • Pausable: Emergency pause capability
  • Reentrancy Guard: Protection against reentrancy attacks
  • Lock Period: Prevents flash loan attacks

Contract Addresses

See Contract Addresses for deployed addresses on each network.