<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aliyu Anate</title>
    <description>The latest articles on DEV Community by Aliyu Anate (@druidde).</description>
    <link>https://dev.to/druidde</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2516229%2Fbb73e48b-f2be-41aa-83d8-cd357c0e2eaf.png</url>
      <title>DEV Community: Aliyu Anate</title>
      <link>https://dev.to/druidde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/druidde"/>
    <language>en</language>
    <item>
      <title>Casually Deploying a Staking dApp on CrossFi Chain with Hardhat: A Technical Walkthrough</title>
      <dc:creator>Aliyu Anate</dc:creator>
      <pubDate>Fri, 28 Feb 2025 11:48:14 +0000</pubDate>
      <link>https://dev.to/druidde/casually-deploying-a-staking-dapp-on-crossfi-chain-with-hardhat-a-technical-walkthrough-o4j</link>
      <guid>https://dev.to/druidde/casually-deploying-a-staking-dapp-on-crossfi-chain-with-hardhat-a-technical-walkthrough-o4j</guid>
      <description>&lt;p&gt;In this article, we’ll explore how to build and deploy a staking dApp on the CrossFi chain using Hardhat. The dApp lets users stake XFI tokens and, after a chosen duration, withdraw their stake along with MPX rewards. We’ll explain the token contracts, the staking smart contract, and the process of deploying them with Hardhat.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Overview of the Contracts
&lt;/h2&gt;

&lt;p&gt;The project consists of three main Solidity contracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XFIToken Contract:&lt;/strong&gt; An ERC20 token that users stake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MPXToken Contract:&lt;/strong&gt; An ERC20 token used for rewards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StakeXFI Contract:&lt;/strong&gt; The staking contract that connects the two tokens and handles staking, reward calculation, and withdrawals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All contracts are built using OpenZeppelin’s ERC20 libraries and follow the MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Token Contracts: XFIToken and MPXToken
&lt;/h2&gt;

&lt;p&gt;Both token contracts inherit from OpenZeppelin’s &lt;em&gt;&lt;strong&gt;ERC20&lt;/strong&gt;&lt;/em&gt;, &lt;em&gt;&lt;strong&gt;ERC20Burnable&lt;/strong&gt;&lt;/em&gt;, and &lt;em&gt;&lt;strong&gt;Ownable&lt;/strong&gt;&lt;/em&gt; contracts. They are very similar; here’s a simplified version of the XFIToken contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract XFIToken is ERC20, ERC20Burnable, Ownable {
    constructor(address initialOwner) ERC20("XFIToken", "XFI") Ownable(initialOwner) {}

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MPXToken is nearly identical, differing only in the token’s name and symbol. Both contracts allow the owner to mint new tokens, which can later be used in staking and rewards.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The StakeXFI Contract
&lt;/h2&gt;

&lt;p&gt;The core of the dApp is the &lt;em&gt;&lt;strong&gt;StakeXFI&lt;/strong&gt;&lt;/em&gt; contract. It connects the XFI and MPX tokens and manages staking. Let’s break down its key components.&lt;/p&gt;

&lt;h3&gt;
  
  
  a. Contract State Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IERC20 public XFIContract;
IERC20 public MPXContract;
address internal owner;
address internal newOwner;
bool internal locked;

uint256 immutable MINIMUM_STAKE_AMOUNT = 1000 * (10**18);
uint256 immutable MAXIMUM_STAKE_AMOUNT = 100000 * (10**18);
uint32 internal constant REWARD_PER_SECOND = 1000000; // Reward rate per second

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token Interfaces:&lt;/strong&gt; The contract holds references to the XFI and MPX token contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership &amp;amp; Security:&lt;/strong&gt; Variables for owner management and a locked flag serve as a reentrancy guard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staking Limits:&lt;/strong&gt; Minimum and maximum staking amounts are set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reward Rate:&lt;/strong&gt; A constant that defines how many reward tokens are generated per second.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  b. Struct and Mapping for Staking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Staking {
    uint256 amount;
    uint256 startTime;
    uint256 duration;
    bool hasWithdrawn;
}

mapping (address =&amp;gt; Staking[]) stakers;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staking Structure:&lt;/strong&gt; Each staking entry records the staked amount, start time, the time when the stake matures (duration), and a flag to check if it has been withdrawn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mapping:&lt;/strong&gt; Each staker (an address) can have multiple stakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  c. The stake Function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function stake(uint256 _amount, uint256 _duration) external reentrancyGuard {
    require(msg.sender != address(0), "Zero address not allowed");
    require(_amount &amp;gt;= MINIMUM_STAKE_AMOUNT &amp;amp;&amp;amp; _amount &amp;lt;= MAXIMUM_STAKE_AMOUNT, "Amount is out of range");
    require(_duration &amp;gt; 0, "Duration is too short");
    require(XFIContract.balanceOf(msg.sender) &amp;gt;= _amount, "You don't have enough");
    require(XFIContract.allowance(msg.sender, address(this)) &amp;gt;= _amount, "Amount allowed is not enough");

    XFIContract.transferFrom(msg.sender, address(this), _amount);

    Staking memory staking;
    staking.amount = _amount;
    staking.duration = block.timestamp + _duration;
    staking.startTime = block.timestamp;

    stakers[msg.sender].push(staking);

    emit DepositSuccessful(msg.sender, _amount, block.timestamp);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; Checks if the sender address is valid, the staking amount is within range, the duration is positive, and that the user has enough balance and allowance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Transfer:&lt;/strong&gt; Transfers the staked XFI tokens from the user to the contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recording the Stake:&lt;/strong&gt; A new Staking entry is created and stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Emission:&lt;/strong&gt; An event is emitted upon successful deposit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  d. The withdrawStake Function
&lt;/h3&gt;

&lt;p&gt;Users call this function to withdraw their stake after the staking period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function withdrawStake(uint8 _index) external reentrancyGuard returns (bool) {
    require(msg.sender != address(0), "Zero address not allowed");
    require(_index &amp;lt; stakers[msg.sender].length, "Out of range");

    Staking storage staking = stakers[msg.sender][_index];
    require(block.timestamp &amp;gt; staking.duration, "Not yet time");
    require(!staking.hasWithdrawn, "Stake already withdrawn");

    uint256 amountStaked_ = staking.amount;
    uint256 rewardAmount_ = calculateReward(staking.startTime, staking.duration);

    staking.hasWithdrawn = true;
    staking.amount = 0;
    staking.startTime = 0;
    staking.duration = 0;

    XFIContract.transfer(msg.sender, amountStaked_);
    MPXContract.transfer(msg.sender, rewardAmount_);

    emit WithdrawalSuccessful(msg.sender, amountStaked_, rewardAmount_);

    return true;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; Ensures that the index is valid, the staking period has ended, and that the stake hasn’t been withdrawn already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reward Calculation:&lt;/strong&gt; Uses the &lt;em&gt;&lt;strong&gt;calculateReward&lt;/strong&gt;&lt;/em&gt; function to determine the reward based on the staking period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resetting State:&lt;/strong&gt; Marks the stake as withdrawn and resets its fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Transfers:&lt;/strong&gt; Returns the staked XFI tokens and sends the calculated MPX rewards to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Emission:&lt;/strong&gt; An event confirms the successful withdrawal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  e. Reward Calculation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function calculateReward(uint256 _startTime, uint256 _endTime) private pure returns (uint256) {
    uint256 stakeDuration = _endTime - _startTime;
    return stakeDuration * REWARD_PER_SECOND;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reward is simply the product of the staking duration (in seconds) and the reward rate. For example, if a user stakes for 100 seconds and the reward rate is 1,000,000 per second, the reward would be 100 × 1,000,000.&lt;/p&gt;

&lt;h3&gt;
  
  
  f. Ownership and Utility Functions
&lt;/h3&gt;

&lt;p&gt;Additional functions manage contract ownership and provide utility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ownership Transfer:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;transferOwnership&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;claimOwnership&lt;/em&gt;&lt;/strong&gt; allow safe transition of control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Information Getters:&lt;/strong&gt; Functions such as &lt;strong&gt;&lt;em&gt;getStakerInfo&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;getContractMPXBalance&lt;/em&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;em&gt;getContractXFIBalance&lt;/em&gt;&lt;/strong&gt; provide insights into the staking state and contract balances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reentrancy Guard:&lt;/strong&gt; A modifier ensures functions like &lt;strong&gt;&lt;em&gt;stake&lt;/em&gt;&lt;/strong&gt;and &lt;strong&gt;&lt;em&gt;withdrawStake&lt;/em&gt;&lt;/strong&gt; are protected from reentrancy attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Deploying the Contracts with Hardhat
&lt;/h2&gt;

&lt;p&gt;Hardhat is a development environment that allows you to compile, deploy, test, and debug Ethereum (and EVM-compatible) contracts. Here’s how you can deploy these contracts on CrossFi chain using Hardhat.&lt;/p&gt;

&lt;h3&gt;
  
  
  a. Setting Up the Hardhat Environment
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Initialize a Hardhat project:
&lt;/h4&gt;

&lt;p&gt;using your terminal&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx hardhat init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install Dependencies:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install --save-dev @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure hardhat.config.js:&lt;/strong&gt;&lt;br&gt;
Make sure to add the CrossFi chain network settings. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

require("dotenv").config();

const config: HardhatUserConfig = {
  solidity: "0.8.26",
  networks: {
    crossFi: {
      url: process.env.CROSSFI_RPC_URL,
      accounts: [process.env.PRIVATE_KEY as string],
      gasPrice: 1000000000,
    },
  }
};

export default config;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Storing RPC URL and Private Key Securely&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of hardcoding sensitive details like your RPC URL and private key, store them in a .env file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a .env file&lt;/strong&gt;&lt;br&gt;
In your project root, create a file named .env and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CROSSFI_RPC_URL=https: https://rpc.testnet.ms
PRIVATE_KEY=your-private-key

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Keep this private! Never share your private key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rpc.testnet.ms" rel="noopener noreferrer"&gt;https://rpc.testnet.ms&lt;/a&gt; is the rpc url for the crossFi testnet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. Install dotenv&lt;/strong&gt;&lt;br&gt;
If not installed, run:&lt;br&gt;
&lt;code&gt;npm install dotenv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;add the .env file to the gitIgnore&lt;/p&gt;
&lt;h3&gt;
  
  
  b. Writing a Deployment Script
&lt;/h3&gt;

&lt;p&gt;Create a deployment script (e.g., &lt;em&gt;scripts/deploy.js&lt;/em&gt;) that deploys the XFIToken, MPXToken, and StakeXFI contracts sequentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const hre = require("hardhat");
const {ethers} = hre

async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying contracts with account:", deployer.address);


  const XFIToken = await ethers.getContractFactory("XFIToken");
  const xfiToken = await XFIToken.deploy(deployer.address);
  await xfiToken.deployed();
  console.log("XFIToken deployed to:", xfiToken.address);


  const MPXToken = await ethers.getContractFactory("MPXToken");
  const mpxToken = await MPXToken.deploy(deployer.address);
  await mpxToken.deployed();
  console.log("MPXToken deployed to:", mpxToken.address);


  const StakeXFI = await ethers.getContractFactory("StakeXFI");
  const stakeXFI = await StakeXFI.deploy(xfiToken.address, mpxToken.address);
  await stakeXFI.deployed();
  console.log("StakeXFI deployed to:", stakeXFI.address);
}

main()
  .then(() =&amp;gt; process.exit(0))
  .catch((error) =&amp;gt; {
    console.error(error);
    process.exit(1);
  });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  c. Deploying on the CrossFi Chain
&lt;/h3&gt;

&lt;p&gt;Run the deployment script using Hardhat’s command-line interface:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx hardhat run scripts/deploy.js --network crossFi&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command deploys the token contracts and then the staking contract on the CrossFi network. Be sure that the network RPC endpoint and private key are correctly configured in your Hardhat config.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explored the Contracts:&lt;/strong&gt; We broke down the XFIToken, MPXToken, and StakeXFI contracts—examining state variables, functions, and calculations (such as reward computation).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explained Key Functions:&lt;/strong&gt; We looked in detail at staking, withdrawal, reward calculations, and ownership management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walked Through Deployment:&lt;/strong&gt; We provided a Hardhat configuration and a deployment script that sequentially deploys the token and staking contracts on the CrossFi chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This walkthrough provides a foundation for developing, testing, and deploying a staking dApp using familiar tools like Hardhat while taking full advantage of CrossFi Chain’s scalability and interoperability.&lt;/p&gt;

&lt;p&gt;Here is the link to the gitHub repo&lt;br&gt;
&lt;a href="https://github.com/Anaboy01/CrossFi-Stake" rel="noopener noreferrer"&gt;CrossFi-Stake&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>blockchain</category>
      <category>web</category>
    </item>
  </channel>
</rss>
