A Solidity-based NFT staking system that rewards NFT holders with ERC20 tokens over time. This project demonstrates how to implement a time-based staking mechanism where NFT holders earn yield tokens proportional to their holding period.
This project consists of three main smart contracts:
- NFT.sol: An ERC721 token with staking capabilities
- NftBase.sol: Base contract that implements the staking logic
- YieldToken.sol: An ERC20 token that is minted as rewards for stakers
The system allows users to:
- Mint NFTs
- Earn yield tokens automatically by holding NFTs
- Collect accrued yield tokens at any time
- Transfer NFTs (which automatically collects rewards)
- Time-based Rewards: NFT holders earn yield tokens based on how long they've held their NFTs
- Adjustable Reward Rates: Admin can change the reward rate at any time
- Staking Periods: Supports multiple staking periods with different reward rates
- Pause/Resume Staking: Admin can pause and resume the staking mechanism
- Automatic Reward Collection: Rewards are automatically collected when NFTs are transferred
- Batch Operations: Collect rewards for all owned NFTs in a single transaction
The main NFT contract that users interact with. It extends NftBase and implements:
- Minting functionality (with a price of 0.01 ETH per NFT)
- Maximum supply of 10,000 NFTs
- Public sale toggle
- Admin functions for managing the contract
The core staking logic that:
- Tracks staking periods and reward rates
- Calculates rewards based on holding time
- Manages the collection of rewards
- Handles NFT transfers with automatic reward collection
A standard ERC20 token with:
- Role-based access control
- Minting functionality restricted to authorized minters (the NFT contract)
The staking system works by:
- Recording the timestamp when an NFT is minted or transferred
- Tracking different staking periods with their respective reward rates
- Calculating rewards based on the formula:
timeElapsed * dailyReward - Where
dailyReward = (rewardRate * 10^18) / (24 * 60 * 60)
Rewards can be collected in three ways:
- Manually for a single NFT using
collectYieldTokenForOne(tokenId) - In batch for all owned NFTs using
collectYieldTokenForAll(address) - Automatically when transferring an NFT (rewards are sent to the previous owner)
- Node.js v16+ (Note: v23.7.0 is used but not officially supported by Hardhat)
- npm or yarn
- Hardhat
- Clone the repository:
git clone https://github.com/zxselimcan/chronostake.git
cd chronostake- Install dependencies:
npm installnpx hardhat compileRun the comprehensive test suite:
npx hardhat testFor test coverage:
npx hardhat coverage- Set up your environment variables in a
.envfile:
PRIVATE_KEY=your_private_key
INFURA_API_KEY=your_infura_api_key
ETHERSCAN_API_KEY=your_etherscan_api_key
- Deploy to a network:
npx hardhat run scripts/deploy.js --network <network_name>// Connect to the NFT contract
const nftContract = await ethers.getContractAt("NFT", nftContractAddress);
// Mint 2 NFTs for 0.02 ETH
await nftContract.mint(receiverAddress, 2, {
value: ethers.parseEther("0.02"),
});// Check rewards for a specific NFT
const rewards = await nftContract.collectableYieldTokenForOne(tokenId);
console.log(`Claimable rewards: ${ethers.formatEther(rewards)} YIELD`);
// Check rewards for all NFTs owned by an address
const totalRewards =
await nftContract.collectableYieldTokenForAll(ownerAddress);
console.log(
`Total claimable rewards: ${ethers.formatEther(totalRewards)} YIELD`
);// Collect rewards for a specific NFT
await nftContract.collectYieldTokenForOne(tokenId);
// Collect rewards for all owned NFTs
await nftContract.collectYieldTokenForAll(ownerAddress);// Toggle public sale status
await nftContract.togglePublicSaleStatus();
// Update reward rate (value between 1-1000)
await nftContract.setRewardRate(20);
// Pause staking
await nftContract.pauseStaking();
// Resume staking
await nftContract.continueStaking();
// Update base URI
await nftContract.setBaseURI("ipfs://newCID/");
// Withdraw contract funds
await nftContract.withdraw();