Staking Contracts Overview
Staking contracts enable users to lock their tokens in exchange for rewards over time. TokenOps SDK provides a comprehensive staking system with flexible reward mechanisms, unbonding periods, whitelist capabilities, and tier-based incentives.
What is Token Staking?
Token staking is a mechanism where users temporarily lock their tokens in a smart contract to earn rewards. It's commonly used for:
- Yield Generation: Earn passive income on token holdings
- Network Security: Participate in blockchain consensus mechanisms
- Governance Participation: Gain voting power in protocol decisions
- Liquidity Provision: Encourage token holders to support the ecosystem
Key Benefits
- Passive Income: Earn rewards for token holding
- Network Effects: Increased participation strengthens the ecosystem
- Token Utility: Provides real utility for token holders
- Aligned Incentives: Long-term holding benefits all stakeholders
Staking Contract Types
TokenOps SDK offers two main staking contract variants:
1. Standard Staking
Basic staking with immediate withdrawals
// Initialize with contract address, staking token, and provider
const staking = new Staking(contractAddress, stakingTokenAddress, provider);
Features:
- Immediate withdrawals (no lock-up period)
- Time-based reward calculations
- Tier-based incentives
- Merkle tree whitelist support
- Pool capacity limits
2. Unbonding Staking
Staking with mandatory unbonding periods
// Initialize with contract address, staking token, and provider
const unbondingStaking = new StakingWithUnbonding(contractAddress, stakingTokenAddress, provider);
Features:
- Mandatory unbonding periods before withdrawals
- Higher APY rewards for locked tokens
- Tier-based multipliers
- Merkle tree whitelist support
- Maximum stake duration limits
Staking Architecture
Reward Mechanisms
1. Standard Staking Rewards
Standard staking uses time-based rewards with tier multipliers:
// Standard staking parameters
const stakingParams = {
timeUnit: BigInt(86400), // 1 day in seconds
rewardRatioNumerator: BigInt(1), // 1% daily
rewardRatioDenominator: BigInt(100), // 100% base
tierUpperBound: BigInt('1000000000000000000000'), // 1,000 tokens
tierLowerBound: BigInt('10000000000000000000') // 10 tokens
};
// Rewards calculated based on time staked and tier
2. Unbonding Staking Rewards
Unbonding staking uses APY-based rewards:
// Unbonding staking parameters
const unbondingParams = {
apyPercentage: BigInt(60000), // 60% APY (in basis points)
maxStakeDuration: BigInt(31536000), // 1 year maximum
unbondingPeriod: BigInt(604800), // 7 days unbonding
tierUpperBound: BigInt('1000000000000000000000'), // 1,000 tokens
tierLowerBound: BigInt('10000000000000000000') // 10 tokens
};
3. Tier-Based Rewards
Both staking types support tier-based multipliers:
// Tier structure example
const tierStructure = {
lowerTier: {
minAmount: BigInt('10000000000000000000'), // 10 tokens
maxAmount: BigInt('1000000000000000000000'), // 1,000 tokens
multiplier: 1.0 // Base rewards
},
upperTier: {
minAmount: BigInt('1000000000000000000000'), // 1,000+ tokens
multiplier: 1.5 // 50% bonus rewards
}
};
Access Control & Whitelisting
Whitelist Status Types
enum WhitelistStatus {
DISABLED = 0, // Anyone can stake
ENABLED = 1, // Only whitelisted addresses
MERKLE_TREE = 2 // Merkle proof required
}
Merkle Tree Whitelisting
Efficient whitelist management using Merkle trees:
// Stake with merkle proof
const merkleProof = [
'0x1234567890abcdef...',
'0xabcdef1234567890...'
];
await staking.stake(
amount,
receiverAddress,
{ account: userAddress, value: gasFee },
merkleIndex, // Optional: merkle tree index
merkleAmount, // Optional: max allowed amount
merkleProof // Optional: merkle proof
);
Contract Factories
Standard Staking Factory
import { StakingFactory } from 'tokenops-sdk';
const factory = new StakingFactory(factoryAddress, provider);
// Create new staking contract
const stakingResult = await factory.newStaking(
merkleRoot, // Merkle root for whitelist
whitelistStatus, // 0, 1, or 2
poolMaxCap, // Maximum tokens in pool
stakingToken, // ERC20 token address
timeUnit, // Time unit for rewards (seconds)
rewardRatioNumerator, // Reward ratio numerator
rewardRatioDenominator, // Reward ratio denominator
tierUpperBound, // Upper tier threshold
tierLowerBound, // Lower tier threshold
startStakingDate, // Start timestamp
endStakingDate, // End timestamp
gasFee // Gas fee for deployment
);
const stakingContract = stakingResult.stakingContract;
Unbonding Staking Factory
import { UnbondingStakingFactory } from 'tokenops-sdk';
const factory = new UnbondingStakingFactory(factoryAddress, provider);
// Create new unbonding staking contract
const stakingResult = await factory.newStakingWithUnbonding(
merkleRoot, // Merkle root for whitelist
whitelistStatus, // 0, 1, or 2
stakingToken, // ERC20 token address
poolMaxCap, // Maximum tokens in pool
tierUpperBound, // Upper tier threshold
tierLowerBound, // Lower tier threshold
unbondingPeriod, // Unbonding period in seconds
maxStakeDuration, // Maximum stake duration
apyPercentage, // APY in basis points
startStakingDate, // Start timestamp
endStakingDate, // End timestamp
gasFee // Gas fee for deployment
);
const stakingContract = stakingResult.stakingContract;
Core Staking Methods
Standard Staking Operations
// Initialize staking contract
const staking = new Staking(contractAddress, stakingTokenAddress, provider);
// Stake tokens
const stakeResult = await staking.stake(
amount, // Amount to stake
receiverAddress, // Address to receive staking rights
{ account: userAddress, value: gasFee },
merkleIndex, // Optional: merkle index
merkleAmount, // Optional: max allowed amount
merkleProof // Optional: merkle proof
);
// Withdraw tokens (immediate)
const withdrawResult = await staking.withdraw(
amount, // Amount to withdraw
{ account: userAddress }
);
// Claim rewards
const claimResult = await staking.claimRewards(
amount, // Amount to claim (0 for all)
{ account: userAddress, value: gasFee }
);
// Fund staking contract with rewards
const fundResult = await staking.fundStakingContract(
amount,
{ account: adminAddress, value: gasFee }
);
Unbonding Staking Operations
// Initialize unbonding staking contract
const unbondingStaking = new StakingWithUnbonding(contractAddress, stakingTokenAddress, provider);
// Stake tokens
const stakeResult = await unbondingStaking.stake(
amount, // Amount to stake
receiverAddress, // Address to receive staking rights
{ account: userAddress, value: gasFee },
merkleIndex, // Optional: merkle index
merkleAmount, // Optional: max allowed amount
merkleProof // Optional: merkle proof
);
// Initiate unbonding (start withdrawal process)
const unbondResult = await unbondingStaking.initiateUnbonding(
amount, // Amount to unbond
{ account: userAddress }
);
// Complete unbonding (after unbonding period)
const completeResult = await unbondingStaking.completeUnbonding(
{ account: userAddress, value: gasFee }
);
// Claim rewards
const claimResult = await unbondingStaking.claimRewards(
amount, // Amount to claim (0 for all)
{ account: userAddress, value: gasFee }
);
Read Methods
Standard Staking Information
// Get stake information
const [tokensStaked, rewards] = await staking.getStakeInfo(userAddress);
// Get staking parameters
const basisPoints = await staking.getBasisPoints();
const fee = await staking.getFee();
const feeType = await staking.getFeeType();
const timeUnit = await staking.getTimeUnit();
const [numerator, denominator] = await staking.getRewardRatio();
// Get contract state
const stakingToken = await staking.getStakingToken();
const balance = await staking.getStakingTokenBalance();
const totalRewards = await staking.getTotalAvailableReward();
const poolMaxCap = await staking.poolMaxCap();
const whitelistStatus = await staking.whitelistEnabled();
// Get staker information
const isInList = await staking.isInStakerList(userAddress);
const addressIndex = await staking.getAddressIndex(userAddress);
const stakerInfo = await staking.stakers(userAddress);
Unbonding Staking Information
// Get extended stake information
const [tokensStaked, rewards, unbondingAmount, timeUntilComplete, firstStakeTime] =
await unbondingStaking.getStakeInfo(userAddress);
// Get unbonding-specific parameters
const apy = await unbondingStaking.getAPY();
const unbondingPeriod = await unbondingStaking.unbondingPeriod();
const maxStakeDuration = await unbondingStaking.maxStakeDuration();
// All standard staking read methods are also available
Fee Structure
Fee Types
enum FeeType {
GAS_FEE = 0, // Pay gas fee in native currency
TOKEN_FEE = 1 // Pay fee in ERC20 tokens
}
Fee Management
// Get factory fee information
const defaultGasFee = await factory.getDefaultGasFee();
const defaultTokenFee = await factory.getDefaultTokenFee();
const feeType = await factory.getDefaultFeeType();
const feeCollector = await factory.getFeeCollector();
// Get custom fees (if enabled)
const customFee = await factory.getCustomFee(creatorAddress);
Event System
All staking contracts emit comprehensive events:
// Staking events
interface TokensStakedEvent {
staker: Address;
amount: bigint;
feeAmount: bigint;
}
interface TokensWithdrawnEvent {
staker: Address;
amount: bigint;
feeAmount: bigint;
}
interface RewardsClaimedEvent {
staker: Address;
rewardAmount: bigint;
feeAmount: bigint;
}
interface UnbondingInitiatedEvent {
staker: Address;
amount: bigint;
}
interface ContractFundedEvent {
funder: Address;
amount: bigint;
}
Administrative Functions
Withdrawal Functions
// Withdraw excess rewards (admin only)
await staking.withdrawExcessRewards(
amount, // Amount to withdraw
recipientAddress, // Recipient address
fromExcessTokens, // Whether from excess tokens
{ account: adminAddress }
);
// Withdraw other tokens (admin only)
await staking.withdrawOtherToken(
tokenAddress, // Token to withdraw
{ account: adminAddress }
);
// Withdraw fees (admin only)
await staking.withdrawGasFee(
amount, // Amount to withdraw
recipientAddress, // Recipient address
{ account: adminAddress }
);
await staking.withdrawTokenFee(
recipientAddress, // Recipient address
amount, // Amount to withdraw
{ account: adminAddress }
);
Integration Examples
Basic Staking Dashboard
interface StakingDashboard {
userStake: bigint;
pendingRewards: bigint;
totalStaked: bigint;
poolCapacity: bigint;
currentAPY: number;
userTier: string;
isWhitelisted: boolean;
}
async function getStakingDashboard(
staking: Staking,
userAddress: Address
): Promise<StakingDashboard> {
const [tokensStaked, rewards] = await staking.getStakeInfo(userAddress);
const balance = await staking.getStakingTokenBalance();
const poolMaxCap = await staking.poolMaxCap();
const isInList = await staking.isInStakerList(userAddress);
const tierUpper = await staking.tierUpperBound();
const tierLower = await staking.tierLowerBound();
return {
userStake: tokensStaked,
pendingRewards: rewards,
totalStaked: balance,
poolCapacity: poolMaxCap,
currentAPY: calculateAPY(balance, poolMaxCap),
userTier: calculateTier(tokensStaked, tierUpper, tierLower),
isWhitelisted: isInList
};
}
function calculateTier(stake: bigint, upper: bigint, lower: bigint): string {
if (stake >= upper) return 'Upper Tier';
if (stake >= lower) return 'Lower Tier';
return 'No Tier';
}
Unbonding Dashboard
interface UnbondingDashboard extends StakingDashboard {
unbondingAmount: bigint;
unbondingEndTime: bigint;
canCompleteUnbonding: boolean;
maxStakeDuration: bigint;
firstStakeTime: bigint;
}
async function getUnbondingDashboard(
staking: StakingWithUnbonding,
userAddress: Address
): Promise<UnbondingDashboard> {
const [tokensStaked, rewards, unbondingAmount, timeUntilComplete, firstStakeTime] =
await staking.getStakeInfo(userAddress);
const maxStakeDuration = await staking.maxStakeDuration();
const currentTime = BigInt(Math.floor(Date.now() / 1000));
return {
...await getStakingDashboard(staking, userAddress),
unbondingAmount,
unbondingEndTime: timeUntilComplete,
canCompleteUnbonding: timeUntilComplete <= currentTime,
maxStakeDuration,
firstStakeTime
};
}
Best Practices
1. Contract Initialization
- Always verify contract addresses before initialization
- Use proper provider configuration with wallet client
- Check staking token compatibility
2. Staking Operations
- Approve token spending before staking
- Handle gas fees properly based on fee type
- Validate merkle proofs for whitelisted pools
3. Unbonding Management
- Track unbonding periods carefully
- Allow sufficient time for unbonding completion
- Monitor stake duration limits
4. Error Handling
- Implement proper transaction failure handling
- Check contract state before operations
- Validate user inputs and permissions
5. Gas Optimization
- Batch operations when possible
- Use appropriate gas limits
- Monitor fee costs for users
Security Considerations
1. Access Control
- Verify whitelist status before staking
- Validate merkle proofs properly
- Check admin permissions for management functions
2. Economic Security
- Monitor pool utilization rates
- Implement maximum stake limits
- Regular reward pool monitoring
3. Contract Interaction
- Use proper transaction simulation
- Handle revert conditions gracefully
- Implement emergency withdrawal mechanisms
Next Steps
Now that you understand staking fundamentals:
- Standard Staking Guide - Detailed implementation guide covered in this documentation
- Unbonding Staking Guide - Advanced staking features detailed above
- Factory Deployment - Deploy your own staking contracts using the factory patterns shown
- Integration Examples - Real-world implementation patterns demonstrated throughout