Mục đích bài viết giúp các bạn biết cách:

  • Kết nối ví Metamask với dự án dApp
  • Đọc, cập nhật dữ liệu từ hợp đồng thông minh bằng cách sử dụng API Alchemy Web3
  • Đăng ký giao dịch Ethereum bằng Metamask

I. Demo

MetaMask kết nối đến smart constract để thực hiện read/update message

II. Implement

1. Tạo Smart Contract

pragma solidity ^0.8.13;

contract HelloWorld {
    // Emitted when update function is called
   // Smart contract events are a way for your contract to communicate 
   // that something happened on the blockchain to your app front-end,
   // which can be 'listening' for certain events and take action when they happen.
   event UpdatedMessages(string oldStr, string newStr);

   string public message;

   // Constructors are used to initialize the contract's data.
   constructor(string memory initMessage) {
      message = initMessage;
   }

   function update(string memory newMessage) public {
      string memory oldMsg = message;
      message = newMessage;
      emit UpdatedMessages(oldMsg, newMessage);
   }
}

・Trước khi tạo smart contract cần 3 thông  tin: API_URL, API_KEY, PRIVATE_KEY
    API_URL = "https://eth-ropsten.alchemyapi.io/v2/your-api-key"
    API_KEY = "your-api-key"
    PRIVATE_KEY = "your-private-account-address"

API_URL, API_KEY : kết nối đến Ethereum network. Ví dụ như bên dưới

API_URL = "https://eth-ropsten.alchemyapi.io/v2/GS9-WPWXWl4STCEb1-cKq8lSI89ErdDu"
API_KEY = "GS9-WPWXWl4STCEb1-cKq8lSI89ErdDu"
PRIVATE_KEY = "1f71ebbb8d0f3f36c443e8043dac5e379c71c4485c681cc2445c8c3c0bda454b"

PRIVATE_KEY : lấy từ Ethereum account (address)

2. Compile

Sau khi compile constract sẽ tạo ra file HelloWorld.json có các abi : như function constructor, update...

{
  "_format": "hh-sol-artifact-1",
  "contractName": "HelloWorld",
  "sourceName": "contracts/HelloWorld.sol",
  "abi": [
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "initMessage",
          "type": "string"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "string",
          "name": "oldStr",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "newStr",
          "type": "string"
        }
      ],
      "name": "UpdatedMessages",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "message",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "newMessage",
          "type": "string"
        }
      ],
      "name": "update",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ],
  ...
}

3. Deploy

thực hiện run file deploy.js có nội dung như bên dưới

async function main() {
    const HelloWorld = await ethers.getContractFactory("HelloWorld");

    // Start deployment, returning a promise that resolves to a contract object
    const hello_world = await HelloWorld.deploy("Hello World!");
    console.log("Contract deployed to address:", hello_world.address);}
 
 main()
   .then(() => process.exit(0))
   .catch(error => {
     console.error(error);
     process.exit(1);
   });

=> Sau khi deploy ok sẽ tạo ra CONTRACT_ADDRESS. Ví dụ như bên dưới

CONTRACT_ADDRESS = "0xc8C82bf123644118B1C8bB01A881C66A8Ee72e5D"

Tóm lại để có được CONTRACT_ADDRESS thì cần các thông tin ví dụ như hình bên dưới

API_URL = "https://eth-ropsten.alchemyapi.io/v2/GS9-WPWXWl4STCEb1-cKq8lSI89ErdDu"
API_KEY = "GS9-WPWXWl4STCEb1-cKq8lSI89ErdDu"
PRIVATE_KEY = "1f71ebbb8d0f3f36c443e8043dac5e379c71c4485c681cc2445c8c3c0bda454b"
CONTRACT_ADDRESS = "0xc8C82bf123644118B1C8bB01A881C66A8Ee72e5D"

4. Tương tác Read/Update Message với Smart Contract

Trước khi tương tác với smart contract cần hiểu 3 khái niệm
Provider : là một node provider cho phép quyền read, write đến blockchain.
Signer : là Ethereum account có khả năng sign transactions.
Contract : là object đại diện cho contract deployed on-chain.

File interact.js chứa đầy đủ thông tin trong source.

require("dotenv").config();
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey);

const contractABI = require("../contract-abi.json");
const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A";

export const helloWorldContract = new web3.eth.Contract(
  contractABI,
  contractAddress
);

export const loadCurrentMessage = async () => {
  const message = await helloWorldContract.methods.message().call();
  return message;
};

export const connectWallet = async () => {
  if (window.ethereum) {
    try {
      const addressArray = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      const obj = {
        status: "👆🏽 Write a message in the text-field above.",
        address: addressArray[0],
      };
      return obj;
    } catch (err) {
      return {
        address: "",
        status: "😥 " + err.message,
      };
    }
  } else {
    return {
      address: "",
      status: (
        <span>
          <p>
            {" "}
            🦊{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};

export const getCurrentWalletConnected = async () => {
  if (window.ethereum) {
    try {
      const addressArray = await window.ethereum.request({
        method: "eth_accounts",
      });
      if (addressArray.length > 0) {
        return {
          address: addressArray[0],
          status: "👆🏽 Write a message in the text-field above.",
        };
      } else {
        return {
          address: "",
          status: "🦊 Connect to Metamask using the top right button.",
        };
      }
    } catch (err) {
      return {
        address: "",
        status: "😥 " + err.message,
      };
    }
  } else {
    return {
      address: "",
      status: (
        <span>
          <p>
            {" "}
            🦊{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};

export const updateMessage = async (address, message) => {
  //input error handling
  if (!window.ethereum || address === null) {
    return {
      status:
        "💡 Connect your Metamask wallet to update the message on the blockchain.",
    };
  }

  if (message.trim() === "") {
    return {
      status: "❌ Your message cannot be an empty string.",
    };
  }
  //set up transaction parameters
  const transactionParameters = {
    to: contractAddress, // Required except during contract publications.
    from: address, // must match user's active address.
    data: helloWorldContract.methods.update(message).encodeABI(),
  };

  //sign the transaction
  try {
    const txHash = await window.ethereum.request({
      method: "eth_sendTransaction",
      params: [transactionParameters],
    });
    return {
      status: (
        <span>
          ✅{" "}
          <a target="_blank" href={`https://ropsten.etherscan.io/tx/${txHash}`}>
            View the status of your transaction on Etherscan!
          </a>
          <br />
          ℹ️ Once the transaction is verified by the network, the message will
          be updated automatically.
        </span>
      ),
    };
  } catch (error) {
    return {
      status: "😥 " + error.message,
    };
  }
};

III. Tài liệu tham khảo

https://docs.alchemy.com/alchemy/tutorials/hello-world-smart-contract/part-4#part-4-marrying-web2-and-web3-connecting-your-smart-contract-to-a-frontend-project