viem TypeScript以太坊库¶
概览¶
viem是一个模块化的TypeScript库,它提供JSON-RPC API的抽象化封装让开发者能够与之交互,从而轻松与以太坊节点交互。由于Moonbeam的类以太坊API完全兼容以太坊格式的JSON-RPC调用,因此开发者可以利用此兼容性与Moonbeam节点交互。关于viem的更多信息,请参考其官方文档网站。
在本教程中,您将学习如何使用viem发送交易并部署合约至Moonbase Alpha测试网。本教程也同样适用于Moonbeam、Moonriver或Moonbeam开发节点。
查看先决条件¶
开始操作之前,您将需要准备以下内容:
- An account with funds.一个拥有资金的账户。 您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试
- 要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥
注意事项
本教程示例基于Ubuntu 22.04和MacOS的环境,用户需根据其所使用的Windows版本进行微调。
安装viem¶
您需要创建一个基础的TypeScript项目。首先,创建一个目录,用于存储所有的本教程中要创建的文件。然后使用以下命令初始化项目:
mkdir viem-examples && cd viem-examples && npm init --y
在本教程中,您需要安装viem库和Solidity编译器。您可以通过运行以下命令安装这两个包:
npm install typescript ts-node viem solc@0.8.0
yarn add typescript ts-node viem solc@0.8.0
您可以通过运行以下命令创建TypeScript配置:
npx tsc --init
注意事项
截至本教程撰写时所使用的Node.js为18.18.0版本。
设置viem Client(提供者)¶
在本教程中,您将创建一些提供不同功能的脚本,例如发送交易、部署合约和与已部署的合约交互。在大部分脚本中,您需要创建一个viem client用于与网络交互。
您可以使用createPublicClient
函数创建一个用于读取链数据的viem client,例如余额或合约数据;您也可以使用createWalletClient
函数创建一个用于写入链数据的viem client,例如发送交易。
用于读取链数据¶
要创建一个用于读取链数据的客户端,请执行以下步骤:
- 从
viem
导入createPublicClient
和http
函数,并从viem/chains
导入想要交互的网络。链可以为moonbeam
、moonriver
或moonbaseAlpha
- 使用
createPublicClient
函数创建client
,并配置network和HTTP RPC端点
import { createPublicClient, http } from 'viem';
import { moonbeam } from 'viem/chains';
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const publicClient = createPublicClient({
chain: moonbeam,
transport: http(rpcUrl),
});
import { createPublicClient, http } from 'viem';
import { moonriver } from 'viem/chains';
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const publicClient = createPublicClient({
chain: moonriver,
transport: http(rpcUrl),
});
import { createPublicClient, http } from 'viem';
import { moonbaseAlpha } from 'viem/chains';
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network'
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
import { createPublicClient, http } from 'viem';
import { moonbeamDev } from 'viem/chains';
const rpcUrl = 'http://127.0.0.1:9944'
const publicClient = createPublicClient({
chain: moonbeamDev,
transport: http(rpcUrl);
})
用于写入链数据¶
要创建一个用于读取链数据的客户端,请执行以下步骤:
- 从
viem
导入createWalletClient
和http
函数,通过其私钥导入加载账户的privateKeyToAccount
函数,以及从viem/chains
导入想要交互的网络。链可以为moonbeam
、moonriver
或moonbaseAlpha
- 使用
privateKeyToAccount
函数创建账户 - 使用
createWalletClient
函数创建client
,并传入账户和HTTP RPC端点
请记住
本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中。
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbeam } from 'viem/chains';
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const walletClient = createWalletClient({
account,
chain: moonbeam,
transport: http(rpcUrl),
});
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonriver } from 'viem/chains';
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'INSERT_RPC_API_ENDPOINT'
const walletClient = createWalletClient({
account,
chain: moonriver,
transport: http(rpcUrl),
});
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha } from 'viem/chains';
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network'
const walletClient = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbeamDev } from 'viem/chains';
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'http://127.0.0.1:9944'
const walletClient = createWalletClient({
account,
chain: moonbeamDev,
transport: http(rpcUrl),
});
注意事项
要与基于浏览器的钱包交互,您可以使用以下代码创建钱包:
const [account] = await window.ethereum.request({
method: 'eth_requestAccounts',
});
const walletClient = createWalletClient({
account,
chain: moonbeam,
transport: custom(window.ethereum),
});
发送交易¶
在这一部分,您将创建一些脚本。第一个脚本是在尝试发送交易之前查看账户余额,第二个脚本是实际发送交易。
您也需要使用余额脚本查看交易发送后的账户余额。
查看余额脚本¶
您只需要一个文件即可在交易发送前后检查两个地址的余额。首先,您需要通过运行以下命令创建一个balances.ts
文件。
touch balances.ts
接下来,您可以为此文件创建脚本,并执行以下步骤:
- 更新您的导入以包含
viem
中的createPublicClient
、http
和formatEther
函数,以及您想要从viem/chains
交互的网络 - 设置viem client,此客户端可用于读取链数据,例如账户余额
- 定义
addressFrom
和addressTo
变量 - 创建
balances
异步函数,该函数包装了publicClient.getBalance
函数 - 使用
publicClient.getBalance
函数获取addressFrom
和addressTo
地址的余额。您也可以使用formatEther
函数将余额转换成特定单位的数值,例如GLMR、MOVR或DEV - 最后,运行
balances
函数
// 1. Imports
import { createPublicClient, http, formatEther } from 'viem';
import { moonbaseAlpha } from 'viem/chains';
// 2. Create a public client for reading chain data
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 4. Create balances function
const balances = async () => {
// 5. Fetch balances
const balanceFrom = formatEther(
await publicClient.getBalance({ address: addressFrom })
);
const balanceTo = formatEther(
await publicClient.getBalance({ address: addressTo })
);
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 6. Call the balances function
balances();
要运行脚本和获取账户余额,您可以运行以下命令:
npx ts-node balances.ts
如果成功,发送地址和接收地址的余额将以DEV为单位显示在终端。
发送交易脚本¶
您只需要一个文件即可在两个账户之间执行交易。在本示例中,您将从源地址(即拥有私钥的账户)转移一个DEV到另一个地址。首先,您需要运行以下命令创建一个transaction.ts
文件。
touch transaction.ts
接下来,您可以为此文件创建脚本,并执行以下步骤:
- 更新您的导入以包含
viem
中的createWalletClient
、http
和parseEther
函数,viem/accounts
中的privateKeyToAccount
函数,以及您想要从viem/chains
交互的网络 - 设置viem wallet client用于写入链数据,该客户端配上私钥一起可用于发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
- 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
- 定义
addressTo
变量 - 创建了
send
异步函数,该函数包装了交易对象和walletClient.sendTransaction
函数 walletClient.sendTransaction
函数能用于签署和发送交易。该函数只需要传入一个交易对象,交易对象仅需包含接收者地址和发送的金额。请注意您可以使用parseEther
函数来处理Ether和Wei之间的单位转换,该转换是必要的且类似于parseUnits(value, decimals)
。使用await
等待交易处理完毕并返回交易哈希值- 使用
publicClient.waitForTransactionReceipt
函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行balances.ts
脚本来检查余额是否已按预期更新,此函数特别有用 - 最后,运行
send
函数
// 1. Imports
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha } from 'viem/chains';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const walletClient = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 4. Create to address variable
const addressTo = 'INSERT_ADDRESS';
// 5. Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${account.address} to ${addressTo}`
);
// 6. Sign and send tx
const hash = await walletClient.sendTransaction({
to: addressTo,
value: parseEther('1'),
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Transaction successful with hash: ${hash}`);
};
// 8. Call the send function
send();
要运行脚本,您可以在终端运行以下命令:
npx ts-node transaction.ts
如果交易成功,您将在终端看到交易哈希。
您也可以使用balances.ts
脚本查看发送和接收账户更改的余额。整个工作流程如下:
npx ts-node transaction.ts Attempting to send transaction from 0x3B939FeaD1557C741Ff06492FD0127bd287A421e to 0x78F34038c82638E0563b974246D421154C26b004 Transaction successful with hash: 0xc482d907b2ae4ca1202c6cc5b486694b8439a9853caad9c2cdafec39defa1968 npx ts-node balances.ts The balance of 0x3B939FeaD1557C741Ff06492FD0127bd287A421e is: 3600.72 DEV The balance of 0x78F34038c82638E0563b974246D421154C26b004 is: 1 DEV
部署合约¶
编译合约脚本¶
// 1. Import packages
const fs = require('fs');
const solc = require('solc');
// 2. Get path and load contract
const source = fs.readFileSync('Incrementer.sol', 'utf8');
// 3. Create input object
const input = {
language: 'Solidity',
sources: {
'Incrementer.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};
// 4. Compile the contract
const tempFile = JSON.parse(solc.compile(JSON.stringify(input)));
const contractFile = tempFile.contracts['Incrementer.sol']['Incrementer'];
// 5. Export contract data
export default contractFile;
部署合约脚本¶
在使用脚本编译Incrementer.sol
合约后,您可以将结果用签署的交易部署至链上。首先,您可以为部署脚本创建一个名为deploy.ts
的文件:
touch deploy.ts
接下来,您可以为此文件创建脚本,并执行以下步骤:
- 更新您的导入以包含
viem
中的createPublicClient
、createWalletClient
和http
函数、viem/accounts
中的privateKeyToAccount
函数、您想要从viem/chains
交互的网络,以及从您在编译合约脚本部分创建的compile.ts
文件中的contractFile
- 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起部署
Incrementer
合约。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中 - 设置公共viem client,用于读取链数据,该客户端将用于读取部署的交易回执
- 为编译的合约加载合约
bytecode
和abi
- 创建
deploy
异步函数,该函数使用walletClient.deployContract
函数部署合约 - 使用
walletClient.deployContract
函数签署和发送交易。您需要传入合约的ABI和字节码,部署交易的账户,以及incrementer的初始值。使用await
等待交易处理完毕并返回交易哈希值 - 使用
publicClient.readContract
函数获取部署的交易回执。使用await
等待交易处理完毕并返回交易地址 - 最后,运行
deploy
函数
// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
// The private key must be prepended with `0x` to avoid errors
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const walletClient = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 4. Load contract information
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
const _initialNumber = 5;
// 5. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${account.address}`);
// 6. Send tx (initial value set to 5)
const contract = await walletClient.deployContract({
abi,
account,
bytecode,
args: [_initialNumber],
});
// 7. Get the transaction receipt for the deployment
const transaction = await publicClient.waitForTransactionReceipt({
hash: contract,
});
console.log(`Contract deployed at address: ${transaction.contractAddress}`);
};
// 8. Call the deploy function
deploy();
要运行脚本,您可以在终端运行以下命令:
npx ts-node deploy.ts
如果成功,您将在终端看到合约地址。
读取合约数据(调用函数)¶
Call函数是一种不会修改合约存储(更改变量)的交互类型,这意味着无需发送任何交易。他们只是读取已部署合约的各种存储变量。
首先,您需要创建一个文件,将其命名为get.ts
:
touch get.ts
然后,您可以执行以下步骤创建脚本:
- 更新您的导入以包含
viem
中的createPublicClient
和http
函数,viem/chains
中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts
文件中的contractFile
- 设置公共viem client,用于读取链数据,该客户端可用于读取
Incrementer
合约的当前数量。 - 使用已部署合约的地址创建
contractAddress
变量,以及使用compile.ts
文件中的contractFile
创建abi
变量 - 创建
get
异步函数 - 使用
publicClient.readContract
函数调用合约,传入abi
、函数名称、contractAddress
和任何参数(若需要)。您可以使用await
,当请求承诺解决,将返回请求的值 - 最后,调用
get
函数
// 1. Update imports
import { createPublicClient, http } from 'viem';
import { moonbaseAlpha } from 'viem/chains';
import contractFile from './compile';
// 2. Create a public client for reading chain data
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const client = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
// 4. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 5. Call contract
const data = await client.readContract({
abi,
functionName: 'number',
address: contractAddress,
args: [],
});
console.log(`The current number stored is: ${data}`);
};
// 6. Call get function
get();
要运行脚本,您可以在终端运行以下命令:
npx ts-node get.ts
如果成功,您将在终端看到值。
与合约交互(发送函数)¶
Send函数是一种修改合约存储(更改变量)的交互类型,这意味着需要签署并发送交易。在这一部分中,您将创建两个脚本:一个用于增量,一个用于重置增量器。 首先,您可以为每个脚本创建一个文件,并将其分别命名为increment.ts
和reset.ts
:
touch increment.ts reset.ts
打开increment.ts
文件,并执行以下步骤创建脚本:
- 更新您的导入以包含
viem
中的createWalletClient
和http
函数,viem/chains
中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts
文件中的contractFile
- 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
- 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
- 使用已部署合约的地址创建
contractAddress
变量,使用compile.ts
文件中的contractFile
创建abi
变量,以及创建合约递增的_value
- 创建
increment
异步函数 - 使用
walletClient.writeContract
函数调用合约,传入abi
、函数名称、contractAddress
和_value
。您可以使用await
,当请求承诺解决,将返回交易哈希 - 使用
publicClient.waitForTransactionReceipt
函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行get.ts
脚本来检查当前数值是否已按预期更新,此函数特别有用 - 最后,调用
increment
函数
// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const walletClient = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 4. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
const _value = 3;
// 5. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 6. Call contract
const hash = await walletClient.writeContract({
abi,
functionName: 'increment',
address: contractAddress,
args: [_value],
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Tx successful with hash: ${hash}`);
};
// 8. Call increment function
increment();
要运行脚本,您可以在终端运行以下命令:
npx ts-node increment.ts
如果成功,您将在终端看到交易哈希。 您可以一同使用get.ts
脚本和increment.ts
脚本,以确保该值按预期更改。
接下来,您可以打开reset.ts
文件,并执行以下步骤创建脚本:
- 更新您的导入以包含
viem
中的createWalletClient
和http
函数,viem/chains
中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts
文件中的contractFile
- 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
- 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
- 使用已部署合约的地址创建
contractAddress
变量,并使用compile.ts
文件中的contractFile
创建abi
变量,以实现合约增量 - 创建
reset
异步函数 - 使用
walletClient.writeContract
函数调用合约,传入abi
、函数名称、contractAddress
和一个参数空白数组。您可以使用await
,当请求承诺解决,将返回交易哈希 - 使用
publicClient.waitForTransactionReceipt
函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行get.ts
脚本来检查当前数值是否已按预期更新,此函数特别有用 - 最后,调用
reset
函数
// 1. Update imports
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha } from 'viem/chains';
import contractFile from './compile';
// 2. Create a wallet client for writing chain data
const account = privateKeyToAccount('INSERT_PRIVATE_KEY');
const rpcUrl = 'https://rpc.api.moonbase.moonbeam.network';
const walletClient = createWalletClient({
account,
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 3. Create a public client for reading chain data
const publicClient = createPublicClient({
chain: moonbaseAlpha,
transport: http(rpcUrl),
});
// 4. Create contract variables
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const abi = contractFile.abi;
// 5. Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// 6. Call contract
const hash = await walletClient.writeContract({
abi,
functionName: 'reset',
address: contractAddress,
args: [],
});
// 7. Wait for the transaction receipt
await publicClient.waitForTransactionReceipt({
hash,
});
console.log(`Tx successful with hash: ${hash}`);
};
// 8. Call reset function
reset();
要运行脚本,您可以在终端运行以下命令:
npx ts-node reset.ts
如果成功,您将在终端看到交易哈希。 您可以一同使用get.ts
脚本和reset.ts
脚本,以确保该值按预期更改。