Web3 开发入门:用 Ethers.js 玩转以太坊交易与合约

目录

[1. 环境准备](#1. 环境准备)

[安装 Ethers.js](#安装 Ethers.js)

创建项目结构

[2. 连接以太坊网络](#2. 连接以太坊网络)

[2.1 连接到各种网络](#2.1 连接到各种网络)

[2.2 检查网络连接](#2.2 检查网络连接)

[3. 钱包和账户管理](#3. 钱包和账户管理)

[3.1 创建和管理钱包](#3.1 创建和管理钱包)

[3.2 查询账户余额](#3.2 查询账户余额)

[4. 发送交易](#4. 发送交易)

[4.1 发送 ETH 交易](#4.1 发送 ETH 交易)

[4.2 动态 Gas 价格](#4.2 动态 Gas 价格)

[5. 智能合约交互](#5. 智能合约交互)

[5.1 连接现有合约](#5.1 连接现有合约)

[5.2 部署新合约](#5.2 部署新合约)

[6. 高级功能](#6. 高级功能)

[6.1 批量交易](#6.1 批量交易)

[6.2 事件监听和过滤](#6.2 事件监听和过滤)

[6.3 签名和验证](#6.3 签名和验证)

[7. 实战项目示例](#7. 实战项目示例)

[7.1 简单的 DApp 前端](#7.1 简单的 DApp 前端)

[7.2 完整的 Node.js 脚本示例](#7.2 完整的 Node.js 脚本示例)

[8. 最佳实践和安全提示](#8. 最佳实践和安全提示)

[8.1 安全注意事项](#8.1 安全注意事项)

[8.2 性能优化](#8.2 性能优化)

[8.3 测试网络水龙头](#8.3 测试网络水龙头)

总结

下一步学习建议:

资源推荐:


本文将带你从零开始学习使用 Ethers.js 进行以太坊开发,涵盖账户管理、交易发送和智能合约交互等核心功能。

1. 环境准备

安装 Ethers.js

bash 复制代码
# 使用 npm
npm install ethers

# 使用 yarn
yarn add ethers

# 使用 CDN(浏览器)
<script src="https://cdn.ethers.io/lib/ethers-5.7.umd.min.js"></script>

创建项目结构

bash 复制代码
mkdir ethers-tutorial
cd ethers-tutorial
npm init -y
npm install ethers dotenv

2. 连接以太坊网络

2.1 连接到各种网络

javascript 复制代码
// 1. 连接到以太坊主网
const { ethers } = require("ethers");

// 使用 Infura 或 Alchemy 节点服务
const provider = new ethers.providers.JsonRpcProvider(
  "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID"
);

// 2. 连接到测试网络
const goerliProvider = new ethers.providers.JsonRpcProvider(
  "https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID"
);

// 3. 连接到本地开发网络(如 Hardhat、Ganache)
const localProvider = new ethers.providers.JsonRpcProvider("http://localhost:8545");

// 4. 使用默认提供者(自动连接到多种服务)
const defaultProvider = ethers.getDefaultProvider("goerli");

2.2 检查网络连接

javascript 复制代码
async function checkNetwork() {
  const network = await provider.getNetwork();
  console.log("网络信息:", network);
  
  const blockNumber = await provider.getBlockNumber();
  console.log("当前区块高度:", blockNumber);
  
  const gasPrice = await provider.getGasPrice();
  console.log("当前 Gas 价格:", ethers.utils.formatUnits(gasPrice, "gwei"), "Gwei");
}

checkNetwork();

3. 钱包和账户管理

3.1 创建和管理钱包

javascript 复制代码
// 1. 从私钥创建钱包
const privateKey = "0xYOUR_PRIVATE_KEY";
const wallet = new ethers.Wallet(privateKey, provider);

console.log("地址:", wallet.address);
console.log("私钥:", wallet.privateKey);

// 2. 创建随机钱包
const randomWallet = ethers.Wallet.createRandom();
console.log("新钱包地址:", randomWallet.address);
console.log("助记词:", randomWallet.mnemonic.phrase);

// 3. 从助记词恢复钱包
const mnemonic = "your twelve word mnemonic phrase here";
const mnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic);

// 4. 加密和导出钱包
async function encryptWallet() {
  const password = "strong-password-123";
  const encryptedJson = await wallet.encrypt(password);
  console.log("加密后的钱包JSON:", encryptedJson);
  
  // 从加密JSON恢复钱包
  const restoredWallet = await ethers.Wallet.fromEncryptedJson(encryptedJson, password);
  console.log("恢复的地址:", restoredWallet.address);
}

3.2 查询账户余额

javascript 复制代码
async function getBalances() {
  const address = "0x742d35Cc6634C0532925a3b844Bc9e90F90a5e55";
  
  // 获取 ETH 余额
  const balance = await provider.getBalance(address);
  console.log("ETH 余额:", ethers.utils.formatEther(balance), "ETH");
  
  // 获取多个地址余额
  const addresses = [
    "0x742d35Cc6634C0532925a3b844Bc9e90F90a5e55",
    "0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf"
  ];
  
  for (let addr of addresses) {
    const bal = await provider.getBalance(addr);
    console.log(`${addr}: ${ethers.utils.formatEther(bal)} ETH`);
  }
}

4. 发送交易

4.1 发送 ETH 交易

javascript 复制代码
async function sendETH() {
  // 设置交易参数
  const toAddress = "0xRecipientAddress";
  const amount = ethers.utils.parseEther("0.1"); // 0.1 ETH
  
  // 创建交易对象
  const tx = {
    to: toAddress,
    value: amount,
    gasLimit: 21000, // 标准转账的 gas 限制
    gasPrice: ethers.utils.parseUnits("10", "gwei") // 10 Gwei
  };
  
  // 发送交易
  console.log("正在发送交易...");
  const transaction = await wallet.sendTransaction(tx);
  console.log("交易已发送,交易哈希:", transaction.hash);
  
  // 等待交易确认
  console.log("等待交易确认...");
  const receipt = await transaction.wait();
  console.log("交易已确认!");
  console.log("区块号:", receipt.blockNumber);
  console.log("使用的 Gas:", receipt.gasUsed.toString());
  
  return receipt;
}

// 发送交易并处理错误
async function sendETHWithErrorHandling() {
  try {
    const receipt = await sendETH();
    return receipt;
  } catch (error) {
    console.error("交易失败:", error.message);
    
    if (error.code === "INSUFFICIENT_FUNDS") {
      console.log("余额不足");
    } else if (error.code === "NETWORK_ERROR") {
      console.log("网络错误,请检查连接");
    }
    throw error;
  }
}

4.2 动态 Gas 价格

javascript 复制代码
async function sendWithDynamicGas() {
  // 获取当前 Gas 价格
  const currentGasPrice = await provider.getGasPrice();
  console.log("当前 Gas 价格:", ethers.utils.formatUnits(currentGasPrice, "gwei"), "Gwei");
  
  // 增加 Gas 价格以加速交易
  const increasedGasPrice = currentGasPrice.mul(110).div(100); // 增加10%
  
  // 估算 Gas 限制
  const tx = {
    to: "0xRecipientAddress",
    value: ethers.utils.parseEther("0.01")
  };
  
  try {
    const estimatedGas = await provider.estimateGas(tx);
    console.log("估算的 Gas 限制:", estimatedGas.toString());
    
    tx.gasLimit = estimatedGas;
    tx.gasPrice = increasedGasPrice;
    
    const transaction = await wallet.sendTransaction(tx);
    console.log("交易已发送:", transaction.hash);
  } catch (error) {
    console.error("Gas 估算失败:", error.message);
  }
}

5. 智能合约交互

5.1 连接现有合约

javascript 复制代码
// ERC20 代币合约 ABI(简化版)
const erc20Abi = [
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function decimals() view returns (uint8)",
  "function totalSupply() view returns (uint256)",
  "function balanceOf(address) view returns (uint256)",
  "function transfer(address to, uint amount) returns (bool)",
  "event Transfer(address indexed from, address indexed to, uint amount)"
];

// 连接到 USDT 合约
const usdtAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; // 主网 USDT
const usdtContract = new ethers.Contract(usdtAddress, erc20Abi, provider);

async function interactWithERC20() {
  // 读取合约信息
  const name = await usdtContract.name();
  const symbol = await usdtContract.symbol();
  const decimals = await usdtContract.decimals();
  
  console.log(`代币: ${name} (${symbol})`);
  console.log(`小数位数: ${decimals}`);
  
  // 查询余额
  const address = "0x742d35Cc6634C0532925a3b844Bc9e90F90a5e55";
  const balance = await usdtContract.balanceOf(address);
  
  const formattedBalance = ethers.utils.formatUnits(balance, decimals);
  console.log(`${address} 的 ${symbol} 余额: ${formattedBalance}`);
  
  // 监听转账事件
  usdtContract.on("Transfer", (from, to, amount, event) => {
    console.log(`转账事件: ${from} -> ${to}`);
    console.log(`金额: ${ethers.utils.formatUnits(amount, decimals)} ${symbol}`);
  });
}

// 发送代币交易
async function sendERC20Tokens() {
  // 使用签名者连接合约
  const contractWithSigner = usdtContract.connect(wallet);
  
  const recipient = "0xRecipientAddress";
  const amount = ethers.utils.parseUnits("10", 6); // 10 USDT(6位小数)
  
  try {
    const tx = await contractWithSigner.transfer(recipient, amount);
    console.log("转账交易已发送:", tx.hash);
    
    const receipt = await tx.wait();
    console.log("转账成功!");
    return receipt;
  } catch (error) {
    console.error("转账失败:", error.message);
    throw error;
  }
}

5.2 部署新合约

javascript 复制代码
// 简单的智能合约示例:SimpleStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
    uint256 private value;
    
    event ValueChanged(uint256 newValue);
    
    function setValue(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }
    
    function getValue() public view returns (uint256) {
        return value;
    }
}
javascript 复制代码
// 部署合约
async function deployContract() {
  // 合约 ABI 和字节码
  const contractABI = [
    "function setValue(uint256) public",
    "function getValue() public view returns (uint256)",
    "event ValueChanged(uint256)"
  ];
  
  const contractBytecode = "0x..."; // 编译后的字节码
  
  // 创建合约工厂
  const factory = new ethers.ContractFactory(contractABI, contractBytecode, wallet);
  
  console.log("正在部署合约...");
  
  // 部署合约
  const contract = await factory.deploy();
  console.log("合约部署交易已发送:", contract.deployTransaction.hash);
  
  // 等待部署完成
  await contract.deployed();
  console.log("合约已部署!");
  console.log("合约地址:", contract.address);
  
  return contract;
}

// 与部署的合约交互
async function useDeployedContract() {
  const contract = await deployContract();
  
  // 设置值
  const setTx = await contract.setValue(42);
  await setTx.wait();
  console.log("值已设置为 42");
  
  // 读取值
  const value = await contract.getValue();
  console.log("当前值:", value.toString());
}

6. 高级功能

6.1 批量交易

javascript 复制代码
// 使用 Multicall 批量查询
async function batchQuery() {
  const addresses = [
    "0xAddress1",
    "0xAddress2",
    "0xAddress3"
  ];
  
  // 创建批量查询
  const calls = addresses.map(addr => ({
    target: usdtAddress,
    callData: usdtContract.interface.encodeFunctionData("balanceOf", [addr])
  }));
  
  // 这里需要实际的 Multicall 合约地址
  // 或者使用 ethers.multicall(如果支持)
  
  console.log("批量查询完成");
}

// 使用 Promise.all 并行处理
async function parallelTransactions() {
  const recipients = [
    { address: "0xRecipient1", amount: "0.01" },
    { address: "0xRecipient2", amount: "0.02" },
    { address: "0xRecipient3", amount: "0.03" }
  ];
  
  const txPromises = recipients.map(async (recipient) => {
    const tx = await wallet.sendTransaction({
      to: recipient.address,
      value: ethers.utils.parseEther(recipient.amount)
    });
    return tx;
  });
  
  const transactions = await Promise.all(txPromises);
  console.log("所有交易已发送:", transactions.map(tx => tx.hash));
}

6.2 事件监听和过滤

javascript 复制代码
async function monitorEvents() {
  // 监听特定事件
  const filter = usdtContract.filters.Transfer(
    null, // 任何发送者
    "0xSpecificAddress" // 特定接收者
  );
  
  // 监听过去的事件
  const pastEvents = await usdtContract.queryFilter(filter, -1000); // 最近1000个区块
  console.log("过去事件数量:", pastEvents.length);
  
  // 实时监听新事件
  usdtContract.on(filter, (from, to, amount, event) => {
    console.log(`新事件 - 从 ${from} 到 ${to}`);
    console.log(`区块: ${event.blockNumber}, 交易哈希: ${event.transactionHash}`);
  });
  
  // 一段时间后停止监听
  setTimeout(() => {
    usdtContract.removeAllListeners();
    console.log("已停止监听事件");
  }, 60000); // 60秒后停止
}

6.3 签名和验证

javascript 复制代码
async function signAndVerify() {
  // 签名消息
  const message = "Hello, Ethereum!";
  const signature = await wallet.signMessage(message);
  console.log("签名:", signature);
  
  // 验证签名
  const recoveredAddress = ethers.utils.verifyMessage(message, signature);
  console.log("恢复的地址:", recoveredAddress);
  console.log("地址匹配:", recoveredAddress === wallet.address);
  
  // 签名交易(离线签名)
  const tx = {
    to: "0xRecipientAddress",
    value: ethers.utils.parseEther("0.1"),
    gasLimit: 21000,
    gasPrice: ethers.utils.parseUnits("10", "gwei"),
    nonce: await wallet.getTransactionCount(),
    chainId: 1 // 以太坊主网
  };
  
  const signedTx = await wallet.signTransaction(tx);
  console.log("已签名的交易:", signedTx);
  
  // 广播已签名的交易
  const txResponse = await provider.sendTransaction(signedTx);
  console.log("交易已广播:", txResponse.hash);
}

7. 实战项目示例

7.1 简单的 DApp 前端

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>简易以太坊钱包</title>
  <script src="https://cdn.ethers.io/lib/ethers-5.7.umd.min.js"></script>
</head>
<body>
  <h1>简易以太坊钱包</h1>
  
  <div>
    <button id="connectWallet">连接钱包</button>
    <p id="walletInfo"></p>
  </div>
  
  <div>
    <h3>发送 ETH</h3>
    <input id="recipient" placeholder="接收地址">
    <input id="amount" placeholder="金额 (ETH)" type="number" step="0.001">
    <button id="sendETH">发送</button>
  </div>
  
  <div>
    <h3>查询余额</h3>
    <input id="balanceAddress" placeholder="地址">
    <button id="getBalance">查询</button>
    <p id="balanceResult"></p>
  </div>

  <script>
    let provider, signer;
    
    document.getElementById('connectWallet').addEventListener('click', async () => {
      if (typeof window.ethereum !== 'undefined') {
        // MetaMask 可用
        await window.ethereum.request({ method: 'eth_requestAccounts' });
        provider = new ethers.providers.Web3Provider(window.ethereum);
        signer = provider.getSigner();
        
        const address = await signer.getAddress();
        document.getElementById('walletInfo').innerText = `已连接: ${address}`;
      } else {
        alert('请安装 MetaMask!');
      }
    });
    
    document.getElementById('getBalance').addEventListener('click', async () => {
      const address = document.getElementById('balanceAddress').value;
      if (!provider || !address) return;
      
      const balance = await provider.getBalance(address);
      document.getElementById('balanceResult').innerText = 
        `余额: ${ethers.utils.formatEther(balance)} ETH`;
    });
    
    document.getElementById('sendETH').addEventListener('click', async () => {
      if (!signer) {
        alert('请先连接钱包');
        return;
      }
      
      const recipient = document.getElementById('recipient').value;
      const amount = document.getElementById('amount').value;
      
      if (!recipient || !amount) {
        alert('请填写完整信息');
        return;
      }
      
      try {
        const tx = await signer.sendTransaction({
          to: recipient,
          value: ethers.utils.parseEther(amount)
        });
        
        alert(`交易已发送! 哈希: ${tx.hash}`);
      } catch (error) {
        alert(`交易失败: ${error.message}`);
      }
    });
  </script>
</body>
</html>

7.2 完整的 Node.js 脚本示例

javascript 复制代码
// complete-example.js
const { ethers } = require("ethers");
require("dotenv").config();

class EthereumManager {
  constructor() {
    this.provider = new ethers.providers.JsonRpcProvider(
      process.env.RPC_URL || "https://goerli.infura.io/v3/YOUR_PROJECT_ID"
    );
    
    if (process.env.PRIVATE_KEY) {
      this.wallet = new ethers.Wallet(process.env.PRIVATE_KEY, this.provider);
      console.log(`钱包已加载: ${this.wallet.address}`);
    }
  }
  
  async getNetworkInfo() {
    const [network, blockNumber, gasPrice] = await Promise.all([
      this.provider.getNetwork(),
      this.provider.getBlockNumber(),
      this.provider.getGasPrice()
    ]);
    
    return {
      name: network.name,
      chainId: network.chainId,
      blockNumber,
      gasPrice: ethers.utils.formatUnits(gasPrice, "gwei")
    };
  }
  
  async sendETH(to, amountETH) {
    if (!this.wallet) throw new Error("未设置钱包");
    
    const amount = ethers.utils.parseEther(amountETH.toString());
    const balance = await this.wallet.getBalance();
    
    if (balance.lt(amount)) {
      throw new Error(`余额不足。当前余额: ${ethers.utils.formatEther(balance)} ETH`);
    }
    
    const tx = await this.wallet.sendTransaction({
      to,
      value: amount,
      gasLimit: 21000
    });
    
    console.log(`交易已发送: ${tx.hash}`);
    const receipt = await tx.wait();
    
    return {
      hash: tx.hash,
      blockNumber: receipt.blockNumber,
      gasUsed: receipt.gasUsed.toString()
    };
  }
  
  async interactWithContract(contractAddress, abi) {
    const contract = new ethers.Contract(contractAddress, abi, this.provider);
    
    // 如果有钱包,创建可签名的合约实例
    const contractWithSigner = this.wallet ? contract.connect(this.wallet) : contract;
    
    return {
      contract,
      contractWithSigner,
      // 便捷方法
      read: async (method, ...args) => {
        return await contract[method](...args);
      },
      write: async (method, ...args) => {
        if (!this.wallet) throw new Error("需要钱包来写入合约");
        const tx = await contractWithSigner[method](...args);
        return await tx.wait();
      }
    };
  }
}

// 使用示例
async function main() {
  const ethManager = new EthereumManager();
  
  try {
    // 获取网络信息
    const networkInfo = await ethManager.getNetworkInfo();
    console.log("网络信息:", networkInfo);
    
    // 发送 ETH(如果有钱包)
    if (ethManager.wallet) {
      const txResult = await ethManager.sendETH(
        "0xRecipientAddress",
        "0.001"
      );
      console.log("交易结果:", txResult);
    }
    
    // 与合约交互
    const erc20Abi = [
      "function balanceOf(address) view returns (uint256)",
      "function symbol() view returns (string)"
    ];
    
    const usdtAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
    const usdt = await ethManager.interactWithContract(usdtAddress, erc20Abi);
    
    const symbol = await usdt.read("symbol");
    console.log(`代币符号: ${symbol}`);
    
  } catch (error) {
    console.error("错误:", error.message);
  }
}

if (require.main === module) {
  main();
}

module.exports = EthereumManager;

8. 最佳实践和安全提示

8.1 安全注意事项

  1. 私钥管理:永远不要将私钥硬编码在代码中,使用环境变量或安全的密钥管理系统

  2. 输入验证:始终验证用户输入和合约返回值

  3. 错误处理:正确处理所有可能的错误情况

  4. Gas 管理:合理设置 Gas 价格和限制,防止资金损失

8.2 性能优化

javascript 复制代码
// 使用批量请求优化性能
async function optimizedBatchRequests() {
  // 使用 provider.send 进行批量 RPC 调用
  const batch = [
    { jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] },
    { jsonrpc: "2.0", id: 2, method: "eth_gasPrice", params: [] },
    { jsonrpc: "2.0", id: 3, method: "eth_getBalance", params: ["0x...", "latest"] }
  ];
  
  // 缓存常用数据
  let cachedGasPrice = null;
  let cacheTime = 0;
  const CACHE_DURATION = 60000; // 60秒
  
  async function getCachedGasPrice() {
    const now = Date.now();
    if (!cachedGasPrice || (now - cacheTime) > CACHE_DURATION) {
      cachedGasPrice = await provider.getGasPrice();
      cacheTime = now;
    }
    return cachedGasPrice;
  }
}

8.3 测试网络水龙头

javascript 复制代码
// 获取测试网 ETH 的实用函数
async function getTestnetETH() {
  const network = await provider.getNetwork();
  
  if (network.chainId === 5) { // Goerli
    // 使用 Goerli 水龙头
    console.log("访问 https://goerlifaucet.com/ 获取测试 ETH");
  } else if (network.chainId === 11155111) { // Sepolia
    console.log("访问 https://sepoliafaucet.com/ 获取测试 ETH");
  }
  
  // 显示余额
  if (this.wallet) {
    const balance = await this.wallet.getBalance();
    console.log(`当前余额: ${ethers.utils.formatEther(balance)} ETH`);
  }
}

总结

通过本文,你已经学会了:

  1. Ethers.js 的基础安装和配置

  2. 连接到不同的以太坊网络

  3. 创建和管理以太坊钱包

  4. 发送 ETH 交易和代币转账

  5. 与智能合约进行交互

  6. 部署新的智能合约

  7. 使用高级功能如事件监听和签名验证

下一步学习建议:

  1. 学习 Solidity:深入理解智能合约开发

  2. 探索其他库:了解 web3.js 和 viem 等替代方案

  3. 学习开发框架:掌握 Hardhat 或 Truffle 等开发工具

  4. 实践项目:尝试构建完整的 DApp 项目

  5. 学习 Layer 2:探索 Optimism、Arbitrum 等扩展方案

资源推荐:

开始你的 Web3 开发之旅吧!记得始终在测试网络上进行开发和测试,确保理解每个操作后再在主网上执行。

相关推荐
AC赳赳老秦3 小时前
专利附图说明:DeepSeek生成的专业技术描述与权利要求书细化
大数据·人工智能·kafka·区块链·数据库开发·数据库架构·deepseek
暴躁小师兄数据学院12 小时前
【WEB3.0零基础转行笔记】基础知识篇—第一讲:区块链基础
笔记·web3·区块链
中金快讯13 小时前
FP Markets澳福点差竞争力存疑?澳福低成本策略能否重塑优势?
人工智能·区块链
泷fyk13 小时前
区块链+AI数据管理产品设计方案
大数据·人工智能·区块链
皮皮学姐分享-ppx14 小时前
中国城市间交通驾车距离矩阵(2024)
大数据·人工智能·经验分享·科技·区块链
草原猫2 天前
产业数智化:DAPP开发赋能实体行业的实践与探索
区块链·dapp开发
China_Yanhy2 天前
我的区块链运维日记 · 第 13 日:跨链惊魂 —— Layer 2 的“快”与“慢”
运维·区块链
voidmort2 天前
web3中的共识:PBFT、Tendermint 与 DAG 共识
web3·区块链