在 Web3 開發過程中,檢查用戶的代幣餘額是一項基本而重要的功能。本文將詳細介紹如何使用 viem 這一現代化的以太坊庫來查詢特定地址(包括 ENS 域名)的 USDC 代幣餘額。我們將以知名的以太坊創始人 Vitalik Buterin 的 ENS 域名 vitalik.eth 作為示例,展示完整的實現流程。
viem 簡介與設置
viem 是由 wagmi 的創建者開發的現代以太坊庫,旨在提供更直觀、更高效的開發體驗,具有更快的執行速度、模組化 API 和強大的類型支持。相比其他以太坊庫,viem 提供了更現代化的開發體驗。
你可以閱讀 我之前寫的關於 viem 的文章 了解更多詳細信息。
首先,我們需要安裝並設置 viem:
// 安裝 viem
npm install viem
// 或使用 yarn
yarn add viem
接下來,創建一個基本的客戶端配置:
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
// 創建公共客戶端
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"),
});
你可以透過 Alchemy、Infura、Etherscan 等服務提供商獲取 RPC 端點。
解析 ENS 域名
在我們查詢 USDC 餘額之前,首先需要將 ENS 域名(例如 vitalik.eth)解析為對應的以太坊地址。viem 提供了方便的 API 來處理這一過程:
import { normalize } from "viem/ens";
async function resolveEnsAddress(ensName: string) {
try {
// 使用 normalize 函數處理 ENS 名稱,確保正確的格式
const address = await publicClient.getEnsAddress({
name: normalize(ensName),
});
if (!address) {
throw new Error(`無法解析 ENS 名稱:${ensName}`);
}
console.log(`${ensName} 解析為地址:${address}`);
return address;
} catch (error) {
console.error(`解析 ENS 時出錯:`, error);
throw error;
}
}
這個函數通過 getEnsAddress
方法將 ENS 名稱解析為標準的以太坊地址。
獲取 USDC 代幣餘額
要獲取 ERC-20 代幣(如 USDC)的餘額,我們需要直接與代幣的智能合約交互。這與獲取原生以太幣餘額有所不同,因為我們需要調用 ERC-20 合約的 balanceOf
函數。
首先,我們需要 USDC 代幣合約的 ABI(應用二進制接口)和地址:
// USDC 在以太坊主網的合約地址
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // 以太坊主網
// 最小化的 ERC-20 ABI,只包含我們需要的函數
const ERC20_ABI = [
{
constant: true,
inputs: [{ name: "_owner", type: "address" }],
name: "balanceOf",
outputs: [{ name: "balance", type: "uint256" }],
type: "function",
},
{
constant: true,
inputs: [],
name: "decimals",
outputs: [{ name: "", type: "uint8" }],
type: "function",
},
];
接下來,我們編寫獲取 USDC 餘額的函數:
import { formatUnits } from "viem";
async function getUsdcBalance(address: string) {
try {
// 獲取代幣小數位數
const decimals = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "decimals",
});
// 獲取餘額
const balance = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address],
});
// 格式化餘額為可讀形式(USDC 通常有 6 個小數位)
const formattedBalance = formatUnits(balance, decimals);
console.log(`地址 ${address} 的 USDC 餘額:${formattedBalance} USDC`);
return { balance, formattedBalance };
} catch (error) {
console.error("獲取 USDC 餘額時出錯:", error);
throw error;
}
}
在這個函數中,我們首先查詢 USDC 代幣的小數位數,然後調用 balanceOf
方法查詢指定地址的餘額。最後,我們使用 formatUnits
工具將原始餘額轉換為更易讀的格式。
整合:查詢 vitalik.eth 的 USDC 餘額
現在,我們可以結合上述函數,創建一個完整的示例來獲取 vitalik.eth 的 USDC 餘額:
import { createPublicClient, http, formatUnits, normalize } from "viem";
import { mainnet } from "viem/chains";
// 創建公共客戶端
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"),
});
// USDC 在以太坊主網的合約地址
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
// 最小化的 ERC-20 ABI
const ERC20_ABI = [
{
constant: true,
inputs: [{ name: "_owner", type: "address" }],
name: "balanceOf",
outputs: [{ name: "balance", type: "uint256" }],
type: "function",
},
{
constant: true,
inputs: [],
name: "decimals",
outputs: [{ name: "", type: "uint8" }],
type: "function",
},
];
// 主函數
async function checkVitalikUsdcBalance() {
try {
// 1. 解析 vitalik.eth 為以太坊地址
console.log("正在解析 vitalik.eth...");
const vitalikAddress = await publicClient.getEnsAddress({
name: normalize("vitalik.eth"),
});
if (!vitalikAddress) {
throw new Error("無法解析 vitalik.eth 的地址");
}
console.log(`vitalik.eth 解析為地址:${vitalikAddress}`);
// 2. 獲取 USDC 小數位數
const decimals = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "decimals",
});
// 3. 獲取 USDC 餘額
const balance = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [vitalikAddress],
});
// 4. 格式化餘額
const formattedBalance = formatUnits(balance, decimals);
console.log(
`vitalik.eth (${vitalikAddress}) 的 USDC 餘額:${formattedBalance} USDC`
);
return {
ensName: "vitalik.eth",
address: vitalikAddress,
rawBalance: balance,
formattedBalance: formattedBalance,
};
} catch (error) {
console.error("查詢過程中出錯:", error);
throw error;
}
}
// 執行
checkVitalikUsdcBalance()
.then((result) => console.log("查詢完成:", result))
.catch((error) => console.error("查詢失敗:", error));
這個完整的例子展示了如何解析 ENS 名稱並查詢相應地址的 USDC 餘額。值得注意的是,2024 年 1 月份有報導指出 Vitalik Buterin 曾轉移了 3,300 USDC 的資金,我們的查詢會反映出當前的最新餘額。
進階應用:在特定區塊查詢餘額
有時候,我們可能需要查詢特定區塊高度或特定時間點的代幣餘額。viem 提供了相關參數來支持這種需求:
async function getUsdcBalanceAtBlock(
address: string,
blockNumberOrTag:
| bigint
| "latest"
| "earliest"
| "pending"
| "safe"
| "finalized"
) {
try {
const decimals = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "decimals",
});
// 指定區塊號或區塊標籤進行查詢
const balance = await publicClient.readContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address],
blockNumber:
typeof blockNumberOrTag === "bigint" ? blockNumberOrTag : undefined,
blockTag:
typeof blockNumberOrTag !== "bigint" ? blockNumberOrTag : undefined,
});
const formattedBalance = formatUnits(balance, decimals);
console.log(
`地址 ${address} 在區塊 ${blockNumberOrTag} 的 USDC 餘額:${formattedBalance} USDC`
);
return { balance, formattedBalance };
} catch (error) {
console.error("獲取特定區塊 USDC 餘額時出錯:", error);
throw error;
}
}
使用這個函數,我們可以查詢指定地址在特定區塊高度或特定區塊標籤(如 ‘latest’, ‘safe’ 等)時的 USDC 餘額。
查詢多個代幣餘額
在實際應用中,我們可能需要查詢用戶持有的多種代幣餘額。我們可以擴展上述方法來實現這一功能:
// 定義代幣接口
interface Token {
symbol: string;
address: string;
decimals?: number;
}
// 常用代幣列表
const tokens: Token[] = [
{ symbol: "USDC", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, // 以太坊主網 USDC
{ symbol: "USDT", address: "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, // Tether
{ symbol: "DAI", address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" }, // DAI
];
async function getMultipleTokenBalances(address: string) {
const results: Record = {};
for (const token of tokens) {
try {
// 獲取小數位數(如果未提供)
let decimals = token.decimals;
if (decimals === undefined) {
decimals = await publicClient.readContract({
address: token.address,
abi: ERC20_ABI,
functionName: "decimals",
});
}
// 獲取餘額
const balance = await publicClient.readContract({
address: token.address,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address],
});
const formattedBalance = formatUnits(balance, decimals);
results[token.symbol] = {
rawBalance: balance,
formattedBalance: formattedBalance,
};
console.log(`${token.symbol} 餘額:${formattedBalance}`);
} catch (error) {
console.error(`獲取 ${token.symbol} 餘額時出錯:`, error);
results[token.symbol] = {
rawBalance: 0n,
formattedBalance: "錯誤",
};
}
}
return results;
}
這個函數允許我們輕鬆查詢用戶持有的多種代幣餘額。正如 Reddit 討論中所指出的,為了查詢用戶持有的所有代幣餘額,我們需要預先知道要查詢的代幣合約地址。
使用多鏈支持查詢 USDC 餘額
USDC 存在於多個區塊鏈上,如果我們需要查詢用戶在不同鏈上的 USDC 餘額,可以使用如下方法:
import { createPublicClient, http, formatUnits } from "viem";
import { mainnet, optimism, arbitrum, base } from "viem/chains";
// USDC 在不同鏈上的合約地址
const USDC_DEPLOYMENTS = [
{ chain: mainnet, address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
{ chain: optimism, address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" },
{ chain: arbitrum, address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" },
{ chain: base, address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
];
async function getMultichainUsdcBalance(address: string) {
const results: Record = {};
for (const deployment of USDC_DEPLOYMENTS) {
const client = createPublicClient({
chain: deployment.chain,
transport: http(),
});
try {
// 獲取 USDC 小數位數
const decimals = await client.readContract({
address: deployment.address,
abi: ERC20_ABI,
functionName: "decimals",
});
// 獲取餘額
const balance = await client.readContract({
address: deployment.address,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address],
});
const formattedBalance = formatUnits(balance, decimals);
results[deployment.chain.name] = {
chainName: deployment.chain.name,
rawBalance: balance,
formattedBalance: formattedBalance,
};
console.log(
`${deployment.chain.name} 鏈上的 USDC 餘額:${formattedBalance}`
);
} catch (error) {
console.error(
`獲取 ${deployment.chain.name} 上的 USDC 餘額時出錯:`,
error
);
results[deployment.chain.name] = {
chainName: deployment.chain.name,
rawBalance: 0n,
formattedBalance: "錯誤",
};
}
}
return results;
}
這個函數可以幫助我們查詢用戶在多個區塊鏈上的 USDC 餘額,為跨鏈應用提供支持。
結論
本文詳細介紹了如何使用 viem 庫獲取 USDC 代幣餘額,特別以解析和查詢 vitalik.eth 的 USDC 餘額為例。viem 作為一個現代化的以太坊庫,提供了強大而直觀的 API,使得查詢代幣餘額等操作變得簡單高效。
我們介紹了基本的查詢方法,以及進階應用,如在特定區塊查詢餘額、查詢多種代幣餘額和支持多鏈查詢等功能。這些方法和技術可以幫助開發者在 Web3 應用中實現更豐富的功能。
在實際應用中,開發者可能還需要考慮錯誤處理、效能優化和用戶體驗等方面的問題。例如,可以使用批量查詢(multicall)來減少 API 調用次數,或者實現餘額變化的監控功能。
希望本文能幫助您理解如何使用 viem 查詢 USDC 代幣餘額,並在您的 Web3 應用開發中發揮作用。