PublishedAt

Blockchain

ลองทำการ Read/Write Contract บน Blockchain ด้วย Viem

ลองทำการ Read/Write Contract บน Blockchain ด้วย Viem

สวัสดีครับ บล็อกนี้เป็นบล็อกบันทึกสั้นๆ เกี่ยวกับการใช้งาน Viem ในการ Read และ Write Contract ซึ่งปกติคิดว่าหลายๆ คนน่าจะใช้ตัว Ethers.js กันมากกว่า แต่หลังๆ ก็เห็นเริ่มมีคนหันมาใช้ Viem มากขึ้น ข้อดีคือ เร็วมากครับ

Viem · TypeScript Interface for Ethereum
Build reliable Ethereum apps & libraries with lightweight, composable, & type-safe modules from viem.viem.sh

Setup Viem

การติดตั้ง Viem ก็ทำผ่าน package manager ได้เลย ส่วนตัวผมใช้ Bun ก็ init และ install ได้เลย

Terminal window
bun init -y
bun install viem

โดยในตัวอย่าง Example เราจะใช้ Network Sepolia Testnet และ Token เป็น USDC

// contract address ของ USDC บน Sepolia
// https://sepolia.etherscan.io/token/0x1c7d4b196cb0c7b01d743fbc6116a902379c7238
const contractAddress = '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238'

ตัวอย่าง ABI สมมติเป็น ABI ของ ERC20 ธรรมดา แบบนี้

import { parseAbi } from 'viem'
const erc20Abi = parseAbi([
// Read function
'function balanceOf(address owner) view returns (uint256)',
'function symbol() view returns (string)',
'function decimals() view returns (uint8)',
// Write function
'function transfer(address to, uint256 amount) returns (bool)',
])

Wallet Account

จริงๆ มันมี 2 ทาง ชื่อ

  1. จาก private key ใน local ของเรา
  2. ผ่าน JSON RPC Account เช่นพวก Metamask, WalletConnect.

ในตัวอย่าง จะใช้เป็น ข้อ 1 คือ โหลด private key จาก .env โดยใช้ function privateKeyToAccount

import { privateKeyToAccount } from 'viem/accounts'
import 'dotenv/config'
const privateKey = process.env.PRIVATE_KEY
const account = privateKeyToAccount(privateKey as `0x${string}`)
console.log(`Using account: ${account.address}`)

Read Contract

การที่จะอ่าน state ของ Contract เราจะใช้ publicClient ผ่าน function createPublicClient เพื่อสร้าง client instance ขึ้นมาก่อน จากนั้นเรียก function readContract เพื่ออ่านค่า state (ตัวอย่าง ทดลองอ่านค่า Symbol)

import {
createPublicClient,
http
} from 'viem'
import { sepolia } from 'viem/chains'
// 1. สร้าง publicClient
const publicClient = createPublicClient({
chain: sepolia,
transport: http()
})
// 2. เรียก readContract โดยระบุ functionName ให้ถูกตาม ABI
const symbol = await publicClient.readContract({
address: contractAddress,
abi: erc20Abi,
functionName: 'symbol',
})
console.log(`Token Symbol: ${symbol}`)

อีกตัวอย่างนึง เช่น การดู balance ของ wallet address นั้นๆ ด้วย balanceOf โดยเราจะส่ง argument ผ่าน args สมมติ ถ้า function ใน ABI ระบุไว้ต้องใช้ 2 arguments ตัว args เราก็จะส่ง array ตามจำนวน args

import { formatUnits } from 'viem'
const balance = await publicClient.readContract({
address: contractAddress,
abi: erc20Abi,
functionName: 'balanceOf',
args: [account.address]
})
const decimals = await publicClient.readContract({
address: contractAddress,
abi: erc20Abi,
functionName: 'decimals',
})
// แปลง format จาก bigint ของ balance ให้เป็น string
const balanceFormatted = formatUnits(balance, decimals)
console.log(`Balance of ${account.address}: ${balanceFormatted} ${symbol}`)

Simulate Contract

เราใช้เพื่อเช็คว่า write contract ที่เราเรียกไปนั้น argument ถูก หรือมี revert อะไรหรือไม่ได้ ตัว simulation ไม่ได้ใช้ gas และก็ไม่ได้เปลี่ยน state ดังนั้น เราใช้ตัว publicClient ได้เลย (เหมือนกับ readContract())

ซึ่งตัว simulate จะใช้ร่วมกับ writeContract โดยเราส่ง result ที่ simulate ผ่าน ไปให้ writeContract นั่นเอง

const recipientAddress = '0x...'
const amount = parseUnits('0.15', 6)
// ตัวอย่าง ลอง simulate transfer ว่าติดปัญหาอะไรมั้ย
const { request } = await publicClient.simulateContract({
account,
address: contractAddress,
abi: erc20Abi,
functionName: 'transfer',
args: [recipientAddress, amountInSmallestUnit],
});
// ถ้าผ่าน เราก็เอา request ส่งไป writeContract step ถัดไปได้เลย

Write Contract

ขั้นตอนสุดท้าย writeContract หลังจาก simulate เรียบร้อย

เราจะใช้ createWalletClient ซึ่งจากจะสร้าง wallet account

import { createWalletClient, http } from 'viem'
import { sepolia } from 'viem/chains'
// 2. สร้าง instance walletClient จาก account private key
const walletClient = createWalletClient({
account,
chain: sepolia,
transport: http(),
})
// 3. เอา request จาก ตอน simulate() มา
const txHash = await walletClient.writeContract(request)
// 4. รอ transaction confirmed
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })
if (receipt.status === 'success') {
console.log('Transfer successful! txHash: ', txHash)
} else {
console.error('Transfer failed!', receipt)
}

อย่าลืมติดตั้ง donenv

Terminal window
bun install dotenv --dev

เพียงแค่นี้ เราก็สามารถ read/write contract ได้แล้ว

ดูตัวอย่าง Source Code เต็มๆได้ด้านล่างครับ

Happy Coding ❤️

Authors
avatar

Chai Phonbopit

เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust

Related Posts