ShamaDualStaking.sol View source on GitHub
Each of the 10 SHAMA pools deploys its own ShamaDualStaking instance. Users stake SHAMA tokens and earn two types of rewards:
SHAMA rewards — distributed over a fixed time window using a Synthetix-style accumulator (time-based, continuous)
USDT rewards — distributed instantly on deposit by the admin, split pro-rata across all current stakers (snapshot-based)
The contract also supports early staking — the paired ShamaTokenSale contract can stake tokens on behalf of buyers before the reward period officially begins.
Constructor
Parameter Type Description initialOwneraddressAdmin/owner address _shamaTokenaddressSHAMA token contract _usdtTokenaddressUSDT token contract _rewardStartDateuint256Unix timestamp — reward accrual begins _rewardEndDateuint256Unix timestamp — reward accrual ends _configStakingConfigStaking parameters (see below)
StakingConfig
struct StakingConfig {
uint256 minStakeAmount; // Minimum SHAMA to stake (18-decimal base units)
uint256 minStakingPeriod; // Minimum lock duration in seconds (e.g. 180 days)
uint256 maxTotalStake; // Global cap on total staked SHAMA (0 = no cap)
}
How Rewards Work
SHAMA Rewards (Time-Based)
SHAMA rewards use a Synthetix RewardRate accumulator :
Admin calls addRewardPool(amount) to deposit SHAMA rewards
The contract calculates shamaRewardRate = totalRewards / rewardDuration
Rewards accrue continuously per second, split pro-rata by stake weight
Users claim via claimReward or receive them automatically on unstake
If addRewardPool is called mid-period, the remaining undistributed rewards are combined with the new deposit and the rate is recalculated over the remaining time.
USDT Rewards (Snapshot-Based)
USDT rewards are distributed instantly when deposited:
Admin calls addUsdtReward(amount) — requires at least one active staker
rewardPerTokenStored is updated immediately: += (amount * 1e18) / totalStaked
Each staker’s share is calculated on their next interaction (stake, unstake, claim)
Users claim via claimUsdtReward or receive them automatically on unstake
USDT rewards go only to users who are staked at the moment of the deposit. Late stakers do not retroactively earn past USDT distributions.
User Functions
stake(uint256 amount, address recipient)
Stake SHAMA tokens to start earning rewards.
Only callable after rewardStartDate and while staking is active
recipient owns the stake (can differ from msg.sender)
Must meet minStakeAmount (cumulative — can add to an existing stake)
Requires SHAMA approval
earlyStake(uint256 amount, address recipient)
Stake before the reward period starts. Only callable by the paired ShamaTokenSale contract.
Enables earlyBuyAndStake flow from the sale contract
Tokens are staked but rewards only begin accruing at rewardStartDate
claimReward(address recipient)
Claim accrued SHAMA rewards without unstaking.
Rewards are capped at the contract’s available SHAMA balance (minus staked principal)
Any excess accrual beyond available balance is preserved for later claim
claimUsdtReward(address recipient)
Claim accrued USDT rewards without unstaking.
unstake(address recipient)
Exit the stake entirely. Returns the staked principal plus all accrued SHAMA and USDT rewards.
Reverts if minStakingPeriod hasn’t elapsed (unless staking was cancelled by admin)
Cannot unstake before rewardStartDate (unless cancelled)
Clears the user’s stake — they can re-stake afterwards
Admin Functions
Function Description addRewardPool(amount)Deposit SHAMA rewards and set/adjust the reward rate addUsdtReward(amount)Deposit USDT rewards, distributed instantly to current stakers startStaking()Re-enable staking (enabled by default on deployment) stopStaking()Pause new stakes (existing stakers can still unstake and claim) cancelStaking()Emergency cancel — users can unstake immediately without penalty setTokenSaleContract(addr)Set which address can call earlyStake setRewardPeriod(start, end)Update the SHAMA reward window updateConfig(config)Update min stake, min period, and max total stake recoverERC20(token, amount)Recover mistakenly sent tokens (SHAMA/USDT protected unless cancelled) recoverETH()Recover mistakenly sent ETH
View Functions
Function Returns pendingReward(user)Accrued but unclaimed SHAMA reward pendingUsdtReward(user)Accrued but unclaimed USDT reward stakes(user)Full StakeInfo struct for a user
StakeInfo
struct StakeInfo {
uint256 amount; // Currently staked SHAMA
uint256 stakedAt; // Timestamp of last stake action
uint256 accruedShamaReward; // Pending SHAMA reward
uint256 userShamaRewardPerTokenPaid; // Accumulator checkpoint (SHAMA)
uint256 userUsdtRewardPerTokenPaid; // Accumulator checkpoint (USDT)
uint256 accruedUsdtReward; // Pending USDT reward
uint256 claimedShama; // Lifetime SHAMA rewards claimed
uint256 claimedUsdt; // Lifetime USDT rewards claimed
}
Staker Enumeration
Function Description stakerCount()Total number of active stakers stakerAt(index)Staker address at a given index getStakers(offset, limit)Paginated list of staker addresses getStakerInfos(offset, limit)Paginated list of StakerSnapshot structs
Integration
Staking Flow
// 1. Approve SHAMA spend
await shamaToken . approve ( stakingContract . address , amount );
// 2. Stake
await stakingContract . stake ( amount , stakerAddress );
// 3. Check pending rewards
const shamaReward = await stakingContract . pendingReward ( stakerAddress );
const usdtReward = await stakingContract . pendingUsdtReward ( stakerAddress );
// 4. Claim rewards without unstaking
await stakingContract . claimReward ( recipientAddress );
await stakingContract . claimUsdtReward ( recipientAddress );
// 5. Unstake (returns principal + all rewards)
await stakingContract . unstake ( recipientAddress );
Admin Setup
// 1. Deploy staking contract with reward period and config
// 2. Link to sale contract
await stakingContract . setTokenSaleContract ( saleContract . address );
// 3. Deposit SHAMA reward pool
await shamaToken . approve ( stakingContract . address , rewardAmount );
await stakingContract . addRewardPool ( rewardAmount );
// 4. Periodically deposit USDT from shop revenue
await usdtToken . approve ( stakingContract . address , usdtAmount );
await stakingContract . addUsdtReward ( usdtAmount );
Events
Event Emitted When Staked(user, amount, recipient)Tokens staked Unstaked(user, amount, shamaReward, recipient)Tokens unstaked with rewards RewardClaimed(user, reward, recipient)SHAMA rewards claimed UsdtRewardClaimed(user, reward, recipient)USDT rewards claimed RewardAdded(funder, amount)SHAMA rewards deposited UsdtRewardAdded(funder, amount)USDT rewards deposited StakingStarted()Staking re-enabled StakingStopped()Staking paused StakingCancelledByAdmin(rewardTokensRecovered)Emergency cancellation RewardPeriodUpdated(startDate, endDate)Reward window changed ConfigUpdated(oldConfig, newConfig)Staking config changed
Security
ReentrancyGuard on all user-facing functions
SafeERC20 for all token transfers
SHAMA and USDT tokens are protected from recovery while staking is active
Minimum staking period enforced on unstake (bypassed only on admin cancellation)
SHAMA reward payouts are capped at available contract balance to prevent insolvency