ต่อจากคราวที่เเล้วที่เราได้ทำในการ Project Watch NFT กันไปเเล้ว วันนี้เราจะมาทำ การทำ Project Watch Balance กัน
โดยในรอบนี้ เราใช้ในการทำคือ ERC-20 ในการทำ
https://docs.openzeppelin.com/contracts/4.x/api/token/erc20
https://eips.ethereum.org/EIPS/eip-20
เราจะเริ่มจาก เอา Abi ของ ERC-20 มาใส่ใน config ก่อน
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]
และ function ที่เราจะเรียกใช้ในการดึงข้อมูลเหรียญต่างๆ
name() → string
จะส่งชื่อเหรียญกลับมา
symbol() → string
จะส่ง symbol ของเหรียญ
decimals() → uint8
จะส่ง decimals ของเหรียญ
ส่วนของการดึง balance เราจะใช้
balanceOf(address account) → uint256
โดย contract address ของเหรียญ เราจะเลือกมาจาก https://optimistic.etherscan.io/tokens
โดน hook ที่เราจะเขียนจะได้หน้าตาแบบนี้ โดย เราจะรับ contract address เหรียญ ในลักษณะของ Array
ซึ่งในการเรียกค่าครั้งนี่เราจะใช้ useContractReads ในการดึงข้อมูลจาก contract
import { isAddress } from 'ethers'
import { useChainId, useContractReads } from 'wagmi'
import { z } from 'zod'
import Erc20ABI from '../assets/abi/erc20.json'
export const zodAddress = z.custom<`0x${string}`>((value) => {
if (typeof value !== 'string') {
return false
}
if (!isAddress(value)) {
return false
}
return true
})
const validator = z.object({
walletAddress: zodAddress,
contractAddress: zodAddress.array(),
})
const useTokenInfo = (input?: z.input<typeof validator>) => {
const result = validator.safeParse(input)
const chainId = useChainId()
const calls =
result.success && result.data.contractAddress.length > 0
? result.data.contractAddress.map((address) => ({
abi: Erc20ABI,
functionName: 'name',
address: address,
args: [],
chainId,
}))
: []
const callsSymbol =
result.success && result.data.contractAddress.length > 0
? result.data.contractAddress.map((address) => ({
abi: Erc20ABI,
functionName: 'symbol',
address: address,
args: [],
chainId,
}))
: []
const callsDecimals =
result.success && result.data.contractAddress.length > 0
? result.data.contractAddress.map((address) => ({
abi: Erc20ABI,
functionName: 'decimals',
address: address,
args: [],
chainId,
}))
: []
const callsBalance =
result.success && result.data.contractAddress.length > 0
? result.data.contractAddress.map((address) => ({
abi: Erc20ABI,
functionName: 'balanceOf',
address: address,
args: [result.data.walletAddress],
chainId,
}))
: []
const { data: names } = useContractReads({
contracts: calls,
watch: true,
enabled: result.success,
})
const { data: symbols } = useContractReads({
contracts: callsSymbol,
watch: true,
enabled: result.success,
})
const { data: decimals } = useContractReads({
contracts: callsDecimals,
watch: true,
enabled: result.success,
})
const { data: balance } = useContractReads({
contracts: callsBalance,
watch: true,
enabled: result.success,
})
return {
names,
symbols,
decimals,
balance,
}
}
export default useTokenInfo
จากนั้นเราจะลองเรียกใน ส่วนของ ViewTokenInfo มาดูผลลัพธ์กัน
import { useState } from 'react'
import useTokenInfo from '../hooks/useTokensInfo'
import { formatUnits } from 'ethers'
type Address = `0x${string}`
export default function ViewTokenInfo() {
const [value, setValue] = useState()
const data = useTokenInfo({
contractAddress: [
'0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
'0x68f180fcCe6836688e9084f035309E29Bf0A2095',
'0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
],
walletAddress: value as Address,
})
return (
<div>
<input
placeholder='enter Address'
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{data?.names?.map((item, index) => (
<div key={index}>
name: {item.result} <br />
symbol: {data?.symbols?.[index].result}
<br />
decimals: {data?.decimals?.[index].result}
<br />
balance:
{data?.balance &&
data?.decimals &&
formatUnits(
data.balance?.[index].result.toString() || '0',
data.decimals?.[index].result || '18'
)}
</div>
))}
</div>
)
}
เมื่อได้ค่า balance ที่เราได้มา เนื่องจากค่าที่ได้มายังไม่ทำการ formatหน่วย ดังนั้น เราจะเรียกใช้ formatUnits ของ ethers มาทำการ format ค่า balance ที่เราได้ โดย units คือ ค่า decimal ของเเต่ละเหรียญ
เมื่อเราลองเรียกเเล้ว เราจะได้ผลลัพธ์ออกมาแบบนี้