Node.js 与以太坊,用 JavaScript/TypeScript 构建去中心化应用(DApp)的实战指南

时间: 2026-03-05 16:06 阅读数: 13人阅读

以太坊,作为全球领先的智能合约平台,为去中心化应用(DApp)的开发提供了坚实的基础,而 Node.js,凭借其异步、事件驱动的特性以及庞大的 npm 生态系统,成为了与以太坊区块链进行交互、构建 DApp 后端逻辑和工具链的理想选择,本文将深入探讨如何使用 Node.js 编写以太坊相关的代码,涵盖从环境搭建到智能合约交互的核心步骤。

为什么选择 Node.js 进行以太坊开发

在开始编码之前,理解 Node.js 在以太坊生态中的优势至关重要:

  1. JavaScript/TypeScript 生态:以太坊智能合约主要用 Solidity 编写,而前端交互通常用 JavaScript,Node.js 允许开发者使用同一种语言(或 TypeScript 增强类型安全)处理后端逻辑、区块链交互和工具脚本,提高开发效率。
  2. 异步 I/O:区块链操作(如发送交易、查询状态)通常是网络 I/O 密集型且耗时的,Node.js 的非阻塞 I/O 模型能优雅地处理这些异步操作,避免阻塞主线程。
  3. 丰富的库支持:npm 上有大量成熟的以太坊相关库(如 web3.js, ethers.js),简化了与以太坊节点、钱包、智能合约的交互。
  4. 全栈开发能力:Node.js 可以轻松构建 DApp 的后端服务,例如提供 API 接口、处理用户认证、管理数据等,与前端无缝集成。
  5. 脚本和自动化:利用 Node.js 可以编写自动化脚本,用于部署合约、测试、监控链上数据等,提升开发运维效率。

环境准备:踏上以太坊开发之旅

在编写 Node.js 以太坊代码之前,需要准备以下环境:

  1. Node.js 和 npm:从 Node.js 官网 下载并安装 LTS 版本。
  2. 以太坊节点
    • 随机配图
>本地节点:运行自己的以太坊节点(如 Geth 或 Parity),但同步区块数据耗时较长。
  • Infura 或 Alchemy:使用这些第三方服务提供商的节点,无需同步全节点,通过 HTTP 或 WebSocket 连接即可,适合开发和测试,注册后可获取节点 URL。
  • Ganache:个人以太坊区块链,用于快速本地测试和开发,会预先分配测试账户和 Ether,非常方便。
  • 钱包:MetaMask 是最流行的浏览器钱包,也提供开发者 API,方便 DApp 与用户交互,管理用户账户和签名交易。
  • Solidity 编译器 (solc):用于将 Solidity 智能合约代码编译成以太坊虚拟机(EVM)可执行的字节码和 ABI(应用程序二进制接口)。
  • 核心库:Web3.js 与 Ethers.js

    与以太坊交互,离不开核心库,目前最主流的是 web3.jsethers.js

    • web3.js:历史最悠久,社区庞大,是 Web3 事实上的标准库,提供了全面的 API 与以太坊节点、钱包、合约交互。
    • ethers.js:相对较新,但设计更现代,API 更简洁,安全性考虑更周全(例如内置签名验证),对 TypeScript 支持更好,近年来发展迅速。

    以下示例将以 ethers.js 为例,因其更现代的特性和开发者友好的 API。

    Node.js 以太坊代码实战

    初始化项目与安装依赖

    mkdir my-dapp
    cd my-dapp
    npm init -y
    npm install ethers

    连接到以太坊节点

    const { ethers } = require("ethers");
    // 替换为你的 Infura/Alchemy 节点 URL 或 Ganache RPC URL
    const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545"); // Ganache 默认
    // const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
    // 获取当前区块号
    provider.getBlockNumber().then((blockNumber) => {
        console.log("当前区块号:", blockNumber);
    }).catch(console.error);
    // 获取账户余额
    const address = "0xYourAddressHere"; // 替换为以太坊地址
    provider.getBalance(address).then((balance) => {
        console.log(`地址 ${address} 的余额:`, ethers.utils.formatEther(balance), "ETH");
    }).catch(console.error);

    编译和部署智能合约

    你需要一个简单的 Solidity 合约,SimpleStorage.sol

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    contract SimpleStorage {
        uint256 private storedData;
        function set(uint256 x) public {
            storedData = x;
        }
        function get() public view returns (uint256) {
            return storedData;
        }
    }

    编译合约: 可以使用 solc-js 命令行工具或 Truffle/Hardhat 框架,这里以 solc-js 为例(需先安装:npm install solc)。

    部署合约(使用 Node.js 和 ethers.js)

    const { ethers } = require("ethers");
    const fs = require("fs");
    const solc = require("solc");
    // 1. 编译合约
    const sourceCode = fs.readFileSync("SimpleStorage.sol", "utf8");
    const input = {
        language: "Solidity",
        sources: {
            "SimpleStorage.sol": {
                content: sourceCode
            }
        },
        settings: {
            outputSelection: {
                "*": {
                    "*": ["*"]
                }
            }
        }
    };
    const compiledOutput = JSON.parse(solc.compile(JSON.stringify(input)));
    const contractInterface = compiledOutput.contracts["SimpleStorage.sol"]["SimpleStorage"].abi;
    const bytecode = compiledOutput.contracts["SimpleStorage.sol"]["SimpleStorage"].evm.bytecode.object;
    // 2. 连接节点和钱包
    // 使用 Ganache 提供的测试账户私钥
    const privateKey = "0xYourTestAccountPrivateKeyHere"; // 替换为 Ganache 中的私钥
    const wallet = new ethers.Wallet(privateKey, provider);
    console.log("部署者地址:", wallet.address);
    // 3. 部署合约
    const factory = new ethers.ContractFactory(contractInterface, bytecode, wallet);
    const contract = await factory.deploy(); // 部署合约,返回 Contract 对象
    console.log("合约部署中,交易哈希:", contract.deployTransaction.hash);
    // 等待合约部署确认
    await contract.deployed();
    console.log("合约部署成功,地址:", contract.address);

    与已部署的智能合约交互

    假设合约已部署,我们可以读取和调用其函数。

    // 合约地址(从部署结果获取或已知)
    const contractAddress = "0xYourDeployedContractAddressHere";
    // 合约 ABI (从编译输出获取或已知)
    const contractABI = [ /* 这里粘贴 SimpleStorage 的 ABI */ ]; // 为了简洁,省略具体 ABI 内容
    // 创建合约实例
    const contract = new ethers.Contract(contractAddress, contractABI, provider); // 使用 provider 只读
    // const contractWithSigner = new ethers.Contract(contractAddress, contractABI, wallet); // 使用 wallet 可写
    // 读取合约状态(调用 view/pure 函数)
    const currentValue = await contract.get();
    console.log("当前存储的值:", currentValue.toString());
    // 调用合约状态修改函数(需要签名)
    const setTx = await contractWithSigner.set(42); // 调用 set 函数,参数为 42
    console.log("交易发送中,哈希:", setTx.hash);
    await setTx.wait(); // 等待交易确认
    console.log("交易确认成功!");
    // 再次读取验证
    const newValue = await contract.get();
    console.log("修改后的值:", newValue.toString());

    进阶主题与最佳实践

    1. 事件监听:智能合约可以触发事件,Node.js 可以通过监听这些事件来获取实时通知。
      contract.on("ValueChanged", (newValue, event) => {
          console.log("监听到 ValueChanged 事件,新值:", newValue.toString());
      });
    2. 交易管理:处理交易回执、Gas 估算、错误处理等。
    3. 安全考虑
      • 私钥安全:切勿将私钥硬编码在代码中,应使用环境变量或专业的密钥管理服务。
      • 输入验证:对用户输入进行严格验证,防止恶意输入。
      • 重入攻击:在智能合约和与合约交互的 Node.js 代码中都要注意防范重入攻击。
    4. 使用框架:对于复杂项目,可以考虑使用 Truffle 或 Hardhat 框架,它们提供了开发环境、测试

    上一篇:

    下一篇: