Node.js 与以太坊,搭建区块链交互的桥梁

投稿 2026-04-07 4:42 点击数: 2

区块链技术,尤其是以太坊,以其智能合约的强大功能,正在重塑多个行业的运作方式,对于开发者而言,如何将现有的应用与以太坊网络连接,实现数据的读取与写入,是一个核心问题,Node.js,凭借其异步、事件驱动的特性以及丰富的生态系统,成为了与以太坊交互的理想选择,本文将详细介绍如何使用Node.js访问以太坊网络,包括连接节点、读取数据、发送交易以及与智能合约交互等关键步骤。

为何选择Node.js访问以太坊

在选择技术栈时,Node.js具有以下优势:

  1. 异步I/O模型:以太坊交互涉及大量的网络请求(如发送交易、查询状态),Node.js的非阻塞I/O特性能够高效处理这些并发操作,提升应用性能。
  2. JavaScript/TypeScript生态:以太坊官方和社区提供了大量基于JavaScript的库(如Web3.js、Ethers.js),使得开发者可以使用熟悉的语言进行开发,降低了学习成本。
  3. 全栈开发能力:如果前端或后端已经使用了Node.js,使用Node.js与以太坊交互可以保持技术栈的一致性,简化开发流程。
  4. 丰富的npm包:npm拥有海量的JavaScript包,其中许多可以辅助以太坊开发,如加密、数据处理等。

前置条件:以太坊节点的接入

Node.js本身不能直接与以太坊区块链通信,它需要一个“中间人”——以太坊节点,节点是运行以太坊协议的计算机,维护着区块链的副本,开发者可以通过以下方式接入以太坊节点:

  1. 自己运行节点

    • Geth:以太坊官方的Go语言客户端功能全面,但资源消耗较大。
    • Parity:另一种流行的客户端,以性能和易用性著称。
    • 优缺点:完全控制数据,隐私性好,但需要较高的硬件配置和持续的网络连接,同步区块可能耗时较长。
  2. 使用Infura等第三方服务

    • Infura是一个提供以太坊节点服务的平台,开发者可以免费或付费获得一个节点连接URL。
    • 优缺点:无需维护节点,开箱即用,稳定可靠,适合大多数开发者,但数据依赖于第三方,且免费版可能有速率限制。
  3. 使用Alchemy等类似服务

    Alchemy是另一个高质量的节点服务提供商,专注于开发者体验,提供强大的API和工具。

对于初学者或大多数应用场景,推荐使用Infura或Alchemy等第三方服务,简单快捷。

核心库的选择:Web3.js vs. Ethers.js

要与以太坊交互,我们需要一个JavaScript库,目前最主流的是Web3.js和Ethers.js。

  • Web3.js

    • 以太坊官方维护的库,历史较悠久,社区庞大,文档相对完善。
    • 提供了与以太坊JSON-RPC API的直接映射。
    • API设计相对底层,某些操作可能需要更多代码。
  • Ethers.js

    • 由社区开发,近年来迅速崛起,以其优雅的API设计和更好的开发者体验著称。
    • 提供了更高层次的抽象,如合约实例、ABI编码解码等,使用起来更便捷。
    • 内置了对Provider、Signer、Contract等核心概念的清晰封装。

推荐:新项目可以考虑优先使用Ethers.js,其API设计更现代化,开发效率更高,但Web3.js依然非常稳定且广泛使用,维护现有项目时可能仍需使用。

使用Node.js访问以太坊实战

下面我们以更流行的Ethers.js为例,展示基本操作。

环境准备

确保你的系统已安装Node.js和npm,然后创建一个新的项目目录并初始化:

mkdir node-ethereum-example
cd node-ethereum-example
npm init -y

安装Ethers.js:

npm install ethers

连接到以太坊网络

以Infura为例,你需要注册Infura并创建一个新的项目,获取一个以https://开头的RPC URL(以太坊主网测试网如Goerli的URL)。

创建一个connect.js文件:

随机配图
ss="brush:javascript;toolbar:false">const { ethers } = require("ethers"); // 替换为你的Infura RPC URL const INFURA_URL = "https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID"; // 创建一个Provider const provider = new ethers.providers.JsonRpcProvider(INFURA_URL); // 获取最新的区块号 async function getLatestBlockNumber() { try { const blockNumber = await provider.getBlockNumber(); console.log("当前最新区块号:", blockNumber); } catch (error) { console.error("获取区块号失败:", error); } } getLatestBlockNumber();

运行node connect.js,如果配置正确,你应该能看到最新的以太坊测试网区块号。

读取链上数据

获取账户余额:

// 延续上面的代码
async function getBalance(address) {
  try {
    const balance = await provider.getBalance(address);
    // ethers.utils.formatEther将wei转换为ether
    console.log(`地址 ${address} 的余额: ${ethers.utils.formatEther(balance)} ETH`);
  } catch (error) {
    console.error("获取余额失败:", error);
  }
}
// 替换为你要查询的地址
const addressToQuery = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B";
getBalance(addressToQuery);

发送交易(需要钱包)

发送交易需要私签名,这通常意味着你需要一个以太坊钱包(如MetaMask)来管理账户。注意:私钥绝对不能硬编码在代码中或提交到版本控制! 应该使用环境变量或安全的密钥管理服务。

require('dotenv').config(); // 用于加载.env文件中的环境变量
// npm install dotenv
// 延续上面的代码
async function sendTransaction() {
  // 从环境变量获取私钥(实际项目中应更安全地管理)
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error("请设置PRIVATE_KEY环境变量");
    return;
  }
  const wallet = new ethers.Wallet(privateKey, provider);
  // 接收方地址
  const recipientAddress = "0x1234567890123456789012345678901234567890";
  // 发送的ETH数量(单位:ether)
  const amountToSend = ethers.utils.parseEther("0.01"); // 0.01 ETH
  // 构建交易
  const tx = {
    to: recipientAddress,
    value: amountToSend,
    gasLimit: 21000, // 转账ETH的典型gas限制
    gasPrice: await provider.getGasPrice(), // 获取当前gas价格
  };
  // 发送交易
  console.log("正在发送交易...");
  const txResponse = await wallet.sendTransaction(tx);
  console.log("交易哈希:", txResponse.hash);
  // 等待交易被确认
  const txReceipt = await txResponse.wait();
  console.log("交易确认成功,区块号:", txReceipt.blockNumber);
}
// 调用发送交易函数(确保钱包地址有足够的ETH支付gas和转账金额)
// sendTransaction();

在项目根目录创建.env文件:

PRIVATE_KEY=你的钱包私钥

并确保.env文件被添加到.gitignore中。

与智能合约交互

这是Node.js访问以太坊最强大的功能之一,你需要:

  1. 智能合约的ABI(Application Binary Interface):一个JSON文件,描述了合约的接口(函数、事件、变量等)。
  2. 智能合约的地址:部署到以太坊网络后的合约地址。

假设我们有一个简单的存储合约,有一个store(uint256)函数和一个retrieve()函数。

// 延续上面的代码
async function interactWithContract() {
  // 合约地址和ABI(这里简化,实际应从文件读取)
  const contractAddress = "0x你的合约地址";
  const contractABI = [
    "function store(uint256 num)",
    "function retrieve() view returns (uint256)"
  ];
  // 创建合约实例
  const contract = new ethers.Contract(contractAddress, contractABI, provider);
  // 1. 读取合约数据(调用retrieve函数)
  try {
    const storedValue = await contract.retrieve();
    console.log("合约中存储的值:", storedValue.toString());
  } catch (error) {
    console.error("读取合约数据失败:", error);
  }
  // 2. 写入合约数据(调用store函数)
  // 注意:这需要签名账户,所以我们需要使用wallet而不是provider
  const connectedContract = contract.connect(wallet); // 将合约实例连接到签名账户
  const newValue = ethers.Big