Viem完全指南(个人总结记录)Viem简介Viem是一个类型安全、轻量级的以太坊TypeScript接口库,提供比ethers.js和web3.js更好的开发者体验。核心特性类型安全:完整的TypeScript支持轻量级:比传统库小70%以上模块
<!--StartFragment-->
Viem 是一个类型安全、轻量级的以太坊 TypeScript 接口库,提供比 ethers.js 和 web3.js 更好的开发者体验。
# 使用 npm
npm install viem
# 使用 yarn
yarn add viem
# 使用 pnpm
pnpm add viem
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
}
}
import { createPublicClient, http } from 'viem'
import { mainnet, sepolia, polygon } from 'viem/chains'
// 主网客户端
const mainnetClient = createPublicClient({
chain: mainnet,
transport: http()
})
// Sepolia 测试网
const sepoliaClient = createPublicClient({
chain: sepolia,
transport: http('https://eth-sepolia.g.alchemy.com/v2/your-api-key')
})
// 多 RPC 端点(故障转移)
const robustClient = createPublicClient({
chain: mainnet,
transport: http([
'https://eth-mainnet.g.alchemy.com/v2/your-api-key',
'https://eth-mainnet.public.blastapi.io',
'https://cloudflare-eth.com'
], {
retryCount: 3,
retryDelay: 1000
})
})
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
// 使用私钥创建钱包客户端
const privateKey = '0x...' // 你的私钥
const account = privateKeyToAccount(privateKey)
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http()
})
// 浏览器钱包(MetaMask 等)
const browserWalletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum
})
import { createTestClient, http } from 'viem'
import { foundry } from 'viem/chains'
// 用于本地测试(Anvil、Hardhat)
const testClient = createTestClient({
chain: foundry,
mode: 'anvil',
transport: http('http://localhost:8545')
})
// 常用测试操作
await testClient.setNextBlockTimestamp({ timestamp: 1678883200 })
await testClient.mine({ blocks: 1 })
await testClient.impersonateAccount({ address: '0x...' })
import { parseEther, formatEther } from 'viem'
// 获取余额
const balance = await publicClient.getBalance({
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
})
console.log('余额:', formatEther(balance), 'ETH')
// 获取区块信息
const block = await publicClient.getBlock()
console.log('最新区块:', block.number, block.hash)
// 获取交易信息
const transaction = await publicClient.getTransaction({
hash: '0x...'
})
// 获取交易收据
const receipt = await publicClient.getTransactionReceipt({
hash: '0x...'
})
// 批量获取多个地址的余额
const addresses = [
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
'0x742d35Cc6634C0532925a3b8D5C9eA6A2A3b8D5C'
]
const balances = await Promise.all(
addresses.map(address => publicClient.getBalance({ address }))
)
// 使用 multicall 批量调用(更高效)
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http()
})
const erc20Abi = parseAbi([
'function balanceOf(address) view returns (uint256)',
'function totalSupply() view returns (uint256)'
])
const results = await client.multicall({
contracts: [
{
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
abi: erc20Abi,
functionName: 'balanceOf',
args: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045']
},
{
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
abi: erc20Abi,
functionName: 'totalSupply'
}
]
})
// 查询历史事件
const logs = await publicClient.getLogs({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', // UNI
event: parseAbiItem('event Transfer(address indexed, address indexed, uint256)'),
fromBlock: 18000000n,
toBlock: 18001000n
})
// 过滤特定事件
const filteredLogs = await publicClient.getLogs({
event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
args: {
from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
}
})
import { parseEther } from 'viem'
// 发送 ETH
const hash = await walletClient.sendTransaction({
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('0.1')
})
console.log('交易哈希:', hash)
// 等待交易确认
const receipt = await publicClient.waitForTransactionReceipt({
hash
})
if (receipt.status === 'success') {
console.log('交易成功!')
} else {
console.log('交易失败!')
}
import { parseAbi } from 'viem'
const erc20Abi = parseAbi([
'function transfer(address to, uint256 amount) returns (bool)',
'function approve(address spender, uint256 amount) returns (bool)'
])
// ERC20 转账
const hash = await walletClient.writeContract({
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
abi: erc20Abi,
functionName: 'transfer',
args: [
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
parseEther('100')
]
})
// 自定义 gas 参数
const hash = await walletClient.sendTransaction({
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('0.1'),
gas: 21000n,
maxFeePerGas: parseGwei('30'),
maxPriorityFeePerGas: parseGwei('1.5')
})
// 使用自定义 nonce
const nonce = await publicClient.getTransactionCount({
address: walletClient.account.address
})
const hash = await walletClient.sendTransaction({
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('0.1'),
nonce: nonce
})
import { parseAbi } from 'viem'
const uniswapV3PoolAbi = parseAbi([
'function token0() view returns (address)',
'function token1() view returns (address)',
'function fee() view returns (uint24)',
'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)'
])
// 读取合约状态
const token0 = await publicClient.readContract({
address: '0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640', // USDC/WETH Pool
abi: uniswapV3PoolAbi,
functionName: 'token0'
})
const slot0 = await publicClient.readContract({
address: '0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640',
abi: uniswapV3PoolAbi,
functionName: 'slot0'
})
console.log('当前价格:', slot0.sqrtPriceX96)
// 复杂的合约交互
const uniswapRouterAbi = parseAbi([
'function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)'
])
const hash = await walletClient.writeContract({
address: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Uniswap V3 Router
abi: uniswapRouterAbi,
functionName: 'exactInputSingle',
args: [{
tokenIn: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
fee: 500,
recipient: walletClient.account.address,
deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 20), // 20分钟后过期
amountIn: parseUnits('1000', 6), // 1000 USDC (6 decimals)
amountOutMinimum: parseEther('0.5'), // 最少接收 0.5 ETH
sqrtPriceLimitX96: 0n
}],
value: 0n // 不是支付 ETH 的交易
})
// 实时监听合约事件
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', // UNI
abi: parseAbi(['event Transfer(address indexed, address indexed, uint256)']),
eventName: 'Transfer',
onLogs: (logs) => {
logs.forEach((log) => {
console.log('转账事件:', {
发送方: log.args[0],
接收方: log.args[1],
金额: log.args[2],
交易哈希: log.transactionHash
})
})
}
})
// 停止监听
// unwatch()
import { hashMessage, verifyMessage } from 'viem'
// 签名文本消息
const signature = await walletClient.signMessage({
message: 'Hello from Viem!'
})
// 验证签名
const isValid = await verifyMessage({
address: walletClient.account.address,
message: 'Hello from Viem!',
signature
})
// 签名结构化数据(EIP-712)
const domain = {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
} as const
const types = {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' }
]
} as const
const message = {
from: {
name: 'Cow',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
},
contents: 'Hello, Bob!'
} as const
const signature = await walletClient.signTypedData({
domain,
types,
primaryType: 'Mail',
message
})
// 签名未发送的交易
const signedTransaction = await walletClient.signTransaction({
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: parseEther('0.1')
})
// 以后可以发送已签名的交易
const hash = await publicClient.sendRawTransaction({
serializedTransaction: signedTransaction
})
import { createPublicClient, http, createWalletClient } from 'viem'
import {
mainnet,
sepolia,
polygon,
arbitrum,
optimism,
bsc
} from 'viem/chains'
// 多链客户端工厂
function createChainClient(chain) {
return createPublicClient({
chain,
transport: http()
})
}
const clients = {
eth: createChainClient(mainnet),
sep: createChainClient(sepolia),
poly: createChainClient(polygon),
arb: createChainClient(arbitrum),
opt: createChainClient(optimism),
bsc: createChainClient(bsc)
}
// 跨链余额查询
async function getMultichainBalances(address) {
const results = await Promise.allSettled(
Object.entries(clients).map(async ([chainName, client]) => {
const balance = await client.getBalance({ address })
return { chain: chainName, balance }
})
)
return results
.filter(result => result.status === 'fulfilled')
.map(result => result.value)
}
import { switchChain } from 'viem/actions'
// 在钱包中切换链
await switchChain(walletClient, { chain: polygon })
// 检查当前链
const currentChain = await walletClient.getChainId()
// 添加自定义链
await walletClient.addChain({
chain: {
id: 12345,
name: 'Custom Chain',
network: 'custom',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: { http: ['https://custom.chain.rpc'] },
public: { http: ['https://custom.chain.rpc'] },
},
}
})
// 使用 try-catch 包装操作
async function safeContractCall(contractCall) {
try {
const result = await contractCall()
return { success: true, data: result }
} catch (error) {
console.error('合约调用失败:', error)
// 分类处理不同错误
if (error.message.includes('insufficient funds')) {
return { success: false, error: '余额不足' }
} else if (error.message.includes('user rejected')) {
return { success: false, error: '用户拒绝交易' }
} else if (error.message.includes('execution reverted')) {
return { success: false, error: '合约执行失败' }
} else {
return { success: false, error: '未知错误' }
}
}
}
// 使用示例
const result = await safeContractCall(() =>
publicClient.readContract({
address: '0x...',
abi: erc20Abi,
functionName: 'balanceOf',
args: ['0x...']
})
)
async function withRetry(operation, maxRetries = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation()
} catch (error) {
if (attempt === maxRetries) throw error
console.log(`尝试 ${attempt} 失败,${delay}ms 后重试...`)
await new Promise(resolve => setTimeout(resolve, delay * attempt))
}
}
}
// 使用重试机制
const balance = await withRetry(() =>
publicClient.getBalance({
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
})
)
// 使用 multicall 批量读取
async function getMultipleBalances(tokenAddresses, userAddress) {
const contracts = tokenAddresses.map(address => ({
address,
abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
functionName: 'balanceOf',
args: [userAddress]
}))
const results = await publicClient.multicall({ contracts })
return results.map((result, index) => ({
token: tokenAddresses[index],
balance: result.result
}))
}
// 简单的内存缓存
class CacheManager {
private cache = new Map()
private ttl = 60000 // 1分钟缓存
async getOrSet(key, operation) {
const cached = this.cache.get(key)
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data
}
const data = await operation()
this.cache.set(key, { data, timestamp: Date.now() })
return data
}
}
const cache = new CacheManager()
// 使用缓存的余额查询
const balance = await cache.getOrSet(
`balance-${address}`,
() => publicClient.getBalance({ address })
)
// 使用 WebSocket 实时连接
import { webSocket } from 'viem'
const wsClient = createPublicClient({
chain: mainnet,
transport: webSocket('wss://eth-mainnet.g.alchemy.com/v2/your-api-key')
})
// 请求去重
class RequestDeduplicator {
private pending = new Map()
async dedupe(key, operation) {
if (this.pending.has(key)) {
return this.pending.get(key)
}
const promise = operation().finally(() => {
this.pending.delete(key)
})
this.pending.set(key, promise)
return promise
}
}
class TokenSwapMonitor {
private unwatchers: (() => void)[] = []
constructor(private publicClient, private dexRouterAddress) {}
startMonitoring(pairs) {
const abi = parseAbi([
'event Swap(address indexed sender, address indexed to, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out)'
])
pairs.forEach(pairAddress => {
const unwatch = this.publicClient.watchContractEvent({
address: pairAddress,
abi,
eventName: 'Swap',
onLogs: this.handleSwap.bind(this)
})
this.unwatchers.push(unwatch)
})
}
private handleSwap(logs) {
logs.forEach(log => {
const { sender, to, amount0In, amount1In, amount0Out, amount1Out } = log.args
console.log('检测到交换:', {
交易者: sender,
接收者: to,
输入金额: { amount0In, amount1In },
输出金额: { amount0Out, amount1Out },
交易哈希: log.transactionHash
})
})
}
stop() {
this.unwatchers.forEach(unwatch => unwatch())
}
}
class MultiChainDashboard {
constructor(private chains) {}
async getPortfolio(address) {
const portfolio = await Promise.all(
this.chains.map(async (chain) => {
const client = createPublicClient({
chain: chain,
transport: http()
})
const nativeBalance = await client.getBalance({ address })
// 这里可以添加 ERC20 代币余额查询
return {
chain: chain.name,
nativeBalance,
tokens: [] // 代币列表
}
})
)
return portfolio
}
}
class GasOptimizer {
constructor(private publicClient) {}
async getOptimalGasPrice() {
const [block, feeHistory] = await Promise.all([
this.publicClient.getBlock(),
this.publicClient.getFeeHistory({
blockCount: 10,
rewardPercentiles: [25, 50, 75]
})
])
const baseFee = block.baseFeePerGas || 0n
const priorityFees = feeHistory.reward.flat()
const averagePriorityFee = priorityFees.reduce((a, b) => a + b, 0n) / BigInt(priorityFees.length)
return {
maxFeePerGas: baseFee * 2n + averagePriorityFee,
maxPriorityFeePerGas: averagePriorityFee
}
}
}
// ethers.js 代码
import { ethers } from 'ethers'
const provider = new ethers.providers.JsonRpcProvider('https://...')
const balance = await provider.getBalance(address)
const tx = await signer.sendTransaction({ to, value })
// 对应的 Viem 代码
import { createPublicClient, http, createWalletClient } from 'viem'
const publicClient = createPublicClient({ transport: http('https://...') })
const balance = await publicClient.getBalance({ address })
const hash = await walletClient.sendTransaction({ to, value })
// web3.js 代码
import Web3 from 'web3'
const web3 = new Web3('https://...')
const balance = await web3.eth.getBalance(address)
const tx = await web3.eth.sendTransaction({ to, value })
// 对应的 Viem 代码
import { createPublicClient, http, createWalletClient } from 'viem'
const publicClient = createPublicClient({ transport: http('https://...') })
const balance = await publicClient.getBalance({ address })
const hash = await walletClient.sendTransaction({ to, value })
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!