Back to Single Steak

Contract Verification Guide

Learn how to verify your staking pool contract on PulseScan

Why Verify Your Contract?

Contract verification provides transparency and trust by:

1

Get Your Pool Contract Address

First, you'll need your pool's contract address. You can find this in:

Note: Each staking pool has its own unique contract address, separate from the factory contract.
2

Go to PulseScan

Navigate to https://scan.pulsechain.com and:

  1. Search for your pool contract address
  2. Click on the contract address in the results
  3. Go to the "Contract" tab
  4. Click "Verify and Publish"
3

Select Verification Method

Choose the following options:

4

Paste the Contract Source Code

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;
    }
}
5

Enter Constructor Parameters

You'll need to enter the exact parameters used when your pool was created. These are:

_stakingToken Your token's contract address
_tokenName Token name you entered (e.g., "My Token")
_tokenSymbol Token symbol you entered (e.g., "MTK")
_poolImageUrl IPFS URL or data URL of your pool image
_initialRewardRate Calculated reward rate (very small number)
_owner Your wallet address
Important: All parameters must be exactly as they were when the pool was created. If you're unsure about any values, you can view them by reading the contract on PulseScan before verification.
6

Complete Verification

After entering all information:

  1. Double-check all parameters are correct
  2. Click "Verify and Publish"
  3. Wait for the verification process to complete
  4. You should see a green checkmark indicating success!
Success! Once verified, users will be able to read your contract's source code directly on PulseScan, increasing trust and transparency.

Troubleshooting

Common Issues:

If you continue having issues, you can contact support or ask for help in the community.