web3实战项目 - hardhat框架
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、目标?
- 步骤总览
- 环境准备(本地)
- 初始化项目 & 依赖
- 二、编写智能合约
- 测试你的合约
- Solidity 测试
- TypeScript 测试
- 编写脚本与网络交互
- 部署合约
- 总结
前言
我把 从零到可运行的 ERC-721 收藏品 + 简单固定价市场(marketplace) 的完整「一步一步」实操指南(含可复制的命令与示例代码)。适合初学者用 Hardhat + OpenZeppelin + ethers.js 在 Sepolia 测试网 上练手(因为 Goerli 已逐步退役,建议用 Sepolia)。
提示:以下是本篇文章正文内容,下面案例可供参考
一、目标?
用 OpenZeppelin 实现一个简单的 ERC-721 收藏品合约(可 mint 带 metadata 的 NFT),再做一个简单的固定价 marketplace 合约(list / buy / cancel / withdraw),并演示如何在 Sepolia 上部署和用前端或脚本交互。
步骤总览
- 环境准备(Node、Hardhat、MetaMask、RPC),node建议最新版本22的版本
- 新建 Hardhat 项目并安装依赖
- 写 ERC-721 合约(MyCollectible)
- 写简单 Marketplace 合约(NftMarketplace,使用 pull payments &
ReentrancyGuard) - 编写部署脚本并在本地 / Sepolia 部署
- 测试:mint → approve → list → buy → withdraw(示例脚本 + 前端调用)
- 注意事项(metadata 存储、审计、生产上要改的点)
环境准备(本地)
- 安装 Node.js(建议使用 LTS,≥18)和 npm/yarn。
- 安装 MetaMask 浏览器扩展并创建钱包(切换到 Sepolia 测试网并申请测试 ETH)。(Sepolia 是当前推荐的测试网。)
- 注册并配置一个 RPC 提供商(Alchemy / Infura / QuickNode),拿到 Sepolia RPC URL(放到
.env)。
初始化项目 & 依赖
执行如下命令
mkdir nft-marketplace
cd nft-marketplace
npm init -y
npm install --save-dev hardhat
npx hardhat --init
出现如下界面说明安装成功
我们在选择的时候会遇到一个选项
? What type of project would you like to initialize?
❯ A TypeScript Hardhat project using Node Test Runner and ViemA TypeScript Hardhat project using Mocha and Ethers.js
- Node Test Runner + Viem
·· Node.js 新的测试运行器(而不是 Mocha)。
··交互库是 Viem(替代 Ethers.js,API 不同,学习成本更高)。 - Mocha + Ethers.js
··使用经典的 Mocha 测试框架。
–使用 Ethers.js,几乎所有教程(包括我给你写的脚本)都是基于 Ethers.js。
建议选择:Node Test Runner + Viem
生成完成后可以看到
contracts/
scripts/
test/
hardhat.config.ts
二、编写智能合约
使用 Hardhat 编写智能合约非常简单,只需在contracts
目录中编写一个 Solidity 文件即可。例如,您的contracts/Counter.sol
代码如下所示:
pragma solidity ^0.8.28;contract Counter {uint public x;event Increment(uint by);function inc() public {x++;emit Increment(1);}function incBy(uint by) public {require(by > 0, "incBy: increment should be positive");x += by;emit Increment(by);}
}
Hardhat 会自动检测它,并根据其pragma声明和你的 Hardhat 配置,使用正确版本的 Solidity 进行编译。你只需运行以下命令:
npx hardhat build
测试你的合约
测试是任何以太坊项目的关键部分。Hardhat 允许您使用Solidity和TypeScript编写测试,让您可以灵活地根据具体情况选择合适的工具。
安全帽测试针对本地内存区块链运行,这比使用真实网络快得多,并且不需要您花费 ETH 或获取测试网络代币。
Solidity 测试
Hardhat 3 完全支持编写 Solidity 测试。示例项目包含一个 Solidity 测试文件,位于contracts/Counter.t.sol:
import { Counter } from "./Counter.sol";
import { Test } from "forge-std/Test.sol";contract CounterTest is Test {Counter counter;function setUp() public {counter = new Counter();}function test_InitialValue() public view {require(counter.x() == 0, "Initial value should be 0");}function testFuzz_Inc(uint8 x) public {for (uint8 i = 0; i < x; i++) {counter.inc();}require(counter.x() == x, "Value after calling inc x times should be x");}function test_IncByZero() public {vm.expectRevert();counter.incBy(0);}
}
执行下面的命令可以测试所有的合约
npx hardhat test
如果您只想运行 Solidity 测试,则可以使用以下命令:
npx hardhat test solidity
当您运行此命令时,Hardhat 将:
- 编译您的合同和测试。
- 收集所有测试文件。包括目录.t.sol中的所有文件
contracts
/以及目录.sol中的所有文件test/。 - 部署这些文件中定义的每个测试合同。
- 调用每个以 开头的函数test。如果任何调用被撤销,则相应的测试将被标记为失败。
在上面的例子中:
test_InitialValue
是单元测试:它们不带参数,并且每次测试执行时运行一次test_IncByZero。testFuzz_Inc
是一个模糊测试:由于它接受一个参数,Hardhat
会使用随机输入多次运行它。如果任何一次运行失败,则模糊测试失败,并打印失败的输入。- 如果您的任何测试失败,Hardhat 将提供详细的Solidity
堆栈跟踪,以帮助您了解原因。要查看它们的实际效果,请首先注释掉vm.expectRevert();以下行test_IncByZero:
function test_IncByZero() public {// vm.expectRevert();counter.incBy(0);
}
Failure (1): test_IncByZero()
Reason: revert: incBy: increment should be positiveat Counter.incBy (contracts/Counter.sol:15)at CounterTest.test_IncByZero (contracts/Counter.t.sol:30)
TypeScript 测试
Solidity 测试非常适合快速、集中的单元测试,但在某些情况下却存在不足:
- 复杂的测试逻辑,其中像 TypeScript 这样的通用语言比 Solidity 更具表现力和人体工程学。
- 需要真实区块链行为的测试,例如区块推进或 Gas
成本计算。虽然作弊码可以在一定程度上模拟这些行为,但过度模拟难以维护,并且可能导致不准确的假设。 - 端到端场景,您希望测试您的合同在生产中的行为,涉及多个交易、客户端和用户交互。
为了支持这些用例,Hardhat 允许您使用 TypeScript(或 JavaScript)编写测试,使用
Node.js 测试运行器
或其他框架,例如
摩卡
这些测试在真实的 Node.js 环境中运行,并通过 JSON-RPC 与您的合约进行交互,使其更能代表实际使用情况。
示例项目附带一个 TypeScript 测试文件,位于test/Counter.ts,其中包含以下测试:
it("The sum of the Increment events should match the current value", async function () {const counter = await viem.deployContract("Counter");// run a series of incrementsfor (let i = 1n; i <= 10n; i++) {await counter.write.incBy([i]);}const events = await publicClient.getContractEvents({address: counter.address,abi: counter.abi,eventName: "Increment",fromBlock: 0n,strict: true,});// check that the aggregated events match the current valuelet total = 0n;for (const event of events) {total += event.args.by;}assert.equal(total, await counter.read.x());
});
该测试部署Counter合约,incBy多次调用(每次在单独的交易中),收集所有发出的Increment事件,并验证它们的总和是否与合约的最终价值相匹配。
用 Solidity 编写同样的测试也是可行的,但不太方便,而且测试会在不同的上下文中执行——更接近于单个交易多次调用合约,而不是不同用户随时间推移与其交互。这使得 TypeScript 更适合那些依赖于实际交易流程或区块链行为的场景。
要运行 TypeScript 测试,请使用以下test nodejs任务:
npx hardhat test nodejs
编写脚本与网络交互
Hardhat 中的脚本只是一个 TypeScript 或 JavaScript 文件,可以访问您的合约、配置以及 Hardhat 提供的任何其他功能。您可以使用它们来运行自定义逻辑或自动化工作流程。
按照惯例,脚本位于scripts/目录中。您可以随意命名它们,并使用.ts或.js扩展名。
示例项目包含两个脚本。其中一个脚本scripts/send-op-tx.ts展示了如何模拟本地类似 Optimism 的网络并在其上发送交易。
要运行脚本,您可以使用以下run任务:
npx hardhat run scripts/send-op-tx.ts
部署合约
示例项目附带我们的官方部署解决方案:Hardhat Ignition,一个用于部署智能合约的声明式系统。
使用 Hardhat Ignition,您可以定义要部署的智能合约实例以及要对其执行的任何操作。这些定义被分组到 Ignition 模块中,然后以最高效的方式进行分析和执行。这包括并行发送独立交易、从错误中恢复以及恢复中断的部署。
Ignition 模块位于ignition/modules/
目录中。这是示例模块ignition/modules/Counter.ts:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";export default buildModule("CounterModule", (m) => {const counter = m.contract("Counter");m.call(counter, "incBy", [5n]);return { counter };
});
npx hardhat ignition deploy ignition/modules/Counter.ts
总结
本文介绍了如何从零开始开发一个ERC-721 NFT收藏品合约和简单的固定价市场(marketplace),并部署到Sepolia测试网。主要内容包括:环境准备(Node.js、Hardhat、MetaMask)、项目初始化、编写ERC-721合约和Marketplace合约、部署流程、测试方法(Solidity和TypeScript测试)以及交互演示。文章提供了详细的步骤指南和代码示例,适合初学者学习使用Hardhat、OpenZeppelin和ethers.js进行区块链开发。特别强调了测试的重要性,并比较了Solidity测试和TypeScript测试的不同适用场景。