news 2026/10/12 3:46:49

使用 Web3.js 编译并部署智能合约到 Sepolia 测试网:Dapp-Learning 实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 Web3.js 编译并部署智能合约到 Sepolia 测试网:Dapp-Learning 实战教程
  • 示例工程
  • 区块链

【免费下载链接】Dapp-Learning

Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.

项目地址:https://gitcode.com/gh_mirrors/da/Dapp-Learning
点击查看免费下载

本文以 Dapp-Learning 仓库 basic/01-web3js-deploy 示例为蓝本,完整讲解从 Solidity 源码编译、ABI 与字节码提取,到通过 Infura 将合约部署到 Sepolia 测试网的全过程。读完本文,你将掌握 web3.js v4 的核心接口(solc标准 JSON 编译、web3.eth.Contract、deploy().send())以及用.env安全管理私钥的工程实践,可直接迁移到其他测试网或主网部署场景。

一、示例概览与项目文件构成

该示例位于仓库 basic/01-web3js-deploy 目录,通过一个最小化的自增计数合约,让开发者理解"合约编译 → 二进制/ABI 提取 → 交易构造 → 签名广播 → 回执解析"的完整部署链路,并掌握基本的 web3js 接口用法。目录包含以下关键文件:

文件作用
Incrementer.sol待部署的 Solidity 智能合约(Solidity ^0.8.0)
index.js主脚本:读取配置、编译合约、部署上链
package.json依赖声明:web3、solc、dotenv
.env.example环境变量模板,说明需要填写的PRIVATE_KEY与INFURA_ID
README.md英文版教程

本项目同时提供后续进阶样例:部署完成后,可继续学习 02-web3js-transaction(合约交互交易)与 03-web3js-erc20(ERC20 代币操作),进一步巩固 web3js 的使用。

二、前置准备

在运行示例之前,需要完成以下四项准备:Infura 项目、MetaMask 私钥、Sepolia 测试币以及.env配置文件。

2.1 创建 Infura Project 获取 PROJECT ID

本示例通过 Infura 将交易发送到区块链网络,因此需要先在 Infura 官网注册并创建一个 Project:

  1. 注册/登录 Infura 账户,进入 Dashboard;
  2. 点击Create new API Key(或类似入口)新建一个 Project;
  3. 创建完成后,将网络的ENDPOINT 切换为 Sepolia,即可在项目详情页拿到对应的PROJECT ID(即示例中的INFURA_ID)。

Infura 作为以太坊节点服务商,为开发者提供了免自建节点的 RPC 访问入口,其 URL 形如https://sepolia.infura.io/v3/<PROJECT_ID>,下文构造 Web3 实例时会用到。

2.2 生成私钥 PRIVATE_KEY

本示例中,私钥需要自己生成,不能硬编码在代码里。最常见的方式是通过浏览器钱包 MetaMask:

  1. 安装 MetaMask 浏览器扩展并创建账户;
  2. 进入设置(Settings)→ 高级(Advanced),打开Show test networks选项,即可在网络上看到 Sepolia 等测试网络;
  3. 选择Sepolia测试网络,记录该账户地址;
  4. 点击账户详情 →导出私钥(Export Private Key),获得该测试账户的私钥PRIVATE_KEY。

安全提示:私钥代表账户的完全控制权,导出后切勿提交到公共仓库或分享给他人;本示例通过环境变量读取而非硬编码,正是出于这一安全考虑。

2.3 给 Sepolia 测试账户充值

上一步创建的测试账户余额为 0,无法支付部署所需的 Gas 费。需要通过水龙头(faucet)领取 Sepolia 测试币:

  • 推荐使用 Chainlink Faucets 等公开水龙头服务,输入账户地址后按提示领取,每次约充入 0.1 ETH;
  • 等待数分钟让交易确认后,可在 MetaMask 中查看余额;
  • 如果 Sepolia 水龙头不可用,也可以切换到其他测试网络(如 Goerli 等),只需相应调整下文 RPC 地址与网络配置。

2.4 配置 .env 环境变量

为方便代码测试,示例通过 .env.example 模板约定环境变量格式:

PRIVATE_KEY=xxxxxxxxxxxxxxxx INFURA_ID=yyyyyyyy

复制模板为.env并填入实际值后,index.js 会自动读取:

cp .env.example .env

然后编辑.env,将INFURA_ID与PRIVATE_KEY替换为真实值(INFURA_ID即 2.1 节获取的 PROJECT ID)。

三、合约功能说明:Incrementer.sol

待部署的 Incrementer.sol 是一个极简的状态合约,完整源码如下:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Incrementer { uint256 public number; constructor(uint256 _initialNumber) { number = _initialNumber; } function increment(uint256 _value) public { number = number + _value; } function reset() public { number = 0; } function getNumber() public view returns (uint256) { return number; } }

各成员的功能如下表:

成员类型说明
numberuint256 public公共状态变量,自动生成 getter,可被外部读取
constructor(uint256 _initialNumber)构造函数部署合约时调用,将number初始化为_initialNumber
increment(uint256 _value)增值函数根据传入的_value,对number执行number + _value
reset()重置函数将number重置为 0
getNumber()查询函数view类型,返回number当前数值,不消耗 Gas

需要注意两个与部署直接相关的点:

  • 构造函数带参数_initialNumber,因此部署交易必须通过arguments传入初始值(示例中传入[0]);
  • pragma solidity ^0.8.0声明了编译器版本下限,package.json 中锁定"solc": "0.8.0",与源码要求匹配——这也是下文编译配置能直接使用solc.compile的原因。

四、测试流程:从安装依赖到首次部署

4.1 安装依赖

进入示例目录执行:

npm install

本教程使用的 Node 版本为v20.11.0。示例依赖(见 package.json)为:

{ "dependencies": { "dotenv": "^16.3.1", "solc": "0.8.0", "web3": "^4.0.3" } }
  • web3(v4 系列):以太坊 JavaScript API 库,用于构造交易、签名与广播;
  • solc:Solidity 编译器,将.sol源码编译为二进制字节码与 ABI;
  • dotenv:自动读取.env文件并加载为进程环境变量。

4.2 配置 .env

cp .env.example .env

然后编辑.env,填入实际值:

PRIVATE_KEY=xxxxxxxxxxxxxxxx INFURA_ID=yyyyyyyy

4.3 执行部署脚本

node index.js

部署成功后,控制台将打印部署进度、估算 Gas、合约地址与交易哈希(具体输出格式见下文"运行结果解读"一节)。

五、index.js 代码逻辑逐段剖析

index.js 是整个任务的核心,包含从读取私钥到部署上链的完整逻辑,共分为以下阶段。

5.1 读取私钥:dotenv + 0x 前缀处理

出于安全考虑,私钥没有硬编码,而是通过环境变量获取。启动时,dotenv插件自动读取.env配置文件并加载为环境变量,之后代码中通过process.env读取私钥及其他环境变量:

require('dotenv').config(); let privatekey = process.env.PRIVATE_KEY; if (privatekey.slice(0, 2) !== '0x') privatekey = '0x' + privatekey;

相较于教程文档中的示例,index.js 增加了一行前缀兼容处理:如果私钥没有0x前缀(如直接从 MetaMask 导出的十六进制字符串),则自动补上,保证后续wallet.add能正确解析。

5.2 编译合约:solc 标准 JSON 输入

我们无法直接使用.sol文件与区块链交互,需要先将其编译为二进制文件。第一步是把Incrementer.sol读取为source变量:

const source = fs.readFileSync('Incrementer.sol', 'utf8');

随后以 Solidity 编译器标准的 JSON 输入格式构造编译配置并执行编译:

const input = { language: 'Solidity', sources: { 'Incrementer.sol': { content: source, }, }, settings: { outputSelection: { '*': { '*': ['*'], }, }, }, }; const compiledCode = JSON.parse(solc.compile(JSON.stringify(input)));

要点说明:

  • sources以文件名 → 源码内容的形式声明编译入口;
  • outputSelection的"*": { "*": ["*"] }表示输出所有合约的全部编译产物(字节码、ABI、元数据等),便于后续提取;
  • solc.compile接收 JSON 字符串、返回 JSON 字符串,因此需要JSON.stringify序列化输入、JSON.parse反序列化输出;
  • 不同 Solidity 源码版本,编译方式可能稍有不同。本示例Incrementer.sol使用 0.8.0 版本,且依赖中锁定了solc@0.8.0,因此上述标准 JSON 方式可直接生效;若源码版本变化,需同步调整编译器版本与编译参数。

5.3 提取字节码与 ABI

编译成功的 Solidity 对象中包含很多属性,我们需要的是合约对象的二进制(bytecode)与 ABI:

const contractFile = compiledCode.contracts['Incrementer.sol']['Incrementer']; // Get bin & abi const bytecode = contractFile.evm.bytecode.object; const abi = contractFile.abi;
  • bytecode:合约的运行时创建字节码(十六进制字符串),部署时作为交易data携带;
  • abi:合约接口的 JSON 描述,用于构造web3.eth.Contract实例,使代码能"读懂"合约的函数签名与参数类型。

Solidity 对象中的其他属性(如sourceMap、元数据等)可以通过调试方式查看,本示例不做展开。

5.4 构造 Web3 实例

Web3是 web3js 库的主 API,通过它可向区块链网络发送交易并获取处理结果。构造Web3实例主要需要传入一个参数:对应的区块链网络 RPC 地址,包括 Sepolia 等测试网络,或是 mainnet 主网:

const web3 = new Web3('https://sepolia.infura.io/v3/' + process.env.INFURA_ID);
  • 这里通过 Infura 的 Sepolia 节点发送交易,INFURA_ID即 2.1 节获取的 PROJECT ID,配置于.env;
  • 注释提示可以将其中的sepolia替换为其他测试网络(Goerli 等),前提是账户在该网络上有测试币;
  • 若部署到主网,需将 RPC 地址替换为对应的主网端点,并确保账户持有真实 ETH 支付 Gas。

5.5 从私钥获取账户地址

在区块链上,每个用户都有对应的账户地址,可通过私钥推导。示例调用web3.eth.accounts相关接口,将私钥加载进钱包:

const accounts = web3.eth.accounts.wallet.add(privatekey);

教程文档中描述的是web3.eth.accounts.privateKeyToAccount(privatekey)方式——它可以返回包含address的账户对象;而当前仓库的 index.js 实际采用wallet.add(privatekey),将私钥直接加入 web3 的本地钱包管理器,之后即可通过accounts[0].address取得该账户的地址(数组下标对应钱包中账户的添加顺序)。两种方式都能实现"私钥 → 地址"的推导,前者偏重单次转换,后者偏重交易签名时的统一管理。

5.6 构造合约实例

在第 5.3 节取得 ABI 后,即可用其构造合约实例,后续通过该实例发起交易:

const deployContract = new web3.eth.Contract(abi);

由于尚未部署,此处不传入合约地址,仅传入 ABI。合约实例封装了deploy、methods、events等能力,是后续一切合约交互的入口。

5.7 创建部署交易

调用deployContract.deploy创建部署合约的二进制交易。此时交易尚未发送到区块链网络,即合约还没有被创建:

const deployTx = deployContract.deploy({ data: '0x' + bytecode, arguments: [0], // Pass arguments to the contract constructor on deployment(_initialNumber in Incremental.sol) });
  • data:部署字节码,需要手动拼接0x前缀;
  • arguments:构造函数的入参数组,[0]对应Incrementer构造函数中的_initialNumber = 0。若初始值改为其他数字(如[100]),部署后number即为 100。

5.8 估算 Gas 消耗

仓库版脚本在发送交易前增加了 Gas 估算步骤,并打印预估结果供开发调试:

const gas = await deployTx.estimateGas({ from: accounts, }); console.log('estimated gas:', gas);

estimateGas会在本地模拟执行部署,返回预估的 Gas 消耗量。将其结果作为send时的gas参数,可避免 Gas 不足导致的失败,同时防止设置过高造成浪费。

5.9 签名并广播部署交易、解析回执

使用私钥对部署交易签名后发送到区块链网络,并返回交易回执。从回执中可以得到此次部署的合约地址:

const tx = await deployTx.send({ from: accounts[0].address, gas, // gasPrice: 10000000000, });
  • from:交易发起账户,即 5.5 节钱包中的账户地址;
  • gas:5.8 节估算出的 Gas 上限;
  • 被注释掉的gasPrice行表示可手动指定 Gas 单价(单位 wei),不指定时由节点/钱包按当前网络情况自动填充;
  • send会完成签名、广播与回执等待的全过程;tx.options.address为部署出的合约地址,tx.transactionHash为交易哈希。

5.10 部署后验证与统一错误处理

仓库版脚本在部署完成后加入了日志与验证逻辑,形成完整的最佳实践闭环:

console.log('Contract deployment started...'); console.log('Network:', await web3.eth.net.getNetworkType()); console.log('Account:', accounts[0].address); // ...部署与回执打印... const code = await web3.eth.getCode(tx.options.address); if (code === '0x') { throw new Error('Contract deployment failed - no code at address'); } return tx.options.address;
  • web3.eth.net.getNetworkType():打印当前连接的网络类型(如sepolia);
  • web3.eth.getCode(address):部署后读取目标地址上的合约代码,若返回0x说明该地址没有代码,即部署失败——这是部署后最直接的链上验证手段;
  • 脚本开头还检查了INFURA_ID与PRIVATE_KEY环境变量是否存在,缺失时抛出明确错误(Missing INFURA_ID environment variable/Missing PRIVATE_KEY environment variable),避免配置遗漏导致的费解报错;
  • 整个部署逻辑包在async函数中,主流程采用 Promise 链式调用并统一处理退出码:
Deploy() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });

六、运行结果解读与常见问题

6.1 预期输出

从 index.js 源码逻辑看,部署成功的控制台输出大致为:

estimated gas: <估算值> Contract deployment started... Network: sepolia Account: 0x<你的账户地址> Contract deployed successfully! Contract address: 0x<新部署的合约地址> Transaction hash: 0x<交易哈希>

之后可以在 MetaMask 或区块链浏览器(输入合约地址)中查看该合约,并调用getNumber验证初始值确实为 0。

6.2 常见问题排查

  • Missing INFURA_ID environment variable/Missing PRIVATE_KEY environment variable:.env未创建或未填写对应字段,检查是否执行了cp .env.example .env并填入真实值;
  • 账户余额不足:Sepolia 测试币余额为 0 时无法支付 Gas,需要先通过水龙头充值(见 2.3 节);
  • Error: ... insufficient funds类报错:Gas 估算与实际费用超出账户余额,或from地址填错,核对wallet.add后的账户地址;
  • 编译结果与预期不符:确认solc版本与合约pragma声明一致,本示例固定为 0.8.0;
  • 部署后getCode返回0x:说明交易虽被接受但部署未成功,检查 Gas 上限与构造函数参数,必要时通过区块链浏览器查看交易回执的失败原因。

七、延伸与参考

  • 完整英文版教程见 README.md,中文原版见 README-cn.md,其中保留了 Infura 创建指引、MetaMask 导出私钥教程、水龙头地址以及 Web3js 官方文档等原始参考链接;
  • 本示例只涉及部署环节;合约部署后如何发起交易调用(increment、reset、getNumber),可继续学习仓库内 02-web3js-transaction 示例,该目录下的 README-cn.md 与 index.js 展示了同样的编译流程在合约交互场景中的应用;
  • 若希望用 Truffle、Hardhat 等框架替代手工编译部署,仓库 04-web3js-truffle 与 07-hardhat 提供了对应的工程化方案。
  • 示例工程
  • 区块链

【免费下载链接】Dapp-Learning

Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.

项目地址:https://gitcode.com/gh_mirrors/da/Dapp-Learning
点击查看免费下载
上一篇:LINQ to GameObject源码分析:InternalUnsafeRefStack实现细节
下一篇:如何在Obsidian中安装Advanced Slides?3分钟快速上手教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 3:45:43

研发总监如何用AI补设计短板:五大实战场景与工具链

1. 研发总监为什么需要AI补设计短板1.1 一个真实困境&#xff1a;技术强、设计弱&#xff0c;产品就是差口气我带研发团队快十年了&#xff0c;从一线码农做到总监&#xff0c;踩过最大的坑不是技术架构&#xff0c;而是设计。团队里清一色工科背景&#xff0c;后端逻辑写得飞起…

作者头像 李华
网站建设 2026/10/12 3:44:39

【Xilem0.4基础语法学与练】第27课 split 可拖拽分割面板

前言 文档参考&#xff1a;https://docs.rs/xilem/latest/xilem/view/fn.split.html 版本&#xff1a;Xilem 0.4 一、split基础概念 split 是双面板可拖拽分割布局原语&#xff0c;容器只能容纳两个子视图&#xff0c;中间有可拖动分割条&#xff0c;鼠标拖动分割条可以动态修…

作者头像 李华
网站建设 2026/10/12 3:44:23

Spring Boot融合人脸识别,构建智能出勤管理系统

做毕设这些年&#xff0c;见过太多人一上来就选“XX管理系统”&#xff0c;最后交上去的功能千篇一律&#xff1a;增删改查、登录注册、导个Excel就完了。但“基于人脸识别的出勤管理系统”这个题目不一样&#xff0c;它天然带着一个技术亮点——人脸识别&#xff0c;做完之后既…

作者头像 李华
网站建设 2026/10/12 3:41:07

JMeter HTTP Request Defaults 配置原理与工程化实践

1. 为什么一个“默认配置”元件值得单独写五千字&#xff1f;你有没有在 JMeter 里写过这样的脚本&#xff1a;二十个 HTTP 请求&#xff0c;每个都重复填一遍服务器地址、端口、协议、超时时间、编码格式&#xff1f;复制粘贴五次之后手开始抖&#xff0c;改个域名要手动点开二…

作者头像 李华