直说结论:这个标题里的“以太网测试网络”,准确叫法是“以太坊测试网络”。不只是读者经常混用,我在本地开发群里也见过不少人把“以太坊”打成“以太网”,意思大家都懂,但写配置的时候还是得按 Etherum Testnet 来查资料,不然搜索方向就偏了。今天就把 Hardhat 连接测试网络这件事一次讲透。
这篇内容适合谁看?刚写完第一个智能合约、想把合约从本地部署到公共测试网络的开发者,或者已经试过npx hardhat run但总报错、不知道去哪查问题的人。整个过程不复杂,核心就是三件事:拿到一个可用的 RPC 地址、准备一个测试账户的私钥、把这两样写进hardhat.config.js。但实际操作中,几乎每个人都在私钥管理、测试币领取、网络配置这些环节上踩过坑。我把这些坑全部列出来,并给出可以直接照抄的配置方案。
1. 先搞清楚连接测试网络这件事的本质
1.1 本地开发时 Hardhat 默认在做什么
很多新手对 Hardhat 有一个误解:以为它在某个数据中心帮你跑了一条区块链。其实不是。Hardhat 默认使用的hardhat network是一个完全内存态的本地模拟链,连磁盘持久化都没有。你执行npx hardhat node时,它只是在你的机器上临时起了一个开发节点,监听本机的 8545 端口,所有交易、区块、合约状态都在 RAM 里。
这意味着两点。第一,本地链的速度极快,不需要等待真实的区块确认,非常利于调试。第二,本地链上的一切都是假的,换台机器或者重启进程,数据就没了,更没法让其他人访问你的合约。所以当你需要模拟“真实使用环境”、需要让前后端同时接入同一个合约实例、需要验证合约在真实网络条件下的行为时,就必须把 Hardhat 指向一条真正运行中的测试网络。
1.2 连接测试网络本质上是替换 RPC 端点
搞懂配置之前,先理解一个抽象概念:RPC 端点。区块链网络对外提供服务的方式就是一个 JSON-RPC 接口地址,比如http://localhost:8545指向本地链,而 Sepolia、Holesky 这些测试网络各有各的公共 RPC 地址。Hardhat 的networks配置项,本质上就是一张“连接清单”,告诉它:在哪个任务里、连哪个 RPC 地址、用哪个账户签名。
连接测试网络并不神秘,它和你把 MetaMask(这里就泛指浏览器钱包插件吧)的链配置从“本地网络”切换到“Sepolia”是同一个原理。钱包通过 RPC 地址去读链上数据、广播交易,Hardhat 做的事情也一样,只是它多了一个维度:你可以通过--network参数在多个网络之间切换,不需要改代码。
1.3 测试网络选哪个:Sepolia、Holesky 还是本地节点
平时群里被问得最多的就是“我该用 Goerli 还是 Sepolia”。Goerli 和 Sepolia 都曾是以太坊的重要测试网络,但 Goerli 已经停止维护了,新项目就不建议再用。目前最主流的公共测试网络是 Sepolia,另外还有一个较新的 Holesky,主要面向共识层测试和大型验证者场景。
我根据自己的使用体验整理了一张对比表:
| 测试网络 | Chain ID | 定位 | 使用体验 | 当前推荐度 |
|---|---|---|---|---|
| Sepolia | 11155111 | 通用应用层测试 | 水龙头多、节点服务商全支持、出块稳定 | 首选 |
| Holesky | 17000 | 共识与质押相关测试 | 面向验证者,普通合约开发也可用 | 按需选择 |
| 本地 Hardhat 节点 | 31337 | 开发调试 | 速度快但环境完全隔离 | 日常开发 |
| Goerli | 5 | 已弃用 | 水龙头大多失效,不建议再用 | 不推荐 |
选网络的标准其实就三条:你的节点服务商是否支持、水龙头能不能领到币、你参考的教程是否基于这个网络。Sepolia 目前在三项上表现最均衡,我自己也把默认测试网络从 Goerli 换到了 Sepolia。
2. 上手前你需要备好的材料
2.1 RPC 地址是怎么来的
连接测试网络的第一件事是搞到一个稳定可用的 RPC 地址。RPC 地址的来源主要有两类:商业节点服务商和公共免费 RPC。
商业节点服务商一般需要注册账户、创建一个项目,然后拿到一个形如https://xxx.io/v3/项目ID的专属地址。这类地址的优点是很稳定,有请求量配额,对于部署合约和批量查询足够用。免费公共 RPC 不用注册,但它是共享的,经常遇到限流,部署合约时偶尔会返回 “rate limit exceeded”。如果你是新手、只是练习部署一两个合约,先用免费公共 RPC 完全够;如果后面要跑自动化测试脚本,建议换商业服务商。
这里额外提醒一句:能不能连上公共 RPC,取决于当前网络的连通性。如果你的机器访问某些 RPC 地址不稳定,换一个服务商或者换一个网络出口往往比反复重试更有效。
2.2 私钥和钱包地址
Hardhat 连接测试网络后,广播交易需要一个签名者(Signer)。签名者由私钥派生。你可以在钱包插件里创建或导入一个专门用于测试的账户,导出它的私钥。私钥就是 64 位十六进制字符串,通常以0x开头。
关于私钥,有两件事必须强调。第一,永远不要用主网账户的私钥来做测试。测试网不涉及真实资金,但如果你用的是主网账户,一旦配置里填错网络、脚本误执行,后果是真实的,这不是开玩笑。第二,不要直接硬编码私钥在hardhat.config.js里,而是通过环境变量引入。原因见后面的配置小节,这里先记住结论。
2.3 测试币
测试网络不收真钱,但你发起交易需要支付 Gas,Gas 要用对应网络的测试币。Sepolia 测试币通过水龙头网站领取,一般流程是:输入你的测试网收款地址、完成一个人机验证,等几分钟,测试币就打过去了。
领测试币最常见的坑有两个。一是水龙头网站对单日领取量有限制,领太多次会拒绝;二是少数水龙头要求你在主网持有一定份额的质押证明,新手没有这个条件,那就换一个不要求该条件的水龙头。实测下来,多准备两个水龙头备用地址,比卡在一个网站上死磕要省时间得多。
3. 配置 Hardhat 连接测试网的完整步骤
3.1 初始化项目与安装依赖
假设你已经有一个本地项目,或者从零开始新建一个。从零新建的话,在空目录里执行:
mkdir hardhat-testnet-demo cd hardhat-testnet-demo npm init -y npm install --save-dev hardhat然后初始化一个 Hardhat 项目:
npx hardhat初始化向导会让你选择项目类型。新手建议选 “Create a JavaScript project”,它会帮你生成contracts、scripts、test目录和一份默认的hardhat.config.js。如果你已经有现成项目,这一步可以跳过。
接着安装后续要用到的两个库:ethers 和 dotenv。Hardhat 2.x 默认内置了 ethers 插件,但如果你要显式使用它的 API,还是确保依赖存在更稳妥。
npm install --save-dev @nomicfoundation/hardhat-toolbox npm install --save-dev dotenvhardhat-toolbox是一个聚合包,把 ethers、chai、hardhat-deploy 等常用测试开发组件都装好了。用聚合包的好处是不用一个个去匹配版本,减少依赖冲突的概率。
3.2 用环境变量管理敏感信息
很多教程里会直接让你在hardhat.config.js里写:
module.exports = { networks: { sepolia: { url: "https://...", accounts: ["你的私钥"] } } };这样写确实能跑通,但有个隐患:如果你的项目推到代码托管平台,或者把配置文件分享给别人,私钥就泄露了。私钥泄露的后果是账户被清空,哪怕是测试币,也会影响你后续所有操作。
正确的做法是用 dotenv 加载.env文件:
touch .env然后把敏感内容写入.env:
SEPOLIA_RPC_URL=https://your-rpc-provider.example.com/v3/your-project-id PRIVATE_KEY=0x你的64位私钥同时在.gitignore里确认包含.env。然后用下面这份hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || ""; const PRIVATE_KEY = process.env.PRIVATE_KEY || ""; module.exports = { solidity: "0.8.24", networks: { sepolia: { url: SEPOLIA_RPC_URL, accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], chainId: 11155111, }, }, };这样配置之后,代码里不会出现任何真实私钥。把项目分享出去,别人拿到配置文件也只是一个空壳。这个习惯一定要从第一天就养成,不要在本地调试阶段偷懒。
3.3 关键字段逐个拆解
hardhat.config.js里的networks对象是核心。我逐个说一下每个字段的含义,避免你只是照着抄但心里没底。
url字段是 RPC 地址。Hardhat 发起的每一次读链、广播交易都会请求这个地址。这个字段填错了,最常见的报错是ECONNREFUSED或CONNECTION TIMEOUT,都指向网络层问题。
accounts字段是一个私钥数组。Hardhat 会用数组里的每个私钥去派生账户,部署交易时按顺序取用。你可以传多个私钥进去,代表一次配置中管理多个账户。注意私钥字符串必须带0x前缀,并且是 32 字节的十六进制格式,少一位多一位都会报错。
chainId字段用来标识区块链网络。Sepolia 的 Chain ID 是 11155111,Holesky 是 17000。为什么不填也能跑?因为很多 RPC 服务端会返回自己的 chainId,Hardhat 多数情况下不强制校验。但建议显式写上,因为做多网络切换时,这个字段能帮你快速发现配置是否复制错了网络。
gas和gasPrice是可选的。如果你不设置,Hardhat 会通过 RPC 自动估算 Gas 价格。测试网络手续费极低,通常不用手动指定。不过我后面会讲到一个不设置可能踩的坑,尤其是当某些测试网络的 Gas 估算接口不稳定时。
3.4 验证配置是否生效
配置写完后,先不要急着部署合约,先用一行命令验证连通性。Hardhat 自带 console 模式,可以直接连上远程网络执行代码:
npx hardhat console --network sepolia进入 console 后,执行:
const provider = ethers.provider; (await provider.getBlockNumber()).toString();如果能看到一个比较大的区块高度数字,比如 600 万以上,说明你的 RPC 连接是通的。如果这步就报错,后面部署合约也没必要继续,先解决网络问题再说。
这一步的价值是隔离问题:把“RPC 地址可用性”和“合约部署脚本错误”两个变量分开排查。我在团队里帮人看问题,基本都会让先跑这一句,80% 的连接问题都能定位到是地址错误还是网络不可达。
4. 部署合约到测试网的完整流程
4.1 准备一个最小合约
连接网络本身不是目的,把合约部署上去才是。写一个最经典的存储合约作为示例:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract SimpleStorage { uint256 private storedNumber; function setNumber(uint256 _num) public { storedNumber = _num; } function getNumber() public view returns (uint256) { return storedNumber; } }放在contracts/SimpleStorage.sol。这个合约足够简单,适合拿来验证整个链路是否畅通。部署成功后,你可以通过测试网浏览器看到它,也可以用脚本去调用getNumber确认读取没问题。
4.2 编写部署脚本
在scripts目录下创建deploy.js:
const hre = require("hardhat"); async function main() { const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage"); const simpleStorage = await SimpleStorage.deploy(); await simpleStorage.waitForDeployment(); console.log("SimpleStorage deployed to:", simpleStorage.target); } main().catch((error) => { console.error(error); process.exitCode = 1; });这里有一个版本相关的细节需要注意。如果你用的是 Hardhat 2.x 的某些老版本,waitForDeployment可能不存在,旧写法是:
await simpleStorage.deployed();两种写法都有人用。如果你执行时遇到waitForDeployment is not a function,就把那行改成deployed()。反过来,如果你在新版本里用了老写法,可能会收到弃用警告,建议按项目里 Hardhat 的实际版本来。判断方法是查看node_modules/hardhat/package.json里的version字段。
simpleStorage.target在新版 ethers 里表示合约地址,老版本用的是simpleStorage.address。这两个 API 的差异也是新手最容易报错的地方。如果打印出来是undefined,就换成另一个字段试试。
4.3 执行部署并核对结果
执行部署命令,关键在于必须带上--network参数:
npx hardhat run scripts/deploy.js --network sepolia不带--network的后果是:任务会默认跑在本地 hardhat 网络上。部署出来的合约只有一个本地内存地址,过一会儿进程退出,地址就永远找不到了。这不是段子,我见过很多人部署完看到返回地址,就以为成功了,第二天再查什么都没有。
部署成功后,日志会输出一个合约地址。用这个地址去测试网的区块浏览器搜索,能看到对应的交易记录。建议部署完自己再执行一次读取函数验证:
npx hardhat console --network sepolia进去之后用合约地址和 ABI 调用:
const contract = await ethers.getContractAt("SimpleStorage", "部署返回的合约地址"); (await contract.getNumber()).toString();返回0是正常的,因为还没来得及调用setNumber。如果你想顺手验证写入,执行:
const tx = await contract.setNumber(42); await tx.wait(); (await contract.getNumber()).toString();能返回42,就说明这个合约在测试网络上是真实可用的,整个过程就打通了。
5. 领测试币与常见问题排查
5.1 测试币怎么领更顺利
测试币领取是整个流程里最看运气的一环。以 Sepolia 为例,水龙头的需求是输入一个账户地址,然后发放少量测试币。但不同的水龙头有不同的规则,有的要求绑定账户名,有的隔 24 小时限领一次,有的直接要求你主网持有历史记录。
我个人的经验是:先找面向普通开发者的第三方水龙头,通常不需要额外条件,输入地址就能领;领到之后先小额测试一笔交易再部署合约,不要一上来就部署大合约。因为测试币额度本来就少,如果部署时 Gas 估算过高导致交易失败,浪费的是你宝贵的测试币余额。
如果不小心把测试币用完了又不满足水龙头条件,还有一个备选思路:查看一些服务商提供的免费测试币通道。不同项目的水龙头常常彼此独立,多试几个,总有一个能领到。千万不要去借别人的私钥代领,安全红线不要碰。
5.2 连接类问题速查表
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
ECONNREFUSED | RPC 地址不可达 | 检查 url 是否拼错、服务商是否欠费 |
CONNECTION TIMEOUT | 网络访问 RPC 不稳定 | 换一个 RPC 节点或检查网络配置 |
rate limit exceeded | 公共 RPC 被限流 | 换商业节点服务,或等待配额恢复 |
could not detect network | chainId 不匹配 | 确认networks里的 chainId 是否对应所选网络 |
invalid argument: 0x... | 私钥格式错误 | 检查私钥是否 64 位、以 0x 开头,是否有空格 |
nonce has already been used | 交易重复或缓存问题 | 查看后文 nonce 处理技巧 |
5.3 交易类问题处理
连接通了,部署却失败,通常能归为三类:余额不足、nonce 混乱、Gas 估算失败。
余额不足的报错一般是insufficient funds for gas * price + value。解决办法是再领一次测试币,别无他法。这里有一个小技巧:在部署前先用地址在区块浏览器里查一下当前余额,避免脚本在余额不足的报错上反复重试拖时间。
nonce 报错是另一类经典问题。nonce 是每个账户发起交易的序号,从 0 开始递增。如果你重复发起交易、或者部署脚本中途崩掉重跑,都可能拿到一个已经被用过的 nonce,从而被网络拒绝。最简单的处理办法是重启 Hardhat console 或者等待几分钟,因为 auto-nonce 机制通常会自动同步最新时序。如果持续报错,可以在部署脚本里手动把 nonce 设置为下一个待用序号:
const account = simpleStorage.runner.address; const nonce = await ethers.provider.getTransactionCount(account, "pending"); const tx = await SimpleStorage.deploy({ nonce });Gas 估算失败没有余额报错那么明显,可能表现为transaction execution reverted。本质上是你部署的合约在构造函数里执行了不合法操作,或者估算接口返回异常。先用简单合约排除代码问题,再考虑是不是 RPC 服务的不稳定导致的。
5.4 几个我实际总结的避坑经验
第一,所有测试环境的配置都放在.env里统一管理,不要存在hardhat.config.js的注释里,更不要贴在公开渠道问人。哪怕只是测试币私钥,泄露后对方可以随意用你的账户部署合约、消耗余额,你排查起来非常痛苦。
第二,部署失败后不要在一分钟内连续重试。某些公共 RPC 对失败交易有短时间惩罚限制,多次重试反而被暂时封禁。等 2 分钟再试,成功率会高很多。
第三,hardhat.config.js里尽量为每个网络单独写一个配置块,不要复制粘贴上一个网络的名字然后替换地址。Chain ID 抄错是低级但最容易发生的错误,尤其是 Sepolia 和 Holesky 的数字只有最后几位不同,很容易看错。
第四,测试网络和本地开发的代码不要混在一套环境变量里。本地开发时我通常把PRIVATE_KEY留空,依赖 Hardhat 默认账户签名;测试网络部署时才把.env切到对应配置。这样能避免偶尔把测试网脚本跑到了本地网络而没察觉。
这篇内容并不是什么最新技术,但它几乎是每个合约开发者都要走过的路。连接测试网络这个操作,难的不是配置本身,而是排查思路和防错习惯。先把连接层验证通,再部署脚本,出了问题就知道该往哪个方向查。后面如果你要在这个基础上做更复杂的操作,比如多合约部署、脚本自动化,思路也基本一样,无非是在networks里多加几个配置块,在脚本里多封装几个函数而已。这个基础打好了,接下来就顺畅了。