ethers.js 全栈开发实战:从 Provider 到 Utils 的 6 大核心模块深度解析

387 阅读7分钟

前言

在以太坊生态中,ethers.js 是开发者与区块链交互的核心工具包。本文系统梳理了其六大核心模块(Provider、Contract、Wallet、Utils、部署工具及高级功能),通过代码示例 + 关键差异对比 + 安全实践,帮助开发者快速掌握从环境搭建到链上交互的全流程。无论是浏览器端轻量集成,还是 Node.js 服务端深度开发,本文均提供可直接落地的解决方案

安装

  • 链接方式

# 在页面上使用链接使用
 <script type="module">
    import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
</script>
  • 安装包方式

# 项目中引入包
npm install ethers
import {ethers} from "ethers";
import { BrowserProvider, parseUnits } from "ethers"; 
import { HDNodeWallet } from "ethers/wallet";

Provider提供者类

通过Provider类,读取链上的信息;

  • ethers.BrowserProvider
#和钱包插件交 链接钱包插件读取钱包账号的信息
import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
const provider = new ethers.BrowserProvider(window.ethereum);
  • ethers.JsonRpcProvider
import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
# 注册infura 创意一个项目获取your-infura-project-id ,读取sepolia链上的信息
    const ALCHEMY_MAINNET_URL="https://sepolia.infura.io/v3/{your-infura-project-id}"
    const provider=new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
    or
    # 启动ganach 
    # 读取本地ganach
    const provider = new ethers.JsonRpcProvider("http://127.0.0.1:7545");

区别

特性ethers.BrowserProviderethers.JsonRpcProvider
用途连接到用户的钱包(如 MetaMask)连接到远程以太坊节点(如 Infura、Alchemy)
依赖依赖 window.ethereum依赖远程节点的 JSON-RPC 接口
用户友好高(用户直接使用钱包)低(需要配置远程节点)
安全性高(私钥存储在本地钱包)低(需要信任远程节点)
功能有限(依赖钱包功能)强大(访问节点全部功能)
适用场景浏览器环境,用户交互服务器端或无钱包环境

Provider常见属性和方法

属性

  • blockNumber:返回 Provider 已经知晓的最新区块号(块高),如果没有同步到区块,则为 null
  • polling:可变属性,表示 Provider 是否正在轮询。轮询可以设置为临时启用/禁用或永久禁用以允许节点进程退出。
  • pollingInterval:可变属性,表示 Provider 的轮询频率(以毫秒为单位)。默认时间间隔为 4 秒

方法

getNetwork() :获取当前连接的网络信息

const ALCHEMY_MAINNET_URL="https://sepolia.infura.io/v3/{your-infura-project-id}"
const provider=new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
const network = await provider.getNetwork();
# 网络名
console.log("Network name:", network.name);//sepolia
# 网络id
console.log("Chain ID:", network.chainId);//11155111

getBalance(address) :获取指定地址的余额

# 节点公钥
例如:0xEb9e88cE633B2a8F400xxxxxxxxx
const balance = await provider.getBalance("0xEb9e88cE633B2a8F400xxxxxxxxx");
console.log("Balance:", ethers.formatEther(balance)); // 把wei格式化为 ETH

getTransaction(transactionHash) :获取指定交易的详细信息。

    const ALCHEMY_MAINNET_URL="https://sepolia.infura.io/v3/{your-infura-project-id}"
    const provider=new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
    //通过交易hash:在sepolia.etherscan浏览器器上找一个交易hash:例如:0xb1fd8ca5493460bb7d9d1ae7d0a96fcd7a77c3eea53954f9ab6ae6225019972b验证
    const transaction = await provider.getTransaction("0xYourTransactionHashHere");
    console.log("Transaction from:", transaction.from);
    console.log("Transaction to:", transaction.to);
    console.log("Transaction value:", ethers.formatEther(transaction.value));

getTransactionReceipt(transactionHash) :获取指定交易的收据信息。

const ALCHEMY_MAINNET_URL="https://sepolia.infura.io/v3/{your-infura-project-id}"
   const provider=new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
   //通过交易hash:在sepolia.etherscan浏览器器上找一个交易hash:例如:0xb1fd8ca5493460bb7d9d1ae7d0a96fcd7a77c3eea53954f9ab6ae6225019972b验证
   const receipt = await provider.getTransactionReceipt("0xYourTransactionHashHere");
   console.log("Transaction status:", receipt.status);
   console.log("Transaction logs:", receipt.logs);

sendTransaction(signedTransaction) :发送一个已签名的交易。

   const txHash = await provider.sendTransaction(signedTransaction);
   console.log("Transaction hash:", txHash); 

getSigner(address) :获取与指定地址关联的 Signer 对象。

   const signer = provider.getSigner("0xYourAddressHere");
   console.log("Signer address:", await signer.getAddress());

estimateGas(transaction) :估算执行指定交易所需的 gas。

   const gasEstimate = await provider.estimateGas({
     to: "0xRecipientAddressHere",
     from: "0xSenderAddressHere",
     value: ethers.parseEther("1.0"),
   });
   console.log("Estimated gas:", gasEstimate.toString());

call(transaction, blockTag) :调用一个智能合约的函数,不发送交易。

   const result = await provider.call({
     to: "0xContractAddressHere",
     data: "0xYourEncodedFunctionCallHere",
   });
   console.log("Call result:", result);

getBlock(blockTag) :获取指定区块的信息

       const block = await provider.getBlock("latest");
       console.log("Block number:", block.number);
       console.log("Block timestamp:", block.timestamp);

getBlockTransactionCount(blockTag) :获取指定区块中的交易数量。

   const transactionCount = await provider.getBlockTransactionCount("latest");
   console.log("Transaction count in block:", transactionCount);

resolveName(ensName) :获取 ensName 对应地址的 Promise,如果没有则为 null

    provider.resolveName("registrar.firefly.eth").then(function(address) {
      console.log("Address: " + address);
      // "0x6fC21092DA55B392b045eD78F4732bff3C580e2c"
    });

lookupAddress(address) :获取 address 对应的 ENS 名称的 Promise,如果没有则为 null

let address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c";
provider.lookupAddress(address).then(function(name) {
  console.log("Name: " + name);
  // "registrar.firefly.eth"
});

Contract合约类

说明:通过一个ERC20的代币合约,实现对合约的读写操作

  • 合约类型

    • 可读:最后参数:Provider
    • 可读、写:最后参数 Singer
    • 区别:创建合约的最后一个参数:是Provider合约只读,Singer可以读、写
  • 只读

# 说明: 使用hardhat 部署成功后会在控制台返回合约地址 abi 在artifacts/contracts/中的json中取出abi数组
# 引入包
import { ethers } from 'https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js';
//const provider = new ethers.BrowserProvider(window.ethereum);
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
# 创建合约
const TokenAddress="0xxxxxxxxxx";
const const TokenABI =[{
                        "inputs": [],
                        "name": "name",
                        "outputs": [{"type": "string"}],
                        "stateMutability": "view",
                        "type": "function"
                    },
                    {
                        "inputs": [],
                        "name": "symbol",
                        "outputs": [{"type": "string"}],
                        "stateMutability": "view",
                        "type": "function"
                    },
                    {
                        "inputs": [],
                        "name": "totalSupply",
                        "outputs": [{"type": "uint256"}],
                        "stateMutability": "view",
                        "type": "function"
                    }
                    //其他更多
                    ]
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, provider);
# 代币名
const name = await TokenContract.name();
console.log("Contract Name:", name);
# 代币符合
const symbol = await TokenContract.symbol();
console.log("Contract Symbol:", symbol);
# 代币总额
const totalSupply = await TokenContract.totalSupply();
  • 读、写

说明:最主要的区别是创建合约的提供者不同(singer)
import { ethers } from 'https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js';
//const provider = new ethers.BrowserProvider(window.ethereum);
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
# 创建合约
const TokenAddress="0xxxxxxxxxx";
const const TokenABI =[];
const signer = await provider.getSigner();
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, singer);
# 交互操作 转账
const addr1="0xxxxxxxxxx";
# 给addr1转1LYTH
const tx= await TokenContract.transfer(addr1,1);
# 等待交易成功
await tx.wait()
console.log("查看账号addr1的余额",await TokenContract.balanceOf(addr1))
console.log("查看账号signer的余额",await TokenContract.balanceOf(signer))
  • 部署

说明通过合约工厂部署合约

const { ethers } = require("hardhat");
 async function deployContract() {
        try {
            const [signer] = await ethers.getSigners();//签名者
            //通过 npx hardhat compile编译合约,在artifacts/contracts/xxx.sol/xxx.json中复制abi数组和bytecode码
            const abi=[]//
            const bytecode="";//
            const deployFactory = new ethers.ContractFactory(abi,bytecode,signer)//工厂合约
            const TokenContract = await deployFactory.deploy();//部署
            console.log("部署合约交易详情",TokenContract.deploymentTransaction())
            await TokenContract.waitForDeployment();//等待部署成功
            console.log("Token deployed at:", TokenContract.target);//合约地址
            # 调用合约的方法和交互
            console.log(`合约代号: ${await TokenContract.name()`)
            console.log(`合约代号: ${await TokenContract.symbol()}`)
            console.log(`合约代号: ${await TokenContract.totalSupply()}`)
            const addr1="0xxxxxxx"
            const tx= await TokenContract.transfer(addr1,1);//向addr1转1
            console.log("Transfer Transaction:", tx);
            const balance = await TokenContract.balanceOf(addr1);//查看addr1余额
            console.log("Balance:", balance.toString());
            console.log(await TokenContract.balanceOf(signer))//查看signer余额
}catch (error) {
    console.log(error)
 }
}

Wallet钱包类

创建钱包

说明:Wallet是Singer的子类,Singer的抽象类不能直接实例化

  • 随机钱包
const Wallet=ethers.Wallet.createRandom();
# 公钥
console.log("Address:", Wallet.address);
# 私钥
console.log("Private Key:", Wallet.privateKey);
# 助记词
console.log("Mnemonic:", Wallet.mnemonic.phrase);
  • 用私钥创建wallet对象

 const private=new ethers.JsonRpcProvider("http://127.0.1:8545");
 const privateKey = "acxxxxxxxxx";//私钥
 const wallet = new ethers.Wallet(privateKey,private);
 console.log("Wallet address:", wallet);//钱包公钥
  • 从助记词创建wallet对象

# 助记词
const Mnemonic="quote shy web universe book wheat turtle cabin degree permit beach capable";//12个不同的单词()
const wallet3 = ethers.Wallet.fromPhrase(Mnemonic);
console.log("Wallet3:", wallet3);
console.log("Address:", wallet3.address);
console.log("Private Key:", wallet3.privateKey);
console.log("Mnemonic:", wallet3.mnemonic.phrase);
  • 通过JSON文件创建wallet对象

如何生成Keystore V3 文件格式(以太坊钱包加密 JSON 文件)
async function creactKeystoreV3() {
   const wallet = ethers.Wallet.createRandom();
// 2. 加密为 Keystore V3 JSON
const password="xxxxxx";//密码
const json = await wallet.encrypt(password);
// 3. 保存到文件
require("fs").writeFileSync("keystore.json", json);
   console.log("JSON:", json);
}
creactKeystoreV3();//会在当前页面生成一个keystore.json文件

Keystore V3创建wallet
async function creatWalletJson() {
console.log("开始读取json文件");
const json=require("fs").readFileSync("keystore.json", "utf8");
const password="xxxxxx"
const walletJson =await ethers.Wallet.fromEncryptedJson(json, password);//json文件和密码
console.log("Wallet from JSON:",walletJson);
console.log("Address:", walletJson.address);//钱包地址
console.log("Private Key:", walletJson.privateKey);//私钥
console.log("Mnemonic:", walletJson.mnemonic.phrase);//助记词
}
creatWalletJson();
转账

async function sendTransactionFn() {
const private=new ethers.JsonRpcProvider("http://127.0.1:8545");
const privateKey = "xxx";//私钥
const wallet = new ethers.Wallet(privateKey,private);
 const address1="0xxxxx";//账号1公钥
const tx={
    to:address1,
    value:ethers.parseEther("100"),//转移100eth
}//
//执行交易
const txRes = await wallet2.sendTransaction(tx);
    const receipt = await txRes.wait() // 等待链上确认交易
    console.log(receipt) // 打印交易的收据
}
sendTransactionFn();
// 把公钥导入MetaMask插件中导入在账号可以查看账号余额的变化

Utils工具类

工具方法典型用途示例代码
parseUnits/formatUnits处理代币单位转换(如 ETH ↔ Wei)ethers.parseUnits("1.5", 18)
keccak256生成哈希(如事件签名、地址校验)ethers.keccak256("0x1234")
toChecksumAddress校验并标准化地址格式ethers.getAddress("0x...")
hexlify/hexStripZeros处理十六进制字符串ethers.hexlify([1, 2, 3])
randomBytes生成安全随机数ethers.randomBytes(32)

总结

通过上述实践,我们系统梳理了 ethers.js 核心模块,在开发中的典型应用场景与最佳实践,为高效构建区块链交互提供了可复用的技术方案。