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

作者: Calpa Liu
字數:2091
出版:2025 年 4 月 16 日
在 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 應用開發中發揮作用。

想跟上前端開發的最新技術與實戰分享嗎?立即訂閱本站,掌握 React、Vue、TypeScript 等趨勢!
關於 Calpa

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

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

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

熱門文章

最新文章