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