Last active
April 19, 2025 15:16
-
-
Save solangegueiros/73eace5058ff90f9465f93e755198dbe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity 0.8.20; | |
| import "@openzeppelin/contracts@5.2.0/token/ERC20/IERC20.sol"; | |
| import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; | |
| interface TokenInterface { | |
| function allowance(address owner, address spender) external view returns (uint256); | |
| function balanceOf(address account) external view returns (uint256); | |
| function decimals() external view returns (uint8); | |
| function mint(address account, uint256 amount) external; | |
| function transfer(address to, uint256 value) external returns (bool); | |
| function transferFrom(address from, address to, uint256 value) external returns (bool); | |
| } | |
| contract DynamicStake { | |
| TokenInterface public myToken; | |
| AggregatorV3Interface public ethUsdPriceFeed; | |
| mapping(address => uint256) public staked; | |
| mapping(address => uint256) public lastClaim; | |
| uint256 constant DATAFEED_PRICE_DECIMALS = 8; | |
| uint256 constant RATE_DIVISOR = 1000000; // rewardRate 0.01% ETH price per minute, with 8 decimals | |
| uint8 public tokenDecimals; | |
| event Staked(address indexed user, uint256 amount); | |
| event Unstaked(address indexed user, uint256 amount); | |
| event RewardClaimed(address indexed user, uint256 reward); | |
| constructor(address _token) { | |
| myToken = TokenInterface(_token); | |
| tokenDecimals = myToken.decimals(); | |
| /** | |
| * Network: Ronin Saigon | |
| * Aggregator: ETH/USD | |
| * Other Data Feeds: https://docs.chain.link/data-feeds/price-feeds/addresses | |
| */ | |
| address _priceFeedAddress = 0x798c8F5FF3dBa2B8CD95814936e1725c539d5C98; | |
| ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress); | |
| } | |
| //Before stake, go to the token and Approve the amount to this contract transfer from you account | |
| function stake(uint256 _amount) external { | |
| require(_amount > 0, "Nothing to stake"); | |
| require(myToken.allowance(msg.sender, address(this)) >= _amount, "Approve the token amount before stake"); | |
| require(myToken.balanceOf(msg.sender) >= _amount, "Amount > token balance"); | |
| myToken.transferFrom(msg.sender, address(this), _amount); | |
| claimReward(); | |
| staked[msg.sender] += _amount; | |
| lastClaim[msg.sender] = block.timestamp; | |
| emit Staked (msg.sender, _amount); | |
| } | |
| function claimReward() public { | |
| uint256 reward = calculateReward(msg.sender); | |
| if (reward > 0) { | |
| myToken.mint(msg.sender, reward); | |
| lastClaim[msg.sender] = block.timestamp; | |
| emit RewardClaimed(msg.sender, reward); | |
| } | |
| } | |
| function getCLDataFeedLatest() public view returns (int) { | |
| ( | |
| /*uint80 roundID*/, | |
| int price, | |
| /*uint startedAt*/, | |
| /*uint timeStamp*/, | |
| /*uint80 answeredInRound*/ | |
| ) = ethUsdPriceFeed.latestRoundData(); | |
| return price; | |
| } | |
| function calculateMinutesStaked(address user) public view returns (uint256) { | |
| if (lastClaim[user] > 0) { | |
| return (block.timestamp - lastClaim[user]) / 60; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| function calculateReward(address user) public view returns (uint256) { | |
| int price = getCLDataFeedLatest(); | |
| if (price <= 0) return 0; | |
| // Dynamic rate based on ETH/USD price, per minute | |
| uint256 rewardRate = uint256(price) / RATE_DIVISOR; | |
| uint256 minutesStaked = calculateMinutesStaked(user); // reward per minute | |
| uint256 decimalsAdjustment = 10 ** (DATAFEED_PRICE_DECIMALS - uint256(tokenDecimals)); | |
| uint256 rewardAmount = (staked[user] * rewardRate * minutesStaked) / decimalsAdjustment; | |
| return rewardAmount; | |
| } | |
| function unStake() external { | |
| require(staked[msg.sender] > 0, "not staked"); | |
| claimReward(); | |
| uint256 amount = staked[msg.sender]; | |
| staked[msg.sender] = 0; | |
| myToken.transfer(msg.sender, amount); | |
| emit Unstaked (msg.sender, amount); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity 0.8.20; | |
| import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
| import "@openzeppelin/contracts/access/AccessControl.sol"; | |
| contract Token is ERC20, AccessControl { | |
| bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); | |
| constructor() ERC20("Chainlink Bootcamp 2025 Token", "CLB25") { | |
| _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); | |
| _grantRole(MINTER_ROLE, msg.sender); | |
| } | |
| function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { | |
| _mint(to, amount); | |
| } | |
| function decimals() public pure override returns (uint8) { | |
| return 2; | |
| } | |
| } |
Thank you for sharing your knowledge !
Author
Hello Sol, thanks for the lesson today, very helpful. I've done some changes to your code adding two more zeros to RATE_DIVISOR. If you want to integrate them here's the link: https://gist.github.com/scurenza/4c424787c3cec9915a4b6818e98bb3fa
Yes!
It was fun to have the infinite tokens, but you version is better :)
Thank you!
Author
Thank you for sharing your knowledge !
You are welcome!
I wish you keep learning :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello Sol, thanks for the lesson today, very helpful. I've done some changes to your code adding two more zeros to RATE_DIVISOR. If you want to integrate them here's the link: https://gist.github.com/scurenza/4c424787c3cec9915a4b6818e98bb3fa