Skip to content

viem TypeScript以太坊库

概览

viem是一个模块化的TypeScript库,它提供JSON-RPC API的抽象化封装让开发者能够与之交互,从而轻松与以太坊节点交互。由于Moonbeam的类以太坊API完全兼容以太坊格式的JSON-RPC调用,因此开发者可以利用此兼容性与Moonbeam节点交互。关于viem的更多信息,请参考其官方文档网站

在本教程中,您将学习如何使用viem发送交易并部署合约至Moonbase Alpha测试网。本教程也同样适用于MoonbeamMoonriverMoonbeam开发节点

查看先决条件

开始操作之前,您将需要准备以下内容:

  • 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,例如发送交易。

用于读取链数据

要创建一个用于读取链数据的客户端,请执行以下步骤:

  1. viem导入createPublicClienthttp函数,并从viem/chains导入想要交互的网络。链可以为moonbeammoonrivermoonbaseAlpha
  2. 使用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);
})

用于写入链数据

要创建一个用于读取链数据的客户端,请执行以下步骤:

  1. viem导入createWalletClienthttp函数,通过其私钥导入加载账户的privateKeyToAccount函数,以及从viem/chains导入想要交互的网络。链可以为moonbeammoonrivermoonbaseAlpha
  2. 使用privateKeyToAccount函数创建账户
  3. 使用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

接下来,您可以为此文件创建脚本,并执行以下步骤:

  1. 更新您的导入以包含viem中的createPublicClienthttpformatEther函数,以及您想要从viem/chains交互的网络
  2. 设置viem client,此客户端可用于读取链数据,例如账户余额
  3. 定义addressFromaddressTo变量
  4. 创建balances异步函数,该函数包装了publicClient.getBalance函数
  5. 使用publicClient.getBalance函数获取addressFromaddressTo地址的余额。您也可以使用formatEther函数将余额转换成特定单位的数值,例如GLMR、MOVR或DEV
  6. 最后,运行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为单位显示在终端。

The result of running the balances script in the terminal

发送交易脚本

您只需要一个文件即可在两个账户之间执行交易。在本示例中,您将从源地址(即拥有私钥的账户)转移一个DEV到另一个地址。首先,您需要运行以下命令创建一个transaction.ts文件。

touch transaction.ts

接下来,您可以为此文件创建脚本,并执行以下步骤:

  1. 更新您的导入以包含viem中的createWalletClienthttpparseEther函数,viem/accounts中的privateKeyToAccount函数,以及您想要从viem/chains交互的网络
  2. 设置viem wallet client用于写入链数据,该客户端配上私钥一起可用于发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
  3. 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
  4. 定义addressTo变量
  5. 创建了send异步函数,该函数包装了交易对象和walletClient.sendTransaction函数
  6. walletClient.sendTransaction函数能用于签署和发送交易。该函数只需要传入一个交易对象,交易对象仅需包含接收者地址和发送的金额。请注意您可以使用parseEther函数来处理Ether和Wei之间的单位转换,该转换是必要的且类似于parseUnits(value, decimals)。使用await等待交易处理完毕并返回交易哈希值
  7. 使用publicClient.waitForTransactionReceipt函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行balances.ts脚本来检查余额是否已按预期更新,此函数特别有用
  8. 最后,运行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脚本查看发送和接收账户更改的余额。整个工作流程如下:

The result of running the transaction and balances scripts in the terminal

部署合约

在下几个部分中您将要编译和部署的合约是一个简单的增量合约,命名为Incrementer.sol。您可以先为合约创建一个文件:

touch Incrementer.sol

接下来,您可以添加Solidity代码至文件:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Incrementer {
    uint256 public number;

    constructor(uint256 _initialNumber) {
        number = _initialNumber;
    }

    function increment(uint256 _value) public {
        number = number + _value;
    }

    function reset() public {
        number = 0;
    }
}

constructor函数将在合约部署时运行,设置存储在链上的数字变量的初始值(默认值为0)。increment函数将提供的_value添加至当前数字,但是需要发送一个交易以修改存储的数据。最后,reset函数将存储的数值重置为零。

注意事项

此合约为简单示例,仅供演示使用,其数值无实际意义。

编译合约脚本

在这一部分,您将创建一个脚本,该脚本使用Solidity编译器为Incrementer.sol合约输出字节码和接口(ABI)。首先,您可以通过运行以下命令创建一个compile.ts文件:

touch compile.ts
接下来,您将为此文件创建脚本,并执行以下步骤:

  1. 导入fssolc安装包
  2. 使用fs.readFileSync函数,您将读取Incrementer.sol的文件内容并保存至source
  3. 通过指定要使用的languagesourcessettings为Solidity编译器构建input对象
  4. 通过input对象,您可以使用solc.compile编译合约
  5. 提取已编译的合约文件并导出以在部署脚本中使用
// 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

接下来,您可以为此文件创建脚本,并执行以下步骤:

  1. 更新您的导入以包含viem中的createPublicClientcreateWalletClienthttp函数、viem/accounts中的privateKeyToAccount函数、您想要从viem/chains交互的网络,以及从您在编译合约脚本部分创建的compile.ts文件中的contractFile
  2. 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起部署Incrementer合约。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
  3. 设置公共viem client,用于读取链数据,该客户端将用于读取部署的交易回执
  4. 为编译的合约加载合约bytecodeabi
  5. 创建deploy异步函数,该函数使用 walletClient.deployContract函数部署合约
  6. 使用walletClient.deployContract函数签署和发送交易。您需要传入合约的ABI和字节码,部署交易的账户,以及incrementer的初始值。使用await等待交易处理完毕并返回交易哈希值
  7. 使用publicClient.readContract函数获取部署的交易回执。使用await等待交易处理完毕并返回交易地址
  8. 最后,运行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

如果成功,您将在终端看到合约地址。

The result of running the deploy script in the terminal

读取合约数据(调用函数)

Call函数是一种不会修改合约存储(更改变量)的交互类型,这意味着无需发送任何交易。他们只是读取已部署合约的各种存储变量。

首先,您需要创建一个文件,将其命名为get.ts

touch get.ts

然后,您可以执行以下步骤创建脚本:

  1. 更新您的导入以包含viem中的createPublicClienthttp函数,viem/chains中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts文件中的contractFile
  2. 设置公共viem client,用于读取链数据,该客户端可用于读取Incrementer合约的当前数量。
  3. 使用已部署合约的地址创建contractAddress变量,以及使用compile.ts文件中的contractFile创建abi变量
  4. 创建get异步函数
  5. 使用publicClient.readContract函数调用合约,传入abi、函数名称、contractAddress和任何参数(若需要)。您可以使用await,当请求承诺解决,将返回请求的值
  6. 最后,调用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

如果成功,您将在终端看到值。

The result of running the get script in the terminal

与合约交互(发送函数)

Send函数是一种修改合约存储(更改变量)的交互类型,这意味着需要签署并发送交易。在这一部分中,您将创建两个脚本:一个用于增量,一个用于重置增量器。 首先,您可以为每个脚本创建一个文件,并将其分别命名为increment.tsreset.ts

touch increment.ts reset.ts

打开increment.ts文件,并执行以下步骤创建脚本:

  1. 更新您的导入以包含viem中的createWalletClienthttp函数,viem/chains中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts文件中的contractFile
  2. 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
  3. 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
  4. 使用已部署合约的地址创建contractAddress变量,使用compile.ts文件中的contractFile创建abi变量,以及创建合约递增的_value
  5. 创建increment异步函数
  6. 使用walletClient.writeContract函数调用合约,传入abi、函数名称、contractAddress_value。您可以使用await,当请求承诺解决,将返回交易哈希
  7. 使用publicClient.waitForTransactionReceipt函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行get.ts脚本来检查当前数值是否已按预期更新,此函数特别有用
  8. 最后,调用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脚本,以确保该值按预期更改。

The result of running the increment and get scripts in the terminal

接下来,您可以打开reset.ts文件,并执行以下步骤创建脚本:

  1. 更新您的导入以包含viem中的createWalletClienthttp函数,viem/chains中您想要交互的网络,以及从您在编译合约脚本部分创建的compile.ts文件中的contractFile
  2. 设置viem wallet client,用于写入链数据,该客户端可用于与私钥一起发送交易。请注意:本教程仅用于操作演示,请勿将您的私钥存储于TypeScript文件中
  3. 设置公共viem client,用于读取链数据,该客户端将用于等待交易回执
  4. 使用已部署合约的地址创建contractAddress变量,并使用compile.ts文件中的contractFile创建abi变量,以实现合约增量
  5. 创建reset异步函数
  6. 使用walletClient.writeContract函数调用合约,传入abi、函数名称、contractAddress和一个参数空白数组。您可以使用await,当请求承诺解决,将返回交易哈希
  7. 使用publicClient.waitForTransactionReceipt函数等待交易回执,这表明交易已完成。如果您需要交易回执,或者在此之后直接运行get.ts脚本来检查当前数值是否已按预期更新,此函数特别有用
  8. 最后,调用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脚本,以确保该值按预期更改。

The result of running the reset and get scripts in the terminal

本网站的所有信息由第三方提供,仅供参考之用。Moonbeam文档网站(https://docs.moonbeam.network/)上列出和描述的任何项目与Moonbeam立场无关。Moonbeam Foundation不保证网站信息的准确性、完整性或真实性。如使用或依赖本网站信息,需自行承担相关风险,Moonbeam Foundation不承担任何责任和义务。这些材料的所有陈述和/或意见由提供方个人或实体负责,与Moonbeam Foundation立场无关,概不构成任何投资建议。对于任何特定事项或情况,应寻求专业权威人士的建议。此处的信息可能会包含或链接至第三方提供的信息与/或第三方服务(包括任何第三方网站等)。这类链接网站不受Moonbeam Foundation控制。Moonbeam Foundation对此类链接网站的内容(包括此类链接网站上包含的任何信息或资料)概不负责也不认可。这些链接内容仅为方便访客而提供,Moonbeam Foundation对因您使用此信息或任何第三方网站或服务提供的信息而产生的所有责任概不负责。
Last update: January 25, 2024
| Created: December 4, 2023