ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Web3.js v4 与 Hardhat 集成实战:从零搭建智能合约开发、部署与测试工作流

Web3.js v4 与 Hardhat 集成实战:从零搭建智能合约开发、部署与测试工作流 区块链Web3【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址https://gitcode.com/gh_mirrors/we/web3.js点击查看免费下载本教程以 web3.js v4 官方文档中的 Hardhat 集成指南为主体完整演示如何从空目录开始借助nomicfoundation/hardhat-web3-v4插件把 web3.js 作为 Hardhat 运行时环境HRE的一部分用于智能合约的编译、部署、测试与链上交互。读完本文你将掌握 Hardhat 项目的初始化、插件式 Web3 环境的配置、web3.eth.Contract的部署与调用.call()/.send()、Gas 估算以及handleRevert等实战技能并能把同一套代码无缝复用到本地测试网络与真实网络。背景为什么用插件方式集成 web3.js 与 Hardhat随着 Hardhat 官方插件 hardhat-web3-v4 的兼容性更新web3.js v4 现在可以作为 Hardhat 的一等公民插件直接使用。相比手动把 Hardhat 的 provider 传入new Web3(...)插件方式的最大收益在于插件会自动修改 Hardhat 运行时环境Hardhat Run-time EnvironmentHRE同时注入web3一个已绑定本地 Hardhat 网络 provider、开箱即用的实例对象和Web3类。此后无论在部署脚本、测试文件还是任务task中都可以直接import { web3 } from hardhat使用无需再手动初始化 provider。官方还提供了create-hardhat-web3命令行工具用于自动化本文中的大部分脚手架步骤快速搭建 Web3.js Hardhat 项目不过本文将从零手动完成每一步以便你理解底层机制。需要说明本仓库web3.js monorepo自身的集成测试 packages/web3/test/integration/external-providers/hardhat.test.ts 验证了 web3.js 与 Hardhat provider 的兼容性——它直接使用 Hardhat 的hardhat.network.provider作为 Web3 的 provider 完成eth_accounts、eth_blockNumber、eth_sendTransaction等基础 RPC 调用印证了这条集成路线的可行性。前置条件在开始之前请确认你已具备智能合约编写的基础知识Solidity 语法、合约部署模型JavaScript / TypeScript 的基本使用经验本机安装有Node.js 且版本大于 v16包管理器使用NPM本教程的依赖安装均通过npm完成。第一步初始化 Hardhat 项目创建一个新项目目录并进入mkdir myprojectcd myproject安装并实例化 Hardhatnpm install hardhatnpx hardhat init运行npx hardhat init后终端会展示如下交互式向导首次运行时 npx 会先询问是否下载 hardhat 包输入y确认即可在向导中选择Create a TypeScript project本教程全程使用 TypeScript后续选项如是否安装依赖等全部选择Yes当提示安装所需依赖时回复yes完成安装。第二步安装依赖并引入 hardhat-web3-v4 插件为了把 web3.js v4 接入 Hardhat通过 npm 安装插件与 web3 本体以--save-dev写入开发依赖npm install --save-dev nomicfoundation/hardhat-web3-v4 web34这一步会把 web3.js 加入到项目的node_modules目录中。接着在 Hardhat 配置文件hardhat.config.ts顶部显式导入插件import { HardhatUserConfig } from hardhat/config; import nomicfoundation/hardhat-toolbox; import nomicfoundation/hardhat-web3-v4; // const config: HardhatUserConfig { solidity: 0.8.19, }; export default config;几点关键说明新建项目时hardhat-toolbox已默认写入配置文件但web3-v4 插件必须显式导入导入后 HRE 才会被扩展插件生效后HRE 会注入Web3类与一个web3实例对象后者自带初始化好的本地 Hardhat provider可直接用于测试与部署文件需要注意web3实例内部持有的是当前 Hardhat 网络的连接。若在测试中通过hardhat.config.ts切换网络例如从hardhat内置网络切到localhost或公共测试网web3实例跟随的 provider 也会随之变化因此部署/测试脚本中直接引用import { web3 } from hardhat即可保持与当前网络一致。作为对照本仓库根目录的 hardhat.config.js 给出了一个不带插件的纯 Hardhat 配置示例solidity 0.8.17内置网络 chainId 1337可以直观看到插件引入前后的配置差异。第三步编写智能合约 Lock.solHardhat 初始化时会自带一个示例合约Lock位于myproject/contracts/Lock.sol// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; // Uncomment this line to use console.log // import hardhat/console.sol; contract Lock { uint public unlockTime; address payable public owner; event Withdrawal(uint amount, uint when); constructor(uint _unlockTime) payable { require( block.timestamp _unlockTime, Unlock time should be in the future ); unlockTime _unlockTime; owner payable(msg.sender); } function withdraw() public { // Uncomment this line, and the import of hardhat/console.sol, to print a log in your terminal // console.log(Unlock time is %o and block timestamp is %o, unlockTime, block.timestamp); require(block.timestamp unlockTime, You cant withdraw yet); require(msg.sender owner, You arent the owner); emit Withdrawal(address(this).balance, block.timestamp); owner.transfer(address(this).balance); } }Lock.sol是一个简单的时间锁合约部署时可接收任意数量的 Ether并接收一个构造参数_unlockTime赋给状态变量withdraw函数只允许被标记为owner的地址取回合约全部余额且取款时block.timestamp必须不小于unlockTime。它涵盖了构造器传参、状态变量、事件与访问控制非常适合作为 web3.js 部署与交互的练习对象。第四步编译合约并理解 artifacts 产物npx hardhat compile执行后会在项目根目录生成artifacts文件夹其中包含编译信息与编译后的合约产物。编译后 VS Code 资源管理器中的目录结构大致如下artifacts下最关键的是artifacts/contracts/Lock.sol/Lock.json它同时包含 ABIApplication Binary Interface即jsonInterface与bytecode。后续的部署与测试中我们会从这个 JSON 中取出abi和bytecode提供给 web3.js。提示教程后续的部署与测试代码均通过import artifacts from ../artifacts/contracts/Lock.sol/Lock.json引入该文件。第五步编写部署脚本并用 web3.js 部署合约修改scripts/deploy.ts先从 hardhat 导入已初始化的web3对象再获取 artifactsimport { web3 } from hardhat; import artifacts from ../artifacts/contracts/Lock.sol/Lock.json; async function main() {} // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch(error { console.error(error); process.exitCode 1; });随后在main函数内借助 web3.js 的.utils与.eth模块完成部署参数准备与合约发布async function main() { const currentTimestampInSeconds Math.round(Date.now() / 1000); const unlockTime currentTimestampInSeconds 60; const lockedAmount web3.utils.toWei(0.001, ether); const [deployer] await web3.eth.getAccounts(); const lockContract new web3.eth.Contract(artifacts.abi); const rawContract lockContract.deploy({ data: artifacts.bytecode, arguments: [unlockTime], }); const lock await rawContract.send({ from: deployer, gasPrice: 10000000000, value: lockedAmount.toString(), }); console.log( Lock with ${web3.utils.toWei( lockedAmount, ether, )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.options.address}, ); } // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch(error { console.error(error); process.exitCode 1; });运行部署命令npx hardhat run scripts/deploy.ts该命令会把Lock合约部署到 Hardhat 内置的本地区块链上——整个过程中web3.js 负责与区块链对话、广播智能合约数据。下面对脚本中用到的几个核心 API 做源码级解读web3.utils.toWei单位换算web3.utils.toWei(0.001, ether)把 0.001 ETH 换算为 wei 整数。其实现位于 packages/web3-utils/src/converters.ts#L574-L635支持字符串单位如ether与数字精度两种形式字符串单位会通过ethUnitMap查表得到 10 的幂次denomination非法单位会抛出InvalidUnitError内部先校验入参再把带小数的值拆分为integer与fraction两部分用BigInt乘以denomination后去除多余的零位避免浮点精度损失对过小 1e-15或过大 1e20的 number 入参会给出PrecisionLossWarning警告因此教程中lockedAmount的字符串处理是推荐做法。web3.eth.getAccounts获取账户web3.eth.getAccounts()返回当前节点Hardhat 内置网络自带 20 个测试账户的地址列表。在 packages/web3-eth/src/web3_eth.ts#L396-L399 中它调用底层ethRpcMethods.getAccounts拿到十六进制地址后还会逐个经过toChecksumAddress转成 EIP-55 校验和格式所以返回的地址是大写混合形式。部署脚本中const [deployer] ...取第一个账户作为交易发送方。web3.eth.Contract deploy().send()合约创建new web3.eth.Contract(artifacts.abi)创建合约实例其核心类定义在 packages/web3-eth-contract/src/contract.ts#L416。.deploy({ data, arguments })的返回对象属于DeployerMethodClass见 packages/web3-eth-contract/src/contract-deployer-method-class.ts#L53它的send()内部会通过calculateDeployParams()校验data/input不能为空否则抛出Web3ContractError(contract creation without any data provided.)并解析构造器 ABI 与参数用getSendTxParams组装交易data 编码后的构造器参数、from等调用sendTransaction在transactionResolver中检查收据receipt.status若为 0 抛出Web3ContractError(code couldnt be stored)成功后克隆合约实例并回填newContract.options.address receipt.contractAddress。因此在await rawContract.send({...})之后lock就是绑定了链上地址的合约实例可直接通过lock.options.address取得部署地址。关于 handleRevert部署与调用失败排查测试/部署中常会遇到交易失败但原因不明的情况。web3.js v4 提供了handleRevert开关开启后sendTransaction在交易回滚时会尝试解析并返回 revert 原因字符串。该配置定义在 packages/web3-core/src/web3_config.ts#L35默认值为false可通过lockContract.handleRevert true开启。需要留意的是文档与源码均注明目前handleRevert仅对sendTransaction生效不支持sendSignedTransaction。第六步编写测试并交互.call 读状态 / .send 改状态部署只是第一步接下来要验证合约行为是否符合预期。由于此前是用 web3.js 广播并保存数据现在仍使用同一套协议来查看与修改数据。将myproject/test/Lock.ts的内容替换为import { time, loadFixture } from nomicfoundation/hardhat-toolbox/network-helpers; import { expect } from chai; import { web3 } from hardhat; import artifacts from ../artifacts/contracts/Lock.sol/Lock.json; describe(Lock, function () { async function deployOneYearLockFixture() { const ONE_YEAR_IN_SECS 365 * 24 * 60 * 60; const ONE_GWEI 1_000_000_000; const lockedAmount ONE_GWEI; const unlockTime (await time.latest()) ONE_YEAR_IN_SECS; const lockContract new web3.eth.Contract(artifacts.abi); lockContract.handleRevert true; const [deployer, otherAccount] await web3.eth.getAccounts(); const rawContract lockContract.deploy({ data: artifacts.bytecode, arguments: [unlockTime], }); // To know how much gas will be consumed, we can estimate it first. const estimateGas await rawContract.estimateGas({ from: deployer, value: lockedAmount.toString(), }); const lock await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: 10000000000, value: lockedAmount.toString(), }); console.log(Lock contract deployed to: , lock.options.address); return { lock, unlockTime, lockedAmount, deployer, otherAccount, rawContract }; } describe(Deployment, function () { it(Should set the right unlockTime, async function () { const { lock, unlockTime } await loadFixture(deployOneYearLockFixture); const setTime await lock.methods.unlockTime().call(); console.log(SetTime, setTime); expect(setTime).to.equal(unlockTime); }); it(Should set the right deployer, async function () { const { lock, deployer } await loadFixture(deployOneYearLockFixture); expect(await lock.methods.owner().call()).to.equal(deployer); }); it(Should receive and store the funds to lock, async function () { const { lock, lockedAmount } await loadFixture(deployOneYearLockFixture); const balance await web3.eth.getBalance(String(lock.options.address)); expect(balance).to.equal(lockedAmount); }); it(Shouldnt fail if the unlockTime has arrived and the deployer calls it, async function () { const { lock, unlockTime, deployer } await loadFixture(deployOneYearLockFixture); await time.increaseTo(unlockTime); await expect(lock.methods.withdraw().send({ from: deployer })).not.to.be.reverted; }); }); });运行测试npx hardhat test test/Lock.ts预期输出与下图类似/* Lock Deployment Lock contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 SetTime 1739193193n ✔ Should set the right unlockTime (884ms) ✔ Should set the right deployer (54ms) ✔ Should receive and store the funds to lock ✔ Shouldnt fail if the unlockTime has arrived and the deployer calls it */测试中的关键点拆解部署前的 Gas 估算deployOneYearLockFixture()复用了部署脚本中的 ABI bytecode 准备逻辑但在send之前先调用rawContract.estimateGas({ from, value })估算构造器执行所需 Gas再把它显式传给send的gas字段。estimateGas是DeployerMethodClass的公开方法见 contract-deployer-method-class.ts#L185-L197它最终委托给contractMethodEstimateGas执行eth_estimateGasRPC返回的估算值以字符串形式传入避免大数精度问题。读状态用.call()要读取链上owner数据使用已部署合约实例的lock.methods.owner().call()。.call()只做本地模拟执行、不改变链上状态因此无需钱包签名也不消耗 Gas。改状态用.send()要修改此前保存的数据如触发withdraw必须通过lock.methods.withdraw().send({ from: deployer })向网络广播交易。使用.send()时必须在from字段显式提供交易发送方本例为deployer账户地址否则无法确定签名账户。开启 handleRevert 便于断言lockContract.handleRevert true让回滚交易携带 revert 原因配合nomicfoundation/hardhat-toolbox/network-helpers的time.increaseTo把区块时间推进到unlockTime之后再用await expect(...).not.to.be.reverted断言withdraw不再失败。与仓库集成测试的呼应本仓库在 packages/web3/test/integration/external-providers/hardhat.test.ts 中做了类似的端到端验证以hardhat.network.provider作为 provider 初始化new Web3(provider)依次完成getAccounts、getBlockNumber、sendTransaction与合约deploy().send()、method().send()调用见同目录 helper.ts。它与本教程的差别仅在于 provider 的获取方式插件注入 vs 手动传入进一步证明web3.js v4 与 Hardhat 网络的对接既可以通过插件自动完成也可以手动传入 provider 等价实现。小结一条可复用的 Web3.js Hardhat 工作流至此你已经完整走通了「初始化 Hardhat → 引入 hardhat-web3-v4 插件 → 编写合约 → 编译 → 部署 → 测试交互」的全流程。总结成可复用的心法环境注入插件把web3实例与Web3类挂到 HRE任何脚本/测试import { web3 } from hardhat即可使用已连好本地网络的 web3.js部署三件套new web3.eth.Contract(abi)deploy({ data: bytecode, arguments })send({ from, gas, gasPrice, value })部署成功后的实例自带options.address读写分离读状态用methods.x().call()无签名、无 Gas写状态用methods.x().send({ from })必须显式from健壮性部署前先estimateGas开启handleRevert获取回滚原因大数一律以字符串传递并使用web3.utils.toWei做精确换算。把相同的模式套用到测试网或主网时只需在hardhat.config.ts中配置对应网络与账户即可合约代码与 web3.js 交互层无需改动——这正是插件化集成带来的最大便利。赞分享区块链Web3【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址https://gitcode.com/gh_mirrors/we/web3.js点击查看免费下载相关推荐ColorKit高级应用构建智能图片编辑与色彩分析工具ColorKit高级应用构建智能图片编辑与色彩分析工具 ColorKit是一款专为iOS开发者打造的高级色彩处理框架它提供了强大的色彩分析、提取和操作功能N_m3u8DL-RE 故障排查指南5 类报错各有对应解法N_m3u8DL RE 故障排查指南5 类报错各有对应解法 N_m3u8DL RE 是一款跨平台的流媒体下载工具支持 MPD / M3U8 / ISM 等格CLI音视频Web3.js 智能合约开发全流程指南Web3.js 智能合约开发全流程指南 本文将通过 Web3.js 库详细讲解如何从零开始开发、部署和交互一个区块链智能合约。作为 JavaScript 开发区块链Web3上一篇告别窗口混乱Electron多窗口管理的5个终极实战技巧下一篇notion-enhancer快速入门10分钟学会安装和基础配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表