玩转Web3:用Viem库实现以太坊合约部署与交互想一窥Web3开发的奥秘?以太坊智能合约是通往区块链世界的大门,而Viem库让你轻松迈出第一步!本文通过一个TypeScript脚本,带你从连接本地以太坊测试网到部署合约、实现交互,全程手把手实战。不管你是Web3新手还是想探
想一窥 Web3 开发的奥秘?以太坊智能合约是通往区块链世界的大门,而 Viem 库让你轻松迈出第一步!本文通过一个 TypeScript 脚本,带你从连接本地以太坊测试网到部署合约、实现交互,全程手把手实战。不管你是 Web3 新手还是想探索新工具的开发者,这篇教程都能让你快速上手,玩转区块链开发的乐趣!
本文献上一场 Web3 开发的实战盛宴!通过一个基于 Viem 库的 TypeScript 脚本,我们将带你连接以太坊本地测试网(如 Hardhat),查询账户信息、发送交易、部署智能合约,并与合约互动,甚至实时监控区块变化。结合一个简单的 Storage 合约和详细的运行结果,这篇教程让你轻松掌握 Viem 的核心用法,快速开启 Web3 开发之旅!
import { createPublicClient, createWalletClient, defineChain, http, hexToBigInt, getContract } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { ABI, BYTECODE } from "../abi/storage";
import { ethers } from "ethers";
import config from '../config';
export const localChain = (url: string) => defineChain({
id: 31337,
name: 'Testnet',
network: 'Testnet',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: [url],
},
},
testnet: true,
})
function toViemAddress(address: string): string {
return address.startsWith("0x") ? address : `0x${address}`
}
export function getViemClient(url: string) {
return createPublicClient({
chain: localChain(url),
transport: http(url),
})
}
export function remove0x(privateKey: string): string {
return privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
}
const privateKey = remove0x(config.privateKey);
console.log("privateKey: ", privateKey);
const walletClient = createWalletClient({
chain: localChain(config.localRpcUrl),
transport: http(config.localRpcUrl),
account: privateKeyToAccount(config.privateKey as `0x${string}`),
})
export async function deployContract(): Promise<string> {
const hash = await walletClient.deployContract({
abi: ABI,
bytecode: `0x${BYTECODE}`,
args: []
})
const publicClient = getViemClient(config.localRpcUrl)
const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (!receipt.contractAddress) {
throw new Error('Contract deployment failed: no contract address in receipt')
}
return receipt.contractAddress
}
async function main() {
const client = getViemClient(config.localRpcUrl);
const accountAddress = toViemAddress(privateKeyToAccount(config.privateKey).address) as `0x${string}`
const balance = await client.getBalance({
address: accountAddress,
})
console.log("Account Balance:", ethers.formatEther(balance), "ETH");
const blockNumber = await client.getBlockNumber()
console.log("Block Number:", blockNumber);
const nonce = await client.getTransactionCount({ address: accountAddress })
console.log("Nonce:", nonce);
// const txHash = await walletClient.sendTransaction({ to: config.accountAddress2, value: hexToBigInt('0x10000') })
const txHash = await walletClient.sendTransaction({ to: config.accountAddress2, value: BigInt(10_000_000_000_000_000) })
console.log("Transaction Hash:", txHash);
const txReceipt = await client.waitForTransactionReceipt({ hash: txHash })
console.log("Transaction Receipt:", txReceipt);
const balanceAfter = await client.getBalance({ address: accountAddress })
console.log("Account Balance After:", ethers.formatEther(balanceAfter), "ETH");
const balance2 = await client.getBalance({ address: config.accountAddress2 })
console.log("Account Balance2:", ethers.formatEther(balance2), "ETH");
const contractAddress = await deployContract() as `0x${string}`
console.log("Contract Address:", contractAddress);
const retrieve = await client.readContract({
address: contractAddress,
abi: ABI,
functionName: 'retrieve',
args: [],
}) as bigint
console.log("Retrieved Value:", retrieve.toString());
const deployedContract = getContract({ address: contractAddress, abi: ABI, client: walletClient })
const storeTx = await deployedContract.write.store([10000])
console.log("Store Transaction Hash:", storeTx);
const receipt = await client.waitForTransactionReceipt({ hash: storeTx })
console.log("Store Transaction Receipt:", receipt);
const newRetrieve = await client.readContract({
address: contractAddress,
abi: ABI,
functionName: 'retrieve',
args: [],
}) as bigint
console.log("Retrieved Value:", newRetrieve.toString());
client.watchBlockNumber({
onBlockNumber: (blockNumber) => {
console.log(`block is ${blockNumber}`)
},
onError: (error) => {
console.error(`error is ${error}`)
}
})
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
这段代码是一个使用 viem 库与本地以太坊区块链交互的 TypeScript 脚本,用于部署智能合约、执行交易和与合约交互。以下是代码的逐部分解释:
导入和初始化
import { createPublicClient, createWalletClient, defineChain, http, hexToBigInt, getContract } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { ABI, BYTECODE } from "../abi/storage";
import { ethers } from "ethers";
import config from '../config';
链定义
export const localChain = (url: string) => defineChain({
id: 31337,
name: 'Testnet',
network: 'Testnet',
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: { default: { http: [url] } },
testnet: true,
})
工具函数
function toViemAddress(address: string): string {
return address.startsWith("0x") ? address : `0x${address}`
}
export function getViemClient(url: string) {
return createPublicClient({
chain: localChain(url),
transport: http(url),
})
}
export function remove0x(privateKey: string): string {
return privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
}
钱包客户端设置
const privateKey = remove0x(config.privateKey);
console.log("privateKey: ", privateKey);
const walletClient = createWalletClient({
chain: localChain(config.localRpcUrl),
transport: http(config.localRpcUrl),
account: privateKeyToAccount(config.privateKey as `0x${string}`),
})
合约部署
export async function deployContract(): Promise<string> {
const hash = await walletClient.deployContract({
abi: ABI,
bytecode: `0x${BYTECODE}`,
args: []
})
const publicClient = getViemClient(config.localRpcUrl)
const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (!receipt.contractAddress) {
throw new Error('Contract deployment failed: no contract address in receipt')
}
return receipt.contractAddress
}
主函数
main 函数是脚本的入口,执行一系列区块链操作:
const client = getViemClient(config.localRpcUrl);
const accountAddress = toViemAddress(privateKeyToAccount(config.privateKey).address) as `0x${string}`
const balance = await client.getBalance({ address: accountAddress })
console.log("Account Balance:", ethers.formatEther(balance), "ETH");
const blockNumber = await client.getBlockNumber()
console.log("Block Number:", blockNumber);
const nonce = await client.getTransactionCount({ address: accountAddress })
console.log("Nonce:", nonce);
const txHash = await walletClient.sendTransaction({ to: config.accountAddress2, value: BigInt(10_000_000_000_000_000) })
console.log("Transaction Hash:", txHash);
const txReceipt = await client.waitForTransactionReceipt({ hash: txHash })
console.log("Transaction Receipt:", txReceipt);
const balanceAfter = await client.getBalance({ address: accountAddress })
console.log("Account Balance After:", ethers.formatEther(balanceAfter), "ETH");
const balance2 = await client.getBalance({ address: config.accountAddress2 })
console.log("Account Balance2:", ethers.formatEther(balance2), "ETH");
const contractAddress = await deployContract() as `0x${string}`
console.log("Contract Address:", contractAddress);
const retrieve = await client.readContract({
address: contractAddress,
abi: ABI,
functionName: 'retrieve',
args: [],
}) as bigint
console.log("Retrieved Value:", retrieve.toString());
const deployedContract = getContract({ address: contractAddress, abi: ABI, client: walletClient })
const storeTx = await deployedContract.write.store([10000])
console.log("Store Transaction Hash:", storeTx);
const receipt = await client.waitForTransactionReceipt({ hash: storeTx })
console.log("Store Transaction Receipt:", receipt);
const newRetrieve = await client.readContract({
address: contractAddress,
abi: ABI,
functionName: 'retrieve',
args: [],
}) as bigint
console.log("Retrieved Value:", newRetrieve.toString());
client.watchBlockNumber({
onBlockNumber: (blockNumber) => {
console.log(`block is ${blockNumber}`)
},
onError: (error) => {
console.error(`error is ${error}`)
}
})
错误处理和执行
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
代码功能总结
➜ ts-node src/viem/index.ts
privateKey: ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Account Balance: 10000.0 ETH
Block Number: 0n
Nonce: 0
Transaction Hash: 0xdea4e55c8911c8aea966eca343a80d984d65637ec449d36582040c052534ccb6
Transaction Receipt: {
type: 'eip1559',
status: 'success',
cumulativeGasUsed: 21000n,
logs: [],
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
transactionHash: '0xdea4e55c8911c8aea966eca343a80d984d65637ec449d36582040c052534ccb6',
transactionIndex: 0,
blockHash: '0x47016456f9bafcb20744e43fa3790199fb3e31d3026697e7b22ed0febd11a2bc',
blockNumber: 1n,
gasUsed: 21000n,
effectiveGasPrice: 2000000000n,
blobGasPrice: 1n,
from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
contractAddress: null
}
Account Balance After: 9999.989958 ETH
Account Balance2: 10000.01 ETH
Contract Address: 0xe7f1725e7734ce288f8367e1bb143e90bb3f0512
Retrieved Value: 0
Store Transaction Hash: 0x2ab751740067986e36f401cfafddaf80214574aa7b1ad808051e23a46581d13f
Store Transaction Receipt: {
type: 'eip1559',
status: 'success',
cumulativeGasUsed: 43730n,
logs: [],
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
transactionHash: '0x2ab751740067986e36f401cfafddaf80214574aa7b1ad808051e23a46581d13f',
transactionIndex: 0,
blockHash: '0x6f78d44d1c8262391d4ba1cad0cc274907c58abd4abe124f2cd549321d8fb2fc',
blockNumber: 3n,
gasUsed: 43730n,
effectiveGasPrice: 1766675216n,
blobGasPrice: 1n,
from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
to: '0xe7f1725e7734ce288f8367e1bb143e90bb3f0512',
contractAddress: null
}
Retrieved Value: 10000
block is 3
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256) {
return number;
}
}
通过这场 Web3 冒险,我们用 Viem 库解锁了以太坊开发的完整流程:从搭建本地测试网到部署 Storage 合约,再到交易和状态交互,每一步都清晰可见。Viem 的简洁高效让区块链开发不再遥不可及!无论你是想初探 Web3 还是寻找更顺手的工具,这篇教程都为你点亮了一盏明灯。快动手试试,结合代码和参考资源,继续探索 Web3 的无限可能吧!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!