Lisk Smart Contract
Hardhat

Triển khai smart contract trên Lisk sử dụng Hardhat

Lưu ý 1: Cài đặt Node và npm trước khi bắt đầu

Lưu ý 2: Wallet deploy contract cần có balance trên Lisk Testnet

Tạo project Hardhat

  1. Tạo folder làm việc
mkdir lisk_smart_contract
cd lisk_smart_contract
  1. Cài đặt node project
npm init --y
  1. Cài đặt thư viện hardhat
npm install --save-dev hardhat
  1. Tạo hardhat project
npx hardhat

Tuỳ chỉnh Hardhat với Lisk Sepolia

  1. Cài đặt dotenv
npm install --save-dev dotenv
  1. Tạo file .env
PRIVATE_KEY=<YOUR_PRIVATE_KEY>
  1. Tuỳ chỉnh hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
 
require('dotenv').config();
 
const config: HardhatUserConfig = {
  solidity: "0.8.23",
  networks: {
    // for testnet
    'lisk-sepolia': {
      url: 'https://rpc.sepolia-api.lisk.com',
      accounts: [process.env.PRIVATE_KEY as string],
      gasPrice: 1000000000,
    },
  },
};
 
export default config;

Tạo smart contract ERC20

  1. Cài đặt thư viện OpenZeppelin
npm install --save @openzeppelin/contracts
  1. Tạo file contracts/ERC20.sol
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
 
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 
contract MyToken is ERC20 {
    constructor() ERC20("MyToken", "MT") {
        _mint(msg.sender, 1000000 * (10 ** uint256(decimals())));
    }
}

Compile smart contract ERC20

npx hardhat compile

Test smart contract ERC20

  1. Tạo file test/ERC20.test

/// test/ERC20.ts

import { expect } from "chai";
import { ethers } from "hardhat";
 
describe("MyToken", function () {
  async function deployTokenFixture() {
    const [owner, otherAccount] = await ethers.getSigners();
    const MyToken = await ethers.getContractFactory("MyToken");
    const token = await MyToken.deploy();
    const initialSupply = BigInt(1000000) * BigInt(10) ** BigInt(18); // 1 million tokens with 18 decimals
 
    return { token, owner, otherAccount, initialSupply };
  }
 
  describe("Deployment", function () {
    it("Should set the right name and symbol", async function () {
      const { token } = await deployTokenFixture();
      expect(await token.name()).to.equal("MyToken");
      expect(await token.symbol()).to.equal("MT");
    });
 
    it("Should assign the total supply of tokens to the owner", async function () {
      const { token, owner, initialSupply } = await deployTokenFixture();
      expect(await token.totalSupply()).to.equal(initialSupply);
      expect(await token.balanceOf(owner.address)).to.equal(initialSupply);
    });
  });
 
  describe("Transfers", function () {
    it("Should transfer tokens between accounts", async function () {
      const { token, owner, otherAccount } = await deployTokenFixture();
      const transferAmount = BigInt(100) * BigInt(10) ** BigInt(18); // 100 tokens
 
      await token.transfer(otherAccount.address, transferAmount);
      expect(await token.balanceOf(otherAccount.address)).to.equal(transferAmount);
      expect(await token.balanceOf(owner.address)).to.equal(
        (await token.totalSupply()) - transferAmount
      );
    });
 
    it("Should fail if sender doesn't have enough tokens", async function () {
      const { token, otherAccount } = await deployTokenFixture();
 
      await expect(
        token.connect(otherAccount).transfer(otherAccount.address, 1)
      ).to.be.revertedWithCustomError(token, "ERC20InsufficientBalance");
    });
  });
 
  describe("Allowances", function () {
    it("Should allow approved spender to transfer tokens", async function () {
      const { token, owner, otherAccount } = await deployTokenFixture();
      const transferAmount = BigInt(100) * BigInt(10) ** BigInt(18); // 100 tokens
 
      await token.approve(otherAccount.address, transferAmount);
      await token.connect(otherAccount).transferFrom(owner.address, otherAccount.address, transferAmount);
 
      expect(await token.balanceOf(otherAccount.address)).to.equal(transferAmount);
    });
 
    it("Should fail if spender doesn't have enough allowance", async function () {
      const { token, owner, otherAccount } = await deployTokenFixture();
      const transferAmount = BigInt(100) * BigInt(10) ** BigInt(18); // 100 tokens
 
      await token.approve(otherAccount.address, transferAmount - BigInt(1));
      await expect(
        token.connect(otherAccount).transferFrom(owner.address, otherAccount.address, transferAmount)
      ).to.be.revertedWithCustomError(token, "ERC20InsufficientAllowance");
    });
  });
});
 
  1. Chạy test
npx hardhat test
 
  MyToken
    Deployment
       Should set the right name and symbol (320ms)
       Should assign the total supply of tokens to the owner
    Transfers
       Should transfer tokens between accounts
       Should fail if sender doesn't have enough tokens
    Allowances
      ✔ Should allow approved spender to transfer tokens
      ✔ Should fail if spender doesn't have enough allowance
 
 
  6 passing (373ms)
 

Deploy smart contract ERC20

  1. Tạo file ignition/modules/ERC20.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
 
const ERC20Module = buildModule("ERC20Module", (m) => {
  const token = m.contract("MyToken", []);
 
  return { token };
});
 
export default ERC20Module;
  1. Deploy và tương tác với smart contract ERC20
npx hardhat ignition deploy ignition/modules/ERC20.ts --network lisk-sepolia
  1. Kết quả
 Confirm deploy to network lisk-sepolia (4202)? … yes
Hardhat Ignition 🚀
 
Deploying [ ERC20Module ]
 
Batch #1
  Executed ERC20Module#MyToken
 
[ ERC20Module ] successfully deployed 🚀
 
Deployed Addresses
 
ERC20Module#MyToken - 0x962c0940d72E7Db6c9a5F81f1cA87D8DB2B82A23
 

Tương tác write and read ERC20 contract

  1. Tạo file scripts/transfer.ts /// scripts/transfer.ts
import { ethers } from "hardhat";
 
async function main() {
  // Thay đổi token address và recipient address 
  const tokenAddress = "0x962c0940d72E7Db6c9a5F81f1cA87D8DB2B82A23";
  const recipientAddress = "0x783FC27915754512E72b5811599504eCa458E4C5";
  const transferAmount = ethers.parseEther("100"); // 100 tokens
 
  const token = await ethers.getContractAt("MyToken", tokenAddress);
 
 
  const [signer] = await ethers.getSigners();
  console.log("Transferring from:", signer.address);
 
 
  const tx = await token.transfer(recipientAddress, transferAmount);
  console.log("Transfer transaction hash:", tx.hash);
 
 
  await tx.wait();
  console.log("Transfer completed!");
 
 
  const senderBalance = await token.balanceOf(signer.address);
  const recipientBalance = await token.balanceOf(recipientAddress);
  
  console.log("Sender balance:", ethers.formatEther(senderBalance));
  console.log("Recipient balance:", ethers.formatEther(recipientBalance));
}
 
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
  1. Chạy script
npx hardhat run scripts/transfer.ts --network lisk-sepolia
  1. Kết quả
Transferring from: 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac
Transfer transaction hash: 0x5ebf04c2a64c3e4c8a0e18ea6d97bcbcb01897629708a31923bec90e17c58406
Transfer completed!
Sender balance: 999900.0
Recipient balance: 100.0
  1. Kiểm tra transaction hash trên Lisk Testnet Block Explorer
https://sepolia-blockscout.lisk.com/tx/0x5ebf04c2a64c3e4c8a0e18ea6d97bcbcb01897629708a31923bec90e17c58406

Verify smart contract ERC20

  1. Tuỳ chỉnh thêm explorer vào hardhat.config.ts
 
const config: HardhatUserConfig = {
  ....
  etherscan: {
    
    apiKey: {
      "lisk-sepolia": "123"
    },
    customChains: [
      {
          network: "lisk-sepolia",
          chainId: 4202,
          urls: {
              apiURL: "https://sepolia-blockscout.lisk.com/api",
              browserURL: "https://sepolia-blockscout.lisk.com"
          }
      }
    ]
  },
  sourcify: {
    enabled: false
  },
};
 
  1. Câu lệnh verify smart contract
npx hardhat verify --network lisk-sepolia <contract_address> <constructor_arguments>
  1. Kết quả
npx hardhat verify --network lisk-sepolia 0x962c0940d72E7Db6c9a5F81f1cA87D8DB2B82A23
[WARNING] Network and explorer-specific api keys are deprecated in favour of the new Etherscan v2 api. Support for v1 is expected to end by May 31st, 2025. To migrate, please specify a single Etherscan.io api key the apiKey config value.
Successfully submitted source code for contract
contracts/ERC20.sol:MyToken at 0x962c0940d72E7Db6c9a5F81f1cA87D8DB2B82A23
for verification on the block explorer. Waiting for verification result...
 
Successfully verified contract MyToken on the block explorer.
https://sepolia-blockscout.lisk.com/address/0x962c0940d72E7Db6c9a5F81f1cA87D8DB2B82A23#code