Interact Smart Contract ด้วย Ethers.js

Ethers.js Mar 26, 2023

วิธีการ Interact กับ Smart Contract ด้วยการใช้ Ethers.js ไม่ว่าจะเป็นการ call function ธรรมดา ที่ไม่ได้ไปยุ่งเกี่ยวกับ state ไม่ต้องเสียค่า gas หรือการ send transaction ที่ต้องมี signer (ทั้งจาก Private Key หรือจากการ sign, การ confirm ผ่าน Client) ทั้งหมด สามารถทำผ่าน Ethers.js ได้ทั้งหมด

แต่ในตัวอย่างนี้ จะเป็นตัวอย่างง่ายๆ ในการ call function เท่านั้นนะครับ โดยตัวอย่าง ผมยกตัวอย่าง การ call function ของเหรียญ Token USDC ซึ่งเป็น ERC-20 ครับ

รายละเอียด ERC-20

ERC-20: Token Standard
Ethereum Improvement Proposals (EIPs) describe standards for the Ethereum platform, including core protocol specifications, client APIs, and contract standards.
ERC20 - OpenZeppelin Docs

Setup Project

เราใช้ JavaScript ธรรมดา (Node.js) เริ่มต้น init project แล้วก็ติดตั้ง ethers

npm init -y

npm install ethers
ตัวอย่างผมใช้ Ethers.js version 6 นะครับ  ถ้าใช้ v5 สามารถดู migrating guide ได้ที่นี่ https://docs.ethers.org/v6/migrating/

วิธีการต่อ Blockchain จาก JS Client

ตัวอย่างนี้ จะใช้การต่อผ่าน RPC (ต่อได้หลายรูปแบบ)

JSON-RPC API | ethereum.org
A stateless, light-weight remote procedure call (RPC) protocol for Ethereum clients.

โค๊ดในการ connect RPC จะเป็นแบบนี้ (ลองสร้างไฟล์ app.js ขึ้นมา)

const { ethers } = require('ethers')

const RPC_URL = 'https://rpc.ankr.com/eth'

const provider = new ethers.JsonRpcProvider(RPC_URL)

ส่วน RPC URL เราสามารถหาได้จาก Infura, Alchemy หรือในตัวอย่างผมใช้ Public RPC ของ Ankr คือ https://rpc.ankr.com/eth

เมื่อเราต่อ Blockchain ผ่าน RPC ได้แล้ว เราสามารถดึงข้อมูลต่างๆ ของ blockchain ได้ เช่น blocknumber, balance, transaction เช่น

ตัวอย่าง การดู block number ของ ethereum

const { ethers } = require('ethers')

const RPC_URL = 'https://rpc.ankr.com/eth'

const run = async () => {
  const provider = new ethers.JsonRpcProvider(RPC_URL)

  const block = await provider.getBlockNumber()
  console.log('block', block)
}

run()
  .then()
  .catch((error) => console.log(error))

ตัวอย่าง การดู balance ของ wallet address ที่เราต้องการ (ผมดูของ vitalik.eth)

const { ethers } = require('ethers')

const RPC_URL = 'https://rpc.ankr.com/eth'

const run = async () => {
  const provider = new ethers.JsonRpcProvider(RPC_URL)

  const balance = await provider.getBalance('vitalik.eth')
  console.log('balance', balance)
}

run()
  .then()
  .catch((error) => console.log(error))

แต่ผลลัพธ์ที่ได้จะเป็นตัวเลข ที่เป็นหน่วน wei เราต้องแปลงเป็น ethers ใช้ formatEthers ของ ethers.js ได้เลย

const { formatEther } = require('ethers')
console.log('balance', formatEther(balance))

ทดสอบรันดู

node app.js
balance 5149.639854481921682572

ค่าที่ได้จะเท่ากับที่ etherscan

ต่อ Contract

เราสามารถ connect Contract เพื่อ call function ของ Contract ได้ เช่น ผมจะ connect USDC เพื่อเรียก function ของ USDC เช่น totalSupply() balanceOf

สิ่งที่ต้องมีคือ

  • contractAddress - Contract Address ที่เราจะ connect
  • ABI - ABI ของ Contract ถ้าเป็น ERC20 ส่วนใหญ่ ใช้ร่วมกันได้
Application Binary Interface
Popular ABIs, an import away.
ถ้าใช้ wagmi สามารถใช้ erc20ABI ได้เลย

หรือ​ Copy Contract ABI จาก หน้า etherscan

แต่อีกวิธีที่ง่าย คือ ethers.js รองรับ ABI แบบ Human Readable เราไม่ต้องใช้ JSON ก็ได้ ตัว method ERC20 มีแบบนี้ใช่มั้ย

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

ทีนี้ ผมจะเอาแค่ decimals, balanceOf และ totalSupply ผมก็ทำแบบนี้แทน

const ERC20ABI = [
  'function symbol() public view returns (string)',
  'function decimals() public view returns (uint8)',
  'function totalSupply() public view returns (uint256)',
  'function balanceOf(address _owner) public view returns (uint256 balance)',
]

ทีนี้ เราก็สามารถ connect USDT ได้แบบนี้

const provider = new ethers.JsonRpcProvider(RPC_URL)
const usdcContract = new ethers.Contract(USDC, ERC20ABI, provider)

โค๊ดเต็มๆ คือ

const { ethers, formatUnits } = require('ethers')

const USDC = '0xdac17f958d2ee523a2206206994597c13d831ec7'
const RPC_URL = 'https://rpc.ankr.com/eth'

const ERC20ABI = [
  'function symbol() public view returns (string)',
  'function decimals() public view returns (uint8)',
  'function totalSupply() public view returns (uint256)',
  'function balanceOf(address _owner) public view returns (uint256 balance)',
]

const run = async () => {
  const provider = new ethers.JsonRpcProvider(RPC_URL)
  const usdcContract = new ethers.Contract(USDC, ERC20ABI, provider)

  const decimals = await usdcContract.decimals()

  const symbol = await usdcContract.symbol()
  const totalSupply = await usdcContract.totalSupply()
  console.log(`${symbol} totalSupply : `, formatUnits(totalSupply, decimals))

  const balance = await usdcContract.balanceOf('vitalik.eth')
  console.log('balance', formatUnits(balance, decimals))
}

run()
  .then()
  .catch((error) => console.log(error))

ลองรันดูว่าได้ผลลัพธ์เป็นยังไง

node app.js

USDT totalSupply :  35283904986.788565
balance 38.13

ทำไมใช้ formatUnits? เพราะว่า formatEthers จะแปลงเลขด้วยการใช้ 18 decimals แต่ว่า USDT decimals ไม่ใช่ 18 เลยต้องเรียก function decimals เพื่อให้ได้ decimals จริงๆ และแปลงด้วย formatUnits นั่นเอง

🎉 จบแล้ว ตัวอย่างง่ายๆ ในการ interact Smart Contract ด้วย Ethers.js

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com