Learn how to verify your staking pool contract on PulseScan
Contract verification provides transparency and trust by:
First, you'll need your pool's contract address. You can find this in:
Navigate to https://scan.pulsechain.com and:
Choose the following options:
Copy and paste the following contract code into the source code field:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title StakingPool
* @dev Single-sided staking pool with time-locked reward rate adjustments
*/
contract StakingPool is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
// Token being staked
IERC20 public stakingToken;
// Token info
string public tokenName;
string public tokenSymbol;
string public poolImageUrl;
// Staking data
uint256 public totalStaked;
uint256 public rewardRate; // Tokens per second
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
// Time lock for reward rate changes
uint256 public constant TIMELOCK_DURATION = 24 hours;
uint256 public pendingRewardRate;
uint256 public rewardRateUpdateTime;
bool public rewardRateUpdatePending;
// User data mapping
mapping(address => uint256) public userStakedAmount;
mapping(address => uint256) public rewards;
mapping(address => uint256) public userRewardPerTokenPaid;
// Events
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardsClaimed(address indexed user, uint256 amount);
event RewardRateUpdateScheduled(uint256 oldRate, uint256 newRate, uint256 effectiveTime);
event RewardRateUpdated(uint256 newRate);
event PoolInfoUpdated(string tokenName, string tokenSymbol, string poolImageUrl);
/**
* @dev Constructor to initialize the staking pool
* @param _stakingToken Address of the token being staked
* @param _tokenName Name of the token
* @param _tokenSymbol Symbol of the token
* @param _poolImageUrl URL of the pool image
* @param _initialRewardRate Initial reward rate (tokens per second)
* @param _owner Address of the pool owner
*/
constructor(
address _stakingToken,
string memory _tokenName,
string memory _tokenSymbol,
string memory _poolImageUrl,
uint256 _initialRewardRate,
address _owner
) Ownable(_owner) {
stakingToken = IERC20(_stakingToken);
tokenName = _tokenName;
tokenSymbol = _tokenSymbol;
poolImageUrl = _poolImageUrl;
rewardRate = _initialRewardRate;
lastUpdateTime = block.timestamp;
}
/**
* @dev Update pool information
* @param _tokenName New token name
* @param _tokenSymbol New token symbol
* @param _poolImageUrl New pool image URL
*/
function updatePoolInfo(
string memory _tokenName,
string memory _tokenSymbol,
string memory _poolImageUrl
) external onlyOwner {
tokenName = _tokenName;
tokenSymbol = _tokenSymbol;
poolImageUrl = _poolImageUrl;
emit PoolInfoUpdated(_tokenName, _tokenSymbol, _poolImageUrl);
}
/**
* @dev Schedule a reward rate update (time-locked)
* @param _newRewardRate New reward rate to be applied after timelock
*/
function scheduleRewardRateUpdate(uint256 _newRewardRate) external onlyOwner {
_updateReward(address(0));
pendingRewardRate = _newRewardRate;
rewardRateUpdateTime = block.timestamp + TIMELOCK_DURATION;
rewardRateUpdatePending = true;
emit RewardRateUpdateScheduled(rewardRate, _newRewardRate, rewardRateUpdateTime);
}
/**
* @dev Apply pending reward rate update after timelock expires
*/
function applyRewardRateUpdate() external {
require(rewardRateUpdatePending, "No pending reward rate update");
require(block.timestamp >= rewardRateUpdateTime, "Timelock not expired");
_updateReward(address(0));
rewardRate = pendingRewardRate;
rewardRateUpdatePending = false;
emit RewardRateUpdated(rewardRate);
}
/**
* @dev Calculate reward per token
* @return Accumulated reward per token
*/
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / totalStaked
);
}
/**
* @dev Calculate pending rewards for a user
* @param account User address
* @return Pending reward amount
*/
function earned(address account) public view returns (uint256) {
return (
(userStakedAmount[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18
) + rewards[account];
}
/**
* @dev Update reward calculations
* @param account User address
*/
function _updateReward(address account) internal {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = block.timestamp;
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
}
/**
* @dev Modifier to update rewards before function execution
* @param account User address
*/
modifier updateReward(address account) {
_updateReward(account);
_;
}
/**
* @dev Add rewards to the staking pool
* @param amount Amount of tokens to add as rewards
*/
function addRewards(uint256 amount) external onlyOwner {
require(amount > 0, "Cannot add zero rewards");
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
}
/**
* @dev Stake tokens
* @param amount Amount to stake
*/
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
totalStaked += amount;
userStakedAmount[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
/**
* @dev Withdraw staked tokens
* @param amount Amount to withdraw
*/
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
require(userStakedAmount[msg.sender] >= amount, "Not enough staked");
totalStaked -= amount;
userStakedAmount[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
/**
* @dev Claim accumulated rewards
*/
function claimRewards() external nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
stakingToken.safeTransfer(msg.sender, reward);
emit RewardsClaimed(msg.sender, reward);
}
}
/**
* @dev Exit staking and claim rewards
*/
function exit() external nonReentrant updateReward(msg.sender) {
uint256 amount = userStakedAmount[msg.sender];
if (amount > 0) {
totalStaked -= amount;
userStakedAmount[msg.sender] = 0;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
stakingToken.safeTransfer(msg.sender, reward);
emit RewardsClaimed(msg.sender, reward);
}
}
/**
* @dev Get current APY
* @return Annual percentage yield (scaled by 1e18)
*/
function getAPY() external view returns (uint256) {
if (totalStaked == 0) return 0;
// Calculate yearly rewards based on current rate
uint256 yearlyRewards = rewardRate * 365 days;
// APY = (yearlyRewards / totalStaked) * 100 * 1e18
return (yearlyRewards * 100 * 1e18) / totalStaked;
}
}
You'll need to enter the exact parameters used when your pool was created. These are:
After entering all information:
If you continue having issues, you can contact support or ask for help in the community.