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

สวัสดีครับ บล็อกนี้เป็นบล็อกบันทึกสั้นๆ เกี่ยวกับการใช้งาน Viem ในการ Read และ Write Contract ซึ่งปกติคิดว่าหลายๆ คนน่าจะใช้ตัว Ethers.js กันมากกว่า แต่หลังๆ ก็เห็นเริ่มมีคนหันมาใช้ Viem มากขึ้น ข้อดีคือ เร็วมากครับ
Setup Viem
การติดตั้ง Viem ก็ทำผ่าน package manager ได้เลย ส่วนตัวผมใช้ Bun ก็ init และ install ได้เลย
bun init -y
bun install viem
โดยในตัวอย่าง Example เราจะใช้ Network Sepolia Testnet และ Token เป็น USDC
// contract address ของ USDC บน Sepolia// https://sepolia.etherscan.io/token/0x1c7d4b196cb0c7b01d743fbc6116a902379c7238const 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 ทาง ชื่อ
- จาก private key ใน local ของเรา
- ผ่าน JSON RPC Account เช่นพวก Metamask, WalletConnect.
ในตัวอย่าง จะใช้เป็น ข้อ 1 คือ โหลด private key จาก .env
โดยใช้ function privateKeyToAccount
import { privateKeyToAccount } from 'viem/accounts'
import 'dotenv/config'
const privateKey = process.env.PRIVATE_KEYconst 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. สร้าง publicClientconst publicClient = createPublicClient({ chain: sepolia, transport: http()})
// 2. เรียก readContract โดยระบุ functionName ให้ถูกตาม ABIconst 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 ให้เป็น stringconst 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 keyconst walletClient = createWalletClient({ account, chain: sepolia, transport: http(),})
// 3. เอา request จาก ตอน simulate() มาconst txHash = await walletClient.writeContract(request)
// 4. รอ transaction confirmedconst receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })
if (receipt.status === 'success') { console.log('Transfer successful! txHash: ', txHash)} else { console.error('Transfer failed!', receipt)}
อย่าลืมติดตั้ง donenv
bun install dotenv --dev
เพียงแค่นี้ เราก็สามารถ read/write contract ได้แล้ว
ดูตัวอย่าง Source Code เต็มๆได้ด้านล่างครับ
Happy Coding ❤️
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust