Triển khai 1 token từ smart contract trên project

Vinh Phan

Xin chào mọi người, tiếp tục trong series blockchain mình sẽ chia sẽ tiếp cho mọi người cách triển khai 1 token lên project. Hãy xem lại bài triển khải 1 token từ smart contract trên remix để hiểu rõ thêm nhé.

Nếu bạn là người mới bắt đầu phát triển blockchain và không biết bắt đầu từ đâu hoặc nếu bạn chỉ muốn hiểu cách triển khai và tương tác với các hợp đồng thông minh thì hướng dẫn này là dành cho bạn. Mình sẽ hướng dẫn cách tạo và triển khai một hợp đồng thông minh đơn giản trên mạng thử nghiệm sepolia bằng cách sử dụng ví ảo (Metamask), Solidity, Hardhat và Alchemy (đừng lo lắng nếu bạn chưa hiểu ý nghĩa của những điều này, mình sẽ giải thích cụ thể ^^!).

Những kiến thức bạn nên đọc để hiểu rõ bài viết này:
Blockchain là gì
Vì sao web3 sẽ là tương lai của ứng dụng web
Blockchain wallet là gì, chi tiết từng loại ví trong blockchain
Metamask là gì? Một số tuỳ chỉnh cho metamask để xây dựng ứng dụng phi tập trung
Sự khác nhau cơ bản của coin và token
Triển khai 1 token từ smart contract trên remix

Kết nối với mạng Ethereum

Có nhiều cách để đưa ra yêu cầu đối với chuỗi Ethereum. Để đơn giản, mình sẽ sử dụng tài khoản miễn phí trên Alchemy, nền tảng và API dành cho nhà phát triển blockchain cho phép chúng tôi giao tiếp với chuỗi Ethereum mà không cần phải chạy các nút của riêng mình. Nền tảng này cũng có các công cụ dành cho nhà phát triển để theo dõi và phân tích mà mình sẽ tận dụng trong hướng dẫn này để hiểu những gì đang diễn ra trong quá trình triển khai hợp đồng thông minh.

Nếu bạn chưa có tài khoản alchemy, hãy đăng ký nó ngay.

Tạo ứng dụng trong alchemy

Sau khi tạo tài khoản Alchemy, bạn có thể tạo khóa API bằng cách tạo một ứng dụng. Điều này sẽ cho phép chúng tôi gửi yêu cầu tới mạng thử nghiệm Sepolia. Nếu bạn không quen với testnet, hãy xem hướng dẫn này.

Điều hướng đến trang “Create App” trong Bảng điều khiển của bạn bằng cách di chuột qua “Application” trong thanh điều hướng và nhấp vào “Create App”

token

Nhập tên ứng dụng của bạn và đừng quên chọn mạng là sepolia nhé

Tạo một tài khoản trên ethereum

Bạn cần một tài khoản Ethereum để gửi và nhận giao dịch. Hãy sử dụng Metamask, ví ảo trong trình duyệt dùng để quản lý địa chỉ tài khoản Ethereum của bạn. Mình đã có bài viết về ví metamask rồi, bạn hãy xem lại nhé.

Bạn có thể tải xuống và tạo tài khoản Metamask miễn phí tại đây. Khi bạn đang tạo tài khoản hoặc nếu bạn đã có tài khoản, hãy đảm bảo chuyển sang “Mạng thử nghiệm Sepolia” ở phía trên bên phải.

token

Thêm ETH cho mạng thử nghiệm sepolia

Để triển khai hợp đồng thông minh vào mạng thử nghiệm, sẽ cần một số Eth giả. Để nhận Eth, bạn có thể truy cập https://sepoliafaucet.com/ và nhập địa chỉ tài khoản ví metamask của bạn, sau đó nhấp vào “Send me ETH”. Có thể mất một thời gian để nhận được Eth giả của bạn do lưu lượng truy cập mạng. Bạn sẽ thấy Eth trong tài khoản Metamask của mình ngay sau đó!

Khởi tạo project

Đầu tiên, chúng ta cần tạo một thư mục cho dự án của mình.

mkdir create-token
cd create-token

Bây giờ chúng ta đang ở trong thư mục dự án, chúng ta sẽ sử dụng npm init để khởi tạo dự án. Nếu bạn chưa cài đặt npm, hãy làm theo các hướng dẫn sau (chúng tôi cũng sẽ cần Node.js vì vậy hãy tải xuống luôn!).

npm init # (or npm init --yes)

Việc bạn trả lời các câu hỏi cài đặt như thế nào thực sự không quan trọng, đây là cách chúng tôi đã thực hiện để tham khảo:

package name: (create-token)
version: (1.0.0)
description: create token
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)

About to write to /Users/.../.../.../hello-world/package.json:

{
   "name": “create-token,
   "version": "1.0.0",
   "description": "create token",
   "main": "index.js",
   "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
   "license": "ISC"
}

Cài đặt HARDHAT

Hardhat là môi trường phát triển để biên dịch, triển khai, kiểm tra và gỡ lỗi phần mềm Ethereum của bạn. Nó giúp các nhà phát triển khi xây dựng hợp đồng thông minh và dApps cục bộ trước khi triển khai vào chuỗi trực tiếp.

npm install --save-dev hardhat

Tạo dự án với HARDHAT

Trong thư mục create-token, chạy lệnh sau:

npx hardhat

Sau đó, bạn sẽ thấy thông báo chào mừng và tùy chọn để chọn những gì bạn muốn làm. Chọn “tạo một hardhat.config.js trống”:

888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.0.11 👷‍

What do you want to do?
Create a sample project
Create an empty hardhat.config.js
Quit

Thao tác này sẽ tạo tệp hardhat.config.js cho chúng tôi, đây là nơi chúng ta sẽ chỉ định tất cả thiết lập cho dự án của mình.

Thêm folder vào project

Để giữ cho dự án của chúng ta được tổ chức, chúng ta sẽ tạo hai thư mục mới. Điều hướng đến thư mục gốc của dự án create-token bằng dòng lệnh và gõ

mkdir contracts
mkdir scripts

Contracts là nơi chúng tôi sẽ lưu giữ tệp mã hợp đồng thông minh create-token của mình
Scripts/ là nơi chúng ta sẽ giữ các tập lệnh để triển khai và tương tác với hợp đồng của mình

Viết smart contract triển khai token

Điều hướng đến thư mục “contracts” và tạo một tệp mới có tên Token.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function mint(address account, uint256 amount) external returns (bool);
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract Token is IERC20 {
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    string public message;

    struct Reservation {
        uint256 id;
        address admin;
        address teacher;
        address student;
        uint256 date;
        uint256 amount;
        uint256 amountForAdmin;
        uint256 amountForTeacher;
        bool paid;
        bool fulfilled;
    }

    mapping(uint256 => Reservation) public reservations;
    uint256 public reservationCounter;

    event ReservationCreated(uint256 id, address admin, address teacher, address student, uint256 date, uint256 amount);

    constructor(string memory initMessage) {
        message = initMessage;
        name = "TRT vertsion 1";
        symbol = "TRT";
        decimals = 18;
        _totalSupply = 1000000 * 10**uint256(decimals);
        _balances[msg.sender] = _totalSupply;
    }

    function createReservation(address teacher, address student, uint256 amount, uint256 amountForAdmin, uint256 amountForTeacher, uint256 date) external payable returns(uint256) {
        require(amount > 0, "Gia tri phai lon hon 0");
        reservationCounter++;
        reservations[reservationCounter] = Reservation(reservationCounter, msg.sender, teacher, student, date, amount, amountForAdmin, amountForTeacher, false, false); // create 1 Reservation vào reservations
        _balances[student] -= amount; // tru token cua student
        emit ReservationCreated(reservationCounter, msg.sender, teacher, student, date, amount);
        return reservationCounter;
    }

    function fulfillReservation(uint256 reservationId) external payable returns(bool) {
        Reservation storage reservation = reservations[reservationId];
        require(!reservation.fulfilled, "fulfill da duoc thuc hien");

        reservation.fulfilled = true;
        _balances[reservation.teacher] += reservation.amountForTeacher; // transfer token for teacher
        _balances[reservation.admin] += reservation.amountForAdmin; // transfer token for admin
        return true;
    }

    function canCelReservation(uint256 reservationId) external payable returns(bool) {
        Reservation storage reservation = reservations[reservationId];
        require(!reservation.paid, "Cancel da duoc thuc hien");

        reservation.paid = true;
        _balances[reservation.student] += reservation.amount; // refund token for student
        return true;
    }


    function mint(address account, uint256 amount) public override returns (bool) {
        require(account != address(0), "MintableToken: mint to the zero address");
        _totalSupply += amount;
        _balances[account] += amount;
        return true;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        require(amount <= _balances[msg.sender], "Insufficient balance");

        _balances[msg.sender] -= amount;
        _balances[recipient] += amount;

        emit Transfer(msg.sender, recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _allowances[spender][msg.sender] = amount;

        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        require(amount <= _balances[sender], "Insufficient balance");
        require(amount <= _allowances[sender][msg.sender], "Insufficient allowance");

        _balances[sender] -= amount;
        _balances[recipient] += amount;
        _allowances[sender][msg.sender] -= amount;

        emit Transfer(sender, recipient, amount);
        return true;
    }
}

Đoạn code trên được mô tả trong bài viết này, mọi người xem lại nhé.

Kết nối Metamask & Alchemy với dự án của bạn

Đầu tiên, cài đặt gói dotenv trong thư mục dự án của bạn:

npm install dotenv --save

Lấy thông tin từ alchemy

token

.env của bạn sẽ trông như thế này:

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY = "your-metamask-private-key"

Cài đặt Ethers.js

Ethers.js là một thư viện giúp tương tác và đưa ra yêu cầu tới Ethereum dễ dàng hơn bằng cách gói các phương thức JSON-RPC tiêu chuẩn bằng các phương thức thân thiện với người dùng hơn.

Hardhat giúp việc tích hợp các Plugin để có thêm công cụ và chức năng mở rộng trở nên cực kỳ dễ dàng. Chúng tôi sẽ tận dụng plugin Ethers để triển khai hợp đồng (Ethers.js có một số phương pháp triển khai hợp đồng siêu rõ ràng).

Trong loại thư mục dự án của bạn:

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

Cập nhật lại hardhat.config.js

Cập nhật hardhat.config.js của bạn trông như thế này:

/**
* @type import('hardhat/config').HardhatUserConfig
*/

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");

const { API_URL, PRIVATE_KEY } = process.env;

module.exports = {
   solidity: "0.7.3",
   defaultNetwork:sepolia,
   networks: {
      hardhat: {},
      sepolia: {
         url: API_URL,
         accounts: [`0x${PRIVATE_KEY}`]
      }
   },
}

Biên soạn smart contract

Để đảm bảo mọi thứ vẫn hoạt động cho đến nay, hãy biên soạn hợp đồng của chúng ta. Tác vụ biên dịch là một trong những tác vụ hardhat được tích hợp sẵn.

npx hardhat compile

Viết kịch bản triển khai

Bây giờ hợp đồng của chúng ta đã được viết và tệp cấu hình của chúng ta đã sẵn sàng, đã đến lúc viết tập lệnh triển khai hợp đồng.

Trong folder scripts, tạo file: deploy.js, thêm code sau:

async function main() {
   const Token = await ethers.getContractFactory(“Token”);

   // Start deployment, returning a promise that resolves to a contract object
   const token = await Token();
   console.log("Contract deployed to address:", token);
}

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

const HelloWorld = await ethers.getContractFactory(“Token”);

ContractFactory trong ethers.js là một bản tóm tắt được sử dụng để triển khai các hợp đồng thông minh mới, vì vậy Token đây là một nhà máy dành cho các phiên bản của hợp đồng hello world của chúng tôi. Khi sử dụng plugin hardhat-ethers ContractFactory và Contract, các phiên bản được kết nối với người ký (chủ sở hữu) đầu tiên theo mặc định.

const token = await Token.deploy();

Việc gọi deploy() trên ContractFactory sẽ bắt đầu triển khai và trả về một Promise phân giải thành đối tượng Hợp đồng. Đây là đối tượng có phương thức cho từng chức năng hợp đồng thông minh của chúng tôi.

Triển khai smart contract

npx hardhat run scripts/deploy.js --network sepolia

Kết quả sẽ như thế này:

Contract deployed to address: 0xxxxxxxxxxxxxxxxxxxxxxx34xx243

Đến đây, bạn có thể lên sepolia testnet để kiểm tra hợp đồng của mình hoặc vào lại tài khoản alchemy của bạn để kiểm tra giao dịch

Kết thúc bài viết nhé mọi người, hi vọng mọi người có thể làm theo được.

Viết một bình luận