javascript3

现在我学完了xxx…

现在我学完了https://cryptozombies.io/zh/course的Solidity: Beginner to Intermediate Smart Contracts,了解了Chainlink 和预言机,NEO, zkSync,tronBox, truffle以及js基础语法。我学了truffle, remix, fountry,hardhat,但我发现这些工具很复杂,我用remix多一点,另外三个只是在linux虚拟机中安装好了。现在我了解了做solidity开发,编写以及测试solidity智能合约一样重要。现在我还不会使用ether.js,现在发现文档好多,学习遇到了困难,不知道下一步该干什么

ChatGPT的回答

首先,恭喜你已经掌握了 Solidity 基础,学习了区块链开发的重要工具和概念!你已经学了很多内容,的确有时候工具和框架太多、文档也多,可能让你感到有点迷茫。以下是我对你当前学习阶段的建议,帮助你梳理思路,并明确下一步该做什么。

1. 巩固和实践 Solidity

你已经学过了 Solidity,接下来最重要的是通过实践来巩固知识。可以通过以下方式进行:

  • 编写和部署合约

    • 继续在 Remix 上进行智能合约的编写、测试和部署。可以尝试一些更复杂的合约,比如实现一个去中心化的代币(ERC-20)或者拍卖合约等。
    • 通过 Remix 部署到以太坊测试网(如 Rinkeby、Ropsten)进行实战练习。你可以通过测试网获取免费的测试ETH,用来模拟真实的部署环境。
  • 写更多智能合约

    • 尝试解决实际问题,比如创建一个去中心化应用(DApp)或者智能合约应用程序,应用你学到的合约和工具。
    • 持续调试、修改合约,理解 Solidity 中的细节,尤其是一些难点(如 gas 优化、安全性等)。

2. Ether.js 学习

你提到目前对 Ether.js 感到困惑,文档确实多,但它是与 Solidity 智能合约交互的关键工具之一,理解如何在前端应用中使用它将对你非常有帮助。为了避免文档的困扰,建议采取以下步骤:

  • 学习 Ether.js 的基本操作

    • 在前端项目中使用 Ether.js 与智能合约进行交互。
    • 学习如何通过 Ether.js 连接到以太坊节点、发送交易、读取区块链数据以及与部署在区块链上的智能合约进行交互。
    • 通过官方文档和教程(例如 Ether.js 文档)进行系统学习,但可以先从简单的案例入手。
  • 实践

    • 尝试编写一个小的前端应用,使用 Ether.js 连接到你的 Solidity 合约。这能帮助你深入理解如何与智能合约交互。
    • 比如创建一个简单的 DApp,用户可以通过界面与合约进行交互,执行例如转账、查询余额等操作。

3. 加深对 Truffle、Hardhat、Foundry 的理解

虽然你已经安装了这些开发工具,但如果还没有深入使用,建议逐步掌握它们的核心功能。它们是 Solidity 开发中非常强大的工具,可以帮助你进行智能合约的测试、部署和调试。

  • Truffle

    • 继续尝试使用 Truffle 来创建和测试智能合约,使用它提供的命令行工具来部署合约和执行脚本。你可以参考 Truffle 的 官方文档 来了解如何创建项目、测试合约等。
  • Hardhat

    • Hardhat 是一个更现代的开发框架,你可以使用它编写和调试智能合约。它还支持更强大的测试功能和与链的交互。可以从 Hardhat 的 官方文档 学习如何设置 Hardhat 环境,编写测试脚本。
  • Foundry

    • Foundry 是一个性能非常好的 Solidity 开发框架。可以通过 Foundry 文档 来了解如何使用 Foundry 创建合约、运行测试等。

4. 学习测试合约

你提到过 测试合约 也很重要,正确的测试能够帮助你验证合约的正确性,并在开发过程中避免错误。

  • Truffle 和 Hardhat 测试

    • 你可以通过 Truffle 或 Hardhat 编写 JavaScript 测试脚本,模拟和验证合约的功能。例如,使用 Mocha 或 Chai 测试框架对合约进行单元测试。
  • 编写单元测试

    • 学习如何使用这些工具编写单元测试,验证合约是否按预期执行。重点关注合约的功能、边界情况、权限控制、攻击防范等。

5. 加入开发者社区和项目实践

加入一些开发者社区或参与开源项目,可以帮助你在实际项目中积累经验,解决实际问题。

  • GitHub 开源项目

    • 在 GitHub 上查找和参与开源的 Solidity 项目,学习别人的代码,贡献自己的代码。
  • 参加开发者竞赛

    • 参加一些区块链开发比赛,如 Ethereum 或其他平台举办的黑客松活动,这能帮助你在实战中学习并解决问题。

6. 小结

  1. 继续深入 Solidity 和 Ether.js,通过实际项目巩固技能。
  2. 理解并使用开发工具(Truffle、Hardhat、Foundry)进行合约的编写、测试和部署。
  3. 多做练习,参与开源项目,与社区互动,解决实际问题。

学习 Solidity 和区块链开发是一项长期的过程,切忌急于求成。保持持续的实践和学习,你会逐渐掌握这些工具和技术的,并能够开发出强大且安全的智能合约和去中心化应用。

豆包的回答

结合你的基础(Solidity 初中级、Remix 熟练、工具已安装、JS 基础)和痛点(工具复杂、Ethers.js 文档多、迷茫下一步),核心思路是:简化工具栈+聚焦“Ethers.js 实操+合约测试”+ 落地小项目,不用贪多,先把“合约编写→Ethers.js 交互→测试”的闭环跑通,再逐步拓展。

以下是「3 阶段落地式学习路径」,每个阶段都有明确目标、具体任务,且完全衔接你已有的知识:

一、第一阶段:简化工具,聚焦 Ethers.js 核心(3-5 天)

目标:不用啃完 Ethers.js 文档,只学“能和你 Solid 合约交互”的核心功能,配合 Hardhat (不用深钻工具本身,只当“辅助环境”),快速实现“合约调用”。

核心动作 1:固定工具栈(弃用冗余工具,减少认知负担)
不用纠结 Truffle、TronBox,暂时只留 3 个核心工具,形成闭环:

  • 「合约编写/编译」:Remix(你最熟悉,不用换)
  • 「Ethers.js 运行环境」:Hardhat(已安装,自带 Ethers.js,不用额外配置)
  • 「链环境」:Hardhat 本地节点(不用装 Ganache,Hardhat 内置本地测试网,零配置)

核心动作 2:用 Hardhat 搭最小 Ethers.js 交互环境(1 天搞定)
Hardhat 能帮你自动管理合约 ABI、启动本地节点,避免手动复制 ABI、配置 RPC 地址的麻烦,专注 Ethers.js 代码:

步骤 1:初始化 Hardhat 项目(已安装可跳过)

1
2
3
4
mkdir solidity-ethers-demo && cd solidity-ethers-demo
npm init -y
proxychains npm install --save-dev hardhat ethers
#用proxychains是因为国内可能有墙,导致无法安装

步骤 2:把你熟悉的合约(比如 CryptoZombies 僵尸合约)复制到 Hardhat

  1. 在项目中创建文件夹:contracts/Zombie.sol
  2. 把 Remix 里写好的僵尸合约(或任意你熟悉的合约,比如简单 ERC20)复制进去(确保合约能编译通过)

步骤 3:编译合约,生成 ABI(Hardhat 自动处理)

1
npx hardhat compile  # 编译后会生成 artifacts/ 文件夹,里面有 ABI

步骤 4:启动 Hardhat 本地节点(模拟区块链)

1
npx hardhat node  # 会启动本地节点,生成 20 个测试账户(地址+私钥+1000 ETH),记住第一个账户地址

核心动作 3:写 3 个极简 Ethers.js 脚本(练会核心交互)
在项目中创建 scripts/ 文件夹,写 3 个脚本,覆盖“读合约→写合约→交易处理”,每个脚本都能直接运行,看到结果:


脚本 1:读合约(无交易费,熟悉 Provider+Contract)
比如读僵尸合约的“某个地址的僵尸数量”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// scripts/read-contract.js
const hre = require("hardhat");

async function main() {
// 1. Hardhat 自动获取合约(不用手动写 ABI 和地址,编译后直接用)
const ZombieContract = await hre.ethers.getContractFactory("Zombie");
const zombieContract = await ZombieContract.deploy(); // 部署到本地节点(或用已部署的地址)
await zombieContract.deployed();
console.log("合约部署地址:", zombieContract.address);

// 2. 调用读函数(比如 CryptoZombies 的 getZombiesByOwner)
const ownerAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9226"; // Hardhat 本地节点第一个账户
const zombieCount = await zombieContract.getZombiesByOwner(ownerAddress);

console.log(`${ownerAddress} 的僵尸数量:`, zombieCount.toString());
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

运行脚本(新开终端,本地节点保持运行):

1
npx hardhat run scripts/read-contract.js --network localhost

脚本 2:写合约(调用写函数,熟悉 Signer+交易发送)
比如调用僵尸合约的“创建僵尸”函数(写操作,需要签名):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// scripts/write-contract.js
const hre = require("hardhat");

async function main() {
// 1. 获取合约+签名者(Hardhat 自动用本地节点的第一个账户当签名者)
const ZombieContract = await hre.ethers.getContractFactory("Zombie");
const zombieContract = await ZombieContract.deploy();
await zombieContract.deployed();

// 2. 调用写函数(比如 createRandomZombie,需要传参数)
const tx = await zombieContract.createRandomZombie("我的第一个僵尸");
console.log("交易哈希:", tx.hash);

// 3. 等待交易上链
await tx.wait();
console.log("僵尸创建成功!");

// 4. 验证结果(读函数查数量)
const ownerAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb9226";
const zombieCount = await zombieContract.getZombiesByOwner(ownerAddress);
console.log(`当前僵尸数量:`, zombieCount.toString());
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

运行脚本:

1
npx hardhat run scripts/write-contract.js --network localhost

脚本 3:处理交易回执+事件(实战高频)
CryptoZombies 合约有 NewZombie 事件,我们可以从交易回执中提取僵尸 ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// scripts/handle-tx-event.js
const hre = require("hardhat");

async function main() {
const ZombieContract = await hre.ethers.getContractFactory("Zombie");
const zombieContract = await ZombieContract.deploy();
await zombieContract.deployed();

// 发送创建僵尸交易
const tx = await zombieContract.createRandomZombie("我的第二个僵尸");
const receipt = await tx.wait();

// 从事件中提取僵尸 ID(CryptoZombies 的 NewZombie 事件参数:uint256 indexed zombieId)
const newZombieEvent = receipt.logs.find(log => log.event === "NewZombie");
console.log("创建的僵尸 ID:", newZombieEvent.args.zombieId.toString());
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

运行脚本后,能看到具体的僵尸 ID,这就是实战中常用的“从事件提取关键数据”。

关键:这阶段不用学 Ethers.js 所有功能

只需要掌握 3 个核心:

  • 用 Hardhat 的 getContractFactory 快速绑定合约(不用手动处理 ABI)
  • 读函数:直接调用合约方法,不用传 gas
  • 写函数:调用后等待 tx.wait(),处理回执和事件

文档只看 Ethers.js 官网的「核心模块」:

二、第二阶段:补上“合约测试”(3-4 天)

目标:用 Ethers.js + Hardhat 写测试用例,你已经知道测试重要,现在落地到实操,测试用例是“保障合约正确”的核心,且 Hardhat 内置测试框架(Mocha+Chai),不用额外学习新工具。

核心动作 1:理解测试的核心逻辑(不用复杂,先覆盖基础场景)

测试用例主要测 3 类场景:

  1. 读函数返回正确结果
  2. 写函数执行后状态正确(比如创建僵尸后,数量+1)
  3. 异常场景(比如非管理员不能调用某个函数)

核心动作 2:写第一个测试用例(基于 CryptoZombies 合约)

在项目中创建 test/ 文件夹,新建 zombie-test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("ZombieContract", function () {
let zombieContract;
let owner; // 测试账户

// 每个测试用例执行前,部署一次合约
beforeEach(async function () {
[owner] = await ethers.getSigners(); // Hardhat 自动获取测试账户
const ZombieContract = await ethers.getContractFactory("Zombie");
zombieContract = await ZombieContract.deploy();
await zombieContract.deployed();
});

// 测试 1:读函数 - 初始僵尸数量为 0
it("should have 0 zombies initially", async function () {
const zombieCount = await zombieContract.getZombiesByOwner(owner.address);
expect(zombieCount).to.equal(0); // 断言:初始数量为 0
});

// 测试 2:写函数 - 创建僵尸后数量+1,且事件正确
it("should create a zombie and emit NewZombie event", async function () {
// 测试写函数+事件
await expect(zombieContract.createRandomZombie("测试僵尸"))
.to.emit(zombieContract, "NewZombie") // 断言事件被触发
.withArgs(owner.address, 0); // 断言事件参数(第一个僵尸 ID 是 0)

// 测试状态变化
const zombieCount = await zombieContract.getZombiesByOwner(owner.address);
expect(zombieCount).to.equal(1); // 断言数量变为 1
});
});

核心动作 3:运行测试用例,查看结果

1
npx hardhat test  # 运行所有测试用例

如果测试通过,会显示绿色的 passing;如果失败,会提示具体哪一步断言错误,你可以回到 Remix 检查合约逻辑,或修改测试用例。

关键:测试不用写得太复杂

新手阶段,每个合约写 3-5 个核心测试用例即可,重点覆盖“核心功能是否正常”“关键状态是否正确”,后续再学复杂场景(比如重入攻击测试)。

三、第三阶段:落地小项目(5-7 天)

目标:把前面学的所有知识串起来,完成一个“从合约编写→测试→Ethers.js 交互”的完整项目,形成肌肉记忆,不再迷茫。

推荐项目:简单 NFT 合约(覆盖你熟悉的知识点,且交互场景清晰)

项目需求:

  1. 编写一个极简 NFT 合约(支持铸造、查询余额、转账)
  2. 用 Hardhat + Ethers.js 写测试用例(覆盖铸造、转账、余额查询)
  3. 用 Ethers.js 写脚本:批量铸造 3 个 NFT,查询某个地址的 NFT 列表,转账一个 NFT
  4. 部署到 Sepolia 测试网(可选,体验真实链上交互)

具体步骤(跟着做就行)

步骤 1:在 Remix 写 NFT 合约(简化版 ERC721)

不用写完整 ERC721,用 OpenZeppelin 库(降低编写难度,实战中也常用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// contracts/MySimpleNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MySimpleNFT is ERC721, Ownable {
uint256 public tokenIdCounter; // NFT ID 计数器

// 构造函数:初始化 NFT 名称和符号
constructor() ERC721("MySimpleNFT", "MSN") Ownable(msg.sender) {}

// 铸造函数:只有拥有者能铸造
function mint(address to) public onlyOwner returns (uint256) {
uint256 newTokenId = tokenIdCounter++;
_safeMint(to, newTokenId);
return newTokenId;
}
}

步骤 2:在 Hardhat 中安装 OpenZeppelin 库

1
npm install @openzeppelin/contracts

步骤 3:编译合约

1
npx hardhat compile

步骤 4:写测试用例(test/my-nft-test.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("MySimpleNFT", function () {
let myNFT;
let owner, user1;

beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const MySimpleNFT = await ethers.getContractFactory("MySimpleNFT");
myNFT = await MySimpleNFT.deploy();
await myNFT.deployed();
});

// 测试:初始 tokenId 为 0
it("should start with tokenId 0", async function () {
expect(await myNFT.tokenIdCounter()).to.equal(0);
});

// 测试:拥有者能铸造 NFT,余额正确
it("should allow owner to mint NFT", async function () {
await myNFT.mint(owner.address);
expect(await myNFT.balanceOf(owner.address)).to.equal(1);
expect(await myNFT.ownerOf(0)).to.equal(owner.address);
});

// 测试:非拥有者不能铸造(异常场景)
it("should reject mint from non-owner", async function () {
await expect(myNFT.connect(user1).mint(user1.address)).to.be.revertedWith(
"Ownable: caller is not the owner"
);
});

// 测试:NFT 转账功能
it("should transfer NFT correctly", async function () {
await myNFT.mint(owner.address); // owner 铸造 NFT ID 0
await myNFT.transferFrom(owner.address, user1.address, 0); // 转账给 user1
expect(await myNFT.balanceOf(user1.address)).to.equal(1);
expect(await myNFT.ownerOf(0)).to.equal(user1.address);
});
});

运行测试:

1
npx hardhat test

步骤 5:写 Ethers.js 交互脚本(批量铸造+查询+转账)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// scripts/nft-interact.js
const hre = require("hardhat");

async function main() {
const [owner, user1] = await ethers.getSigners();

// 部署合约
const MySimpleNFT = await ethers.getContractFactory("MySimpleNFT");
const myNFT = await MySimpleNFT.deploy();
await myNFT.deployed();
console.log("NFT 合约地址:", myNFT.address);

// 1. 批量铸造 3 个 NFT 给 owner
console.log("开始批量铸造...");
for (let i = 0; i < 3; i++) {
const tx = await myNFT.mint(owner.address);
await tx.wait();
console.log(`铸造成功!NFT ID:${i}`);
}

// 2. 查询 owner 的 NFT 余额
const ownerBalance = await myNFT.balanceOf(owner.address);
console.log(`owner 余额:${ownerBalance.toString()}`);

// 3. 转账 NFT ID 1 给 user1
console.log("开始转账 NFT ID 1 给 user1...");
const transferTx = await myNFT.transferFrom(owner.address, user1.address, 1);
await transferTx.wait();
console.log("转账成功!");

// 验证转账结果
const user1Balance = await myNFT.balanceOf(user1.address);
console.log(`user1 余额:${user1Balance.toString()}`);
console.log(`NFT ID 1 的拥有者:${await myNFT.ownerOf(1)}`);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

运行脚本:

1
npx hardhat run scripts/nft-interact.js --network localhost

步骤 6:(可选)部署到 Sepolia 测试网

  1. 配置 Hardhat 测试网参数(修改 hardhat.config.js):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    require("@nomiclabs/hardhat-ethers");

    module.exports = {
    solidity: "0.8.20",
    networks: {
    sepolia: {
    url: "https://eth-sepolia.g.alchemy.com/v2/你的 Alchemy API 密钥", // 之前准备的 Alchemy 节点
    accounts: ["你的测试账户私钥"] // 测试账户私钥(不要硬编码到生产环境)
    }
    }
    };
  2. 部署合约:

    1
    npx hardhat run scripts/nft-interact.js --network sepolia
  3. 在 Sepolia Etherscan 输入合约地址,查看合约状态和交易记录。

四、避坑&心态调整(新手必看)

  1. 工具不用全精通:Truffle、TronBox 暂时放一放,先把 Hardhat+Remix+Ethers.js 用熟,这三个足够覆盖 80% 的开发场景。
  2. Ethers.js 文档不用全看:聚焦“合约交互”相关模块,遇到问题再查文档,不要一开始就啃完整文档(容易劝退)。
  3. 测试用例不用写太多:新手先覆盖核心功能,后续做复杂项目再补充异常场景、边界条件。
  4. 遇到报错不要慌:Hardhat 报错会提示具体行号,优先查“合约是否编译通过”“参数是否正确”“账户是否有权限”,其次查 Ethers.js 文档或 Stack Overflow(关键词:Ethers.js + 报错信息)。

五、后续拓展方向(完成项目后再考虑)

  1. 学习 Ethers.js 前端集成:对接 MetaMask,写一个简单网页(用 Vue/React),实现“用户点击按钮铸造 NFT”。
  2. 深入 Hardhat 插件:比如 hardhat-etherscan(合约验证)、hardhat-gas-reporter(gas 分析)。
  3. 学习 Chainlink 实操:在你的 NFT 合约中集成 Chainlink VRF,实现“随机铸造 NFT 属性”(衔接你之前学的 Chainlink 知识)。
  4. 尝试更复杂的合约:比如 ERC20+NFT 结合的合约,或简单的 DeFi 合约(如质押挖矿)。

总结

你现在的核心问题是“知识碎片化,没有形成闭环”,所以下一步不用学新东西,而是把已有的知识“串起来”:

  1. 用 Hardhat 简化 Ethers.js 学习,专注核心交互;
  2. 用测试用例巩固合约逻辑,知道“怎么验证合约正确”;
  3. 用一个小项目落地所有知识点,形成“编写→测试→交互”的完整流程。

按这个路径走,1-2 周就能摆脱迷茫,并且能独立完成“合约+交互”的开发,后续再拓展新工具、新知识点就会很顺畅!

deepseek的回答

看到你的学习历程,我能感受到你现在正处于一个从“了解概念”到“实际构建”的关键过渡期。你遇到的困难(工具复杂、文档繁多、方向模糊)是每一位区块链开发者都会经历的“成长阵痛”。别担心,我们来制定一个清晰、可执行的下一步计划。

核心问题诊断与破局思路

你的核心问题不是“学得不够”,而是缺乏一个明确、具体的项目目标来串联所有知识点。工具和库(ethers.js, Hardhat)是“器”,而项目是“术”与“道”。你要做的是:通过做一个具体的、感兴趣的项目,来驱动对工具和库的学习

我给你的建议是:暂时放下对“掌握所有工具”的执念,选定一个技术栈,围绕一个迷你项目深钻下去。


下一步行动方案:四步走战略

第一步:选定并专注一个开发框架——推荐 Hardhat

你已安装多个框架,这反而造成选择困难。我强烈推荐你将 Hardhat 作为主力,原因如下:

  • 与 Remix 互补:你熟悉 Remix 的编写和部署,Hardhat 则擅长本地测试、部署脚本和任务自动化
  • 测试体验极佳:它内置了 hardhat-chai-matchers,写测试用例非常直观,完美契合你“测试很重要”的认知。
  • 生态系统活跃:插件丰富,社区支持好,是当前最主流的专业开发框架。

具体任务

  1. 在你的Linux虚拟机中,创建一个全新的Hardhat项目:
    1
    2
    3
    4
    mkdir my-first-dapp && cd my-first-dapp
    npm init -y
    npm install --save-dev hardhat
    npx hardhat init
    选择 “Create a JavaScript project”(暂时避开TypeScript,减少复杂度)。
  2. 浏览生成的项目结构:重点关注 contracts/, scripts/, test/ 目录和 hardhat.config.js 文件。
  3. 运行 npx hardhat test,感受一下测试流程。

第二步:以项目驱动的方式学习 ethers.js

不要试图通读全部文档。带着任务去查文档,效率最高。Ethers.js 的核心就是这几件事:

  1. 连接钱包/提供商(Provider)
  2. 读取合约数据(Call)
  3. 发送交易(Send Transaction)
  4. 监听事件(Event)

迷你项目建议:构建一个“存款罐”智能合约及前端
这个项目小到可管理,但涵盖了核心流程:

  • 合约功能:一个可以接收ETH的合约,记录每个地址的存款金额,允许所有者提取。
  • 前端功能:一个网页,可以连接MetaMask,查看余额,进行存款。

分模块学习 ethers.js

  • 模块A:连接与读取

    • 任务:在Hardhat的测试脚本或一个单独的Node.js脚本中,连接本地Hardhat网络或以太坊测试网。
    • 学习点:ethers.providers.JsonRpcProvider, provider.getBalance()
    • 代码实践:编写脚本,读取测试网某个账户的ETH余额。
  • 模块B:与合约交互(只读)

    • 任务:为你编写的“存款罐”合约,写一个测试脚本,调用其“查看总存款”的函数。
    • 学习点:new ethers.Contract(address, abi, provider),调用 contract.totalDeposits()
  • 模块C:与合约交互(写入)

    • 任务:在测试脚本中,模拟一个用户向“存款罐”存款。
    • 学习点:contract.connect(signer).deposit({value: ...}),理解 signer(签名者)的概念。

关键技巧:Hardhat测试中,你可以直接使用 ethers 全局变量和 hre.ethers,无需复杂配置。从写测试用例开始学,比直接写前端更简单。

第三步:将“测试驱动开发”落到实处

你现在已经知道测试重要,现在是实践的时候。为“存款罐”合约编写完善的测试。

示例测试思路(在 test/DepositBox.js 中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { expect } = require("chai");

describe("DepositBox", function () {
it("Should accept ETH deposit", async function () {
const [owner, addr1] = await ethers.getSigners();
const DepositBox = await ethers.getContractFactory("DepositBox");
const depositBox = await DepositBox.deploy();

// 测试存款
await expect(() =>
addr1.sendTransaction({ to: depositBox.address, value: 100 })
).to.changeEtherBalance(addr1, -100); // 检查addr1余额减少

// 检查合约余额增加
expect(await ethers.provider.getBalance(depositBox.address)).to.equal(100);
});
});

通过写测试,你会被迫思考合约的所有行为边界,这是提升代码质量最快的方法。

第四步:整合与扩展——做一个简单前端

当你的合约通过测试后,可以尝试用前端与之交互。

  1. 使用 Vite 或 Create React App 快速搭建一个前端项目
  2. 在前端集成 ethers.jsnpm install ethers
  3. 实现核心功能
    • 使用 window.ethereum 检测并连接MetaMask(ethers.providers.Web3Provider)。
    • 使用连接后的 providersigner 来实例化你的合约。
    • 制作一个按钮,调用合约的 deposit 函数。

这时,你会遇到前端开发的挑战(状态管理、错误处理等),但这会将你的学习扩展到全栈DApp开发。


学习资源与心态调整建议

  1. 文档阅读法

    • ethers.js:直奔 v6文档,只看 API → ProvidersAPI → Contract 这两个核心章节。其他部分随用随查。
    • Hardhat:精读 Hardhat Tutorial,它引导你完成一个完整的任务。
  2. 社区与求助

    • 遇到具体错误时,将错误信息直接复制到 Google 或 Stack Overflow 搜索,大概率已有解决方案。
    • 在 Hardhat 和 ethers.js 的 Discord 或 GitHub Discussions 中提问。
  3. 心态管理

    • 接受“螺旋式上升”:你可能需要在“写代码 → 查文档 → 报错 → 搜索 → 解决”的循环中反复多次。这是最有效的学习过程。
    • 完成比完美重要:你的第一个项目可以很丑、效率不高,但一定要把它完整地走通:编写、测试、部署、前端交互。这个闭环的经验价值千金。

总结:你的立即行动清单

  1. 今天:用 Hardhat 创建一个新项目,部署一个简单的 “Hello World” 合约并运行测试。
  2. 本周末:完成 “存款罐” 合约及其 全部测试用例
  3. 下周:为 “存款罐” 合约编写一个最简单的前端页面,实现连接钱包和存款功能。
  4. 长期:以此为基础,构思并开发一个你真正感兴趣的、稍复杂的项目(例如:一个多签钱包、一个代币质押池)。

你已具备了扎实的基础知识,现在需要的只是将注意力从“学习所有”转移到“构建一个”上来。一旦你通过第一个小项目打通了任督二脉,所有的文档和工具都会变得亲切起来。动手开始吧!

我该如何学习 合约交互库 ether.js

学习 Ethers.js 的核心是围绕“实操”展开——先掌握“连接链→操作合约→处理交易”的固定流程,再逐步细化细节(如 gas 控制、签名安全、事件监听)。以下是一套循序渐进的学习路径,从环境搭建到实战落地,完全匹配你的核心需求:

一、前置准备:搭建最小可运行环境

Ethers.js 是 Node.js 生态库,先搞定环境,再写代码才不卡壳:

1. 基础环境安装

  • 安装 Node.js(推荐 v16+,自带 npm):官网下载
  • 初始化项目(创建文件夹,执行命令):
    1
    2
    3
    4
    5
    6
    mkdir ethers-learn && cd ethers-learn
    npm init -y # 生成 package.json
    npm install ethers # 核心库(最新版 v6,语法更简洁)
    # 可选:安装 hardhat(辅助获取合约 ABI、本地节点,对应你给的示例)
    npm install --save-dev hardhat @nomiclabs/hardhat-ethers ethers
    npx hardhat # 初始化 hardhat(选 Create an empty hardhat.config.js)

2. 关键工具准备(必备)

  • 测试网节点:本地节点(Ganache)或第三方节点(Alchemy/Infura,免搭节点)
    • 本地节点:安装 Ganache(官网),启动后会生成 10 个测试账户(带 ETH)和 RPC 地址(如 http://127.0.0.1:7545
    • 第三方节点(推荐):注册 Alchemy,创建 Sepolia 测试网项目,获取 RPC 地址(如 https://eth-sepolia.g.alchemy.com/v2/你的API密钥
  • 测试币:Sepolia 测试网领 ETH(水龙头),用于发送交易
  • 合约 ABI + 地址:找一个测试网合约(如自己部署的 NFT 合约,或公开测试合约),ABI 是 JSON 格式(合约编译后生成,Hardhat 编译后在 artifacts/contracts/xxx.sol/xxx.json 中)

二、核心概念拆解(3个核心对象,必须吃透)

Ethers.js 的所有合约交互,本质是围绕「Provider + Signer + Contract」三个对象展开,搞懂它们的分工,代码逻辑就通了:

对象 作用 核心场景
Provider 连接区块链(读链数据,无签名权限) 调用合约读函数、查区块/交易
Signer 持有私钥(签名交易,有写链权限) 发送交易、调用合约写函数
Contract 绑定 ABI + 合约地址(JS 操作合约的入口) 调用合约的读/写方法

代码示例(快速理解三者关系)

1
2
3
4
5
6
7
8
9
10
11
12
13
const { ethers } = require("ethers");

// 1. Provider:连接 Sepolia 测试网(用 Alchemy 节点)
const provider = new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/你的API密钥");

// 2. Signer:用私钥创建签名者(测试用,生产环境绝对不能硬编码私钥!)
const privateKey = "你的测试账户私钥(Ganache/Metamask 导出)";
const signer = new ethers.Wallet(privateKey, provider); // 绑定 Provider,让签名者知道连哪个链

// 3. Contract:绑定合约 ABI + 地址(示例:测试网 NFT 合约)
const contractABI = [/* 合约 ABI 的 JSON 数组,比如 mint 函数的定义 */];
const contractAddress = "0x123..."; // 测试网合约地址
const nftContract = new ethers.Contract(contractAddress, contractABI, signer); // 绑定 Signer(可写),若只读则绑定 Provider

三、分模块实操(核心需求逐一突破)

按你的核心需求拆分,每个模块写“可直接运行的代码”,多练 2 次就熟了:

模块1:连接区块链(本地/测试网)

核心是创建 Provider,支持 3 种常见场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { ethers } = require("ethers");

// 场景1:连接本地 Ganache 节点
const localProvider = new ethers.JsonRpcProvider("http://127.0.0.1:7545");

// 场景2:连接测试网(Sepolia,Alchemy/Infura)
const testnetProvider = new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/你的API密钥");

// 场景3:前端连接 MetaMask(浏览器环境,MetaMask 会注入 window.ethereum)
// if (window.ethereum) {
// const provider = new ethers.BrowserProvider(window.ethereum);
// const signer = await provider.getSigner(); // 无需硬编码私钥,MetaMask 签名
// }

// 验证连接成功:查当前区块号
async function checkConnection() {
const blockNumber = await localProvider.getBlockNumber();
console.log("当前区块号:", blockNumber); // 输出数字说明连接成功
}
checkConnection();

模块2:加载合约 ABI(2种常用方式)

ABI 是 JS 和合约沟通的“翻译器”,必须正确加载:

1
2
3
4
5
6
7
8
9
10
11
12
// 方式1:直接复制 ABI 数组(适合简单合约)
const simpleABI = [
"function mint(string calldata tokenURI) external returns (uint256)", // 写函数
"function balanceOf(address owner) external view returns (uint256)" // 读函数(view 修饰)
];

// 方式2:导入 Hardhat 编译后的 ABI(推荐,适合复杂合约)
// 假设合约叫 MyNFT.sol,编译后生成 artifacts/contracts/MyNFT.sol/MyNFT.json
const MyNFTABI = require("./artifacts/contracts/MyNFT.sol/MyNFT.json").abi;

// 方式3:从 JSON 文件导入(单独保存 ABI 为 nft-abi.json)
// const MyNFTABI = require("./nft-abi.json");

模块3:调用合约读函数(无交易费,无需签名)

读函数用 viewpure 修饰,只需要 Provider 或绑定 ProviderContract

1
2
3
4
5
6
7
8
9
10
async function readContract() {
const provider = new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/你的API密钥");
const contract = new ethers.Contract(contractAddress, MyNFTABI, provider); // 只绑定 Provider(读)

// 调用读函数:查询某个地址的 NFT 余额
const ownerAddress = "0x456...";
const balance = await contract.balanceOf(ownerAddress);
console.log("NFT 余额:", ethers.formatUnits(balance, 0)); // 余额是 uint256,转成数字
}
readContract();

模块4:调用合约写函数(需交易费,签名交易)

写函数(如 minttransfer)会修改链上数据,必须绑定 Signer,核心流程:调用函数→签名交易→发送上链→等待回执

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function mintNFT() {
// 1. 初始化 Provider + Signer + Contract
const provider = new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/你的API密钥");
const signer = new ethers.Wallet(privateKey, provider);
const nftContract = new ethers.Contract(contractAddress, MyNFTABI, signer); // 绑定 Signer(写)

// 2. 调用写函数(指定参数,可选设置 gas)
const tokenURI = "ipfs://QmXXX..."; // NFT 的元数据地址
const tx = await nftContract.mint(tokenURI, {
gasLimit: 300000, // 可选:指定 gas 上限(防止 gas 无限飙升)
gasPrice: ethers.parseUnits("20", "gwei") // 可选:指定 gas 价格(当前链上平均价格即可)
});

// 3. 等待交易上链(获取回执)
console.log("交易哈希:", tx.hash); // 可在 Etherscan 查看交易状态
const receipt = await tx.wait(); // 等待区块确认(默认 1 个确认)
console.log("交易确认!区块号:", receipt.blockNumber);
console.log("铸造的 NFT ID:", receipt.logs[0].args.tokenId); // 从事件中提取 NFT ID(需合约定义事件)
}
mintNFT();

模块5:处理交易回执(关键信息提取)

tx.wait() 会返回交易回执,包含核心信息,按需提取:

1
2
3
4
5
6
7
8
const receipt = await tx.wait();
console.log({
交易哈希: receipt.hash,
区块号: receipt.blockNumber,
消耗 gas: receipt.gasUsed.toString(), // 实际消耗的 gas
事件列表: receipt.logs, // 合约触发的事件(如 Transfer、Minted)
状态: receipt.status === 1 ? "成功" : "失败" // status=1 表示交易成功
});

四、完整实战案例(复刻你的 NFT 铸造示例)

把上面的模块串起来,写一个可直接运行的脚本(mint.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const { ethers } = require("ethers");

// 配置信息(替换成自己的)
const config = {
rpcUrl: "https://eth-sepolia.g.alchemy.com/v2/你的API密钥",
privateKey: "你的测试账户私钥",
contractAddress: "0x123...", // 你的测试网 NFT 合约地址
tokenURI: "ipfs://QmXXX..." // 你的 NFT 元数据地址
};

// 合约 ABI(只保留需要的函数,精简体积)
const MyNFTABI = [
"function mint(string calldata tokenURI) external returns (uint256)",
"event Minted(address indexed minter, uint256 indexed tokenId)" // 合约定义的事件(需和合约一致)
];

async function main() {
try {
// 1. 连接链 + 创建签名者
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
const signer = new ethers.Wallet(config.privateKey, provider);
console.log("当前钱包地址:", await signer.getAddress());

// 2. 绑定合约
const nftContract = new ethers.Contract(config.contractAddress, MyNFTABI, signer);

// 3. 发送铸造交易
console.log("开始铸造 NFT...");
const tx = await nftContract.mint(config.tokenURI, { gasLimit: 300000 });
console.log("交易哈希:", tx.hash);

// 4. 等待上链并输出结果
const receipt = await tx.wait();
console.log("NFT 铸造成功!");
console.log("区块号:", receipt.blockNumber);
console.log("铸造的 NFT ID:", receipt.logs[0].args.tokenId.toString());
} catch (error) {
console.error("铸造失败:", error.message);
}
}

main();

运行脚本:node mint.js,成功后可在 Sepolia Etherscan 输入交易哈希查看详情。

五、避坑指南(新手必看)

  1. 私钥安全:绝对不能把私钥硬编码到代码里!生产环境用:
    • 前端:通过 BrowserProvider 对接 MetaMask(用户自己签名,不暴露私钥)
    • 后端:用环境变量(process.env.PRIVATE_KEY),配合 .env 文件(安装 dotenv 库)
  2. ABI 错误:ABI 必须和合约一致,缺少函数定义或参数类型错误会直接报错(建议从 Hardhat 编译产物中导入,避免手动写错)
  3. gas 问题
    • 交易失败提示 out of gas:提高 gasLimit(比如从 200000 调到 300000)
    • gas 价格太高:用 provider.getGasPrice() 获取当前链上平均价格,动态设置:
      1
      2
      const gasPrice = await provider.getGasPrice();
      const tx = await nftContract.mint(tokenURI, { gasPrice });
  4. 节点连接失败:检查 RPC 地址是否正确(Alchemy 密钥是否有效),测试网是否正常(可换 Infura 节点备用)
  5. 合约权限:部分写函数(如 mint)可能限制仅管理员调用,确保你的签名者地址有权限

六、进阶学习方向(按需拓展)

掌握基础流程后,可补充这些实用技能:

  1. 事件监听:实时监听合约事件(如 NFT 转让、铸造):
    1
    2
    3
    nftContract.on("Minted", (minter, tokenId) => {
    console.log(`新 NFT 铸造:地址 ${minter},ID ${tokenId}`);
    });
  2. 批量交易:用 signer.sendTransaction() 批量发送交易,或用 Promise.all 批量调用合约
  3. 合约部署:用 Ethers.js 部署合约(配合 Hardhat 更简单)
  4. 前端集成:对接 MetaMask(浏览器环境),实现“用户点击按钮铸造 NFT”
  5. 错误处理:捕获交易失败(如余额不足、合约 revert),给用户友好提示

七、推荐学习资源(高效入门)

  1. 官方文档(首选):Ethers.js v6 文档(简洁清晰,示例到位)
    https://docs.ethers.org/v6/
  2. 实战教程:Alchemy 文档(含 Ethers.js 合约交互、NFT 铸造完整案例)
    https://docs.alchemy.com/docs/ethers-js-tutorial
  3. 代码示例:Ethers.js GitHub 示例库(覆盖所有核心功能)
    https://github.com/ethers-io/ethers.js/tree/main/examples
  4. 问题排查:Stack Overflow(关键词 ethers.js)、Ethers.js Discord 社区

总结

Ethers.js 学习的核心是「流程固化 + 实操验证」:

  1. 先记住「Provider(连链)→ Signer(签名)→ Contract(操作合约)」的固定流程;
  2. 用测试网反复练“读函数→写函数→交易回执”,熟悉参数和错误;
  3. 重点关注私钥安全、gas 控制、事件提取这 3 个实战高频点。

按这个路径,1-2 天就能熟练写出合约交互代码,多换几个合约(比如 ERC20 转账、NFT 转让)练习,很快就能掌握!