Skip to main content

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:

  1. Standard Staking Guide - Detailed implementation guide covered in this documentation
  2. Unbonding Staking Guide - Advanced staking features detailed above
  3. Factory Deployment - Deploy your own staking contracts using the factory patterns shown
  4. Integration Examples - Real-world implementation patterns demonstrated throughout