使用 viem 獲取使用者 USDC 代幣餘額的技術指南:以 vitalik.eth 為例

使用 viem 獲取使用者 USDC 代幣餘額的技術指南:以 vitalik.eth 為例
作者: Calpa Liu
字數:2091
出版:April 16, 2025

在 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 應用開發中發揮作用。

感謝您閱讀我的文章。歡迎隨時分享你的想法。
關於 Calpa

Calpa 擅長使用 TypeScriptReact.jsVue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。