- 示例工程
- 区块链
【免费下载链接】Dapp-Learning
Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.
本文以 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:
- 注册/登录 Infura 账户,进入 Dashboard;
- 点击Create new API Key(或类似入口)新建一个 Project;
- 创建完成后,将网络的ENDPOINT 切换为 Sepolia,即可在项目详情页拿到对应的PROJECT ID(即示例中的
INFURA_ID)。
Infura 作为以太坊节点服务商,为开发者提供了免自建节点的 RPC 访问入口,其 URL 形如https://sepolia.infura.io/v3/<PROJECT_ID>,下文构造 Web3 实例时会用到。
2.2 生成私钥 PRIVATE_KEY
本示例中,私钥需要自己生成,不能硬编码在代码里。最常见的方式是通过浏览器钱包 MetaMask:
- 安装 MetaMask 浏览器扩展并创建账户;
- 进入设置(Settings)→ 高级(Advanced),打开Show test networks选项,即可在网络上看到 Sepolia 等测试网络;
- 选择Sepolia测试网络,记录该账户地址;
- 点击账户详情 →导出私钥(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; } }各成员的功能如下表:
| 成员 | 类型 | 说明 |
|---|---|---|
number | uint256 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=yyyyyyyy4.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.
相关推荐
web3.js 智能合约实战:从 Solidity 编译到部署与链上交互的完整指南
web3.js 智能合约实战:从 Solidity 编译到部署与链上交互的完整指南 本篇技术指南基于当前仓库 docs/docs/guides/05_smart
区块链Web3FHEVM 合约的 Foundry 部署实战:本地 Anvil 与 Sepolia 测试网完整指南
FHEVM 合约的 Foundry 部署实战:本地 Anvil 与 Sepolia 测试网完整指南 导读 本文基于 fhEVM 开源仓库的 Solidity 指
密码学隐私计算区块链后端sway-farm智能合约部署教程:从本地测试网到Fuel主网全流程
sway farm智能合约部署教程:从本地测试网到Fuel主网全流程 你是否正在寻找一份详尽的Fuel网络智能合约部署指南?本文将以sway farm项目为例,
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考