一步步学会 Solidity 合约编译、部署与 Web3.js 交互,面向前端开发者、区块链初学者与 dApp 架构者。
内容速览
本文将带你完成以下 7 个关键动作:
- 检查运行环境
- 初始化 Node 项目
- 用 Solidity 编写并保存智能合约
- 生成 合约 ABI 与 字节码
- 利用 Hardhat 建立开发网络并配置 Web3.js
- 用 Web3.js 部署智能合约
- 调用合约函数并验证结果
👉 十分钟无废话代码+DEMO,边学边跑高效入门智能合约交互。
前置条件
- ✔ 已安装 Node.js(≥v16)与 npm
- ✔ 熟悉命令行与 JavaScript 基础语法
- ✔ 本地已配置终端(Git Bash / PowerShell / zsh 均可)
打开终端,验证版本:
node -v # 输出版本即可
npm -v # 输出版本即可第一步:项目初始化
建立工作目录并进入:
mkdir smart-contract-tutorial
cd smart-contract-tutorial自动生成 package.json:
npm init -y第二步:编写 Solidity 合约
新建 MyContract.sol,内容如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
uint256 public myNumber;
constructor(uint256 _initial) {
myNumber = _initial;
}
function setMyNumber(uint256 _newValue) public {
myNumber = _newValue;
}
}作用:
- 将
myNumber变量暴露到链上 - 构造函数部署时写入初始值
- 任何人调用
setMyNumber()可修改值
第三步:编译合约 & 产出 ABI + 字节码
安装 solc 编译器:
npm install solc@latest创建 compile.js:
/* compile.js */
const solc = require('solc');
const path = require('path');
const fs = require('fs');
const fileName = 'MyContract.sol';
const contractName = 'MyContract';
const sourceCode = fs.readFileSync(path.join(__dirname, fileName), 'utf8');
// 配置输入
const input = {
language: 'Solidity',
sources: { [fileName]: { content: sourceCode } },
settings: { outputSelection: { '*': { '*': ['*'] } } }
};
const compiled = JSON.parse(solc.compile(JSON.stringify(input)));
const bytecode = compiled.contracts[fileName][contractName].evm.bytecode.object;
const abi = compiled.contracts[fileName][contractName].abi;
fs.writeFileSync('./MyContractBytecode.bin', bytecode);
fs.writeFileSync('./MyContractAbi.json', JSON.stringify(abi, null, 2));运行:
node compile.js成功后会生成:
MyContractBytecode.bin—— 字节码,供部署使用MyContractAbi.json—— 易读的 ABI 文件
第四步:启动 Hardhat 开发网络
安装依赖:
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox初始化 Hardhat:
npx hardhat init按提示一路回车选 JavaScript 模板即可。
启动本地链:
npx hardhat node终端会输出 20 个测试账号、公开私钥与连接 URL(默认 http://127.0.0.1:8545/)。
⚠️ 切勿泄露私钥到公链,仅限于开发!
第五步:连接 Web3.js
安装 Web3.js:
npm install web3新建 index.js 做连接验证:
const { Web3 } = require('web3');
const web3 = new Web3('http://127.0.0.1:8545');
web3.eth.getChainId()
.then(console.log)
.catch(console.error);运行:
node index.js看到 31337(Hardhat 默认链 ID),即表示 Web3.js 已对接成功。
第六步:部署合约到开发网络
部署脚本 deploy.js:
const { Web3 } = require('web3');
const fs = require('fs');
const path = require('path');
const web3 = new Web3('http://127.0.0.1:8545');
const abi = JSON.parse(fs.readFileSync('./MyContractAbi.json'));
const bytecode = fs.readFileSync('./MyContractBytecode.bin', 'utf8');
const deploy = async () => {
const [deployer] = await web3.eth.getAccounts();
console.log('部署账户:', deployer);
const contract = new web3.eth.Contract(abi);
const deployerInstance = contract.deploy({
data: '0x' + bytecode,
arguments: [123] // 构造函数传参
});
const gasEstimate = await deployerInstance.estimateGas({ from: deployer });
console.log('预计 gas: ', gasEstimate);
const deployed = await deployerInstance.send({ from: deployer, gas: gasEstimate });
const address = deployed.options.address;
console.log('合约地址:', address);
fs.writeFileSync('./MyContractAddress.txt', address);
};
deploy().catch(console.error);运行
node deploy.js示例输出:
部署账户: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
预计 gas: 123987
合约地址: 0x9fE1...第七步:与合约交互
创建 interact.js:
const { Web3 } = require('web3');
const fs = require('fs');
const path = require('path');
const web3 = new Web3('http://127.0.0.1:8545');
const abi = JSON.parse(fs.readFileSync('./MyContractAbi.json'));
const address = fs.readFileSync('./MyContractAddress.txt', 'utf8').trim();
const contract = new web3.eth.Contract(abi, address);
const interact = async () => {
const [caller] = await web3.eth.getAccounts();
// 读取当前值
const val = await contract.methods.myNumber().call();
console.log('当前值:', val);
// 发送修改交易
const receipt = await contract.methods.setMyNumber(Number(val) + 1).send({
from: caller,
gas: 100000
});
console.log('交易哈希:', receipt.transactionHash);
// 再读值
const val2 = await contract.methods.myNumber().call();
console.log('更新后值:', val2);
};
interact().catch(console.error);运行:
node interact.js预期输出:
当前值: 123
交易哈希: 0xe89b...
更新后值: 124👉 动手跑通示例后,再来试试一键导出 ABI 方案,提升合约调试效率。
常见问题(FAQ)
Q1:提示 Error: invalid opcode?
A:检查 Solidity 版本与 solc 版本是否一致,或者在 compile.js 内手动指定同版本。
Q2:部署时报错 gas estimation failed?
A:开发网络账户余额不足?默认 10000 ETH,确认地址正确;也可能是 constructor 参数类型不匹配。
Q3:调用函数时得到 transaction reverted?
A:合约逻辑导致回滚,如 require 条件不满足;可在 Hardhat 打印详细 revert 原因。
Q4:如何切换到真实的测试网?
A:把 Web3 的连接字符串改为 Goerli 等相关 RPC,并导入有测试币的钱包私钥即可。
Q5:ABI 文件太长可以精简吗?
A:无需删减,前端反序列化会依赖完整签名;若仅做调用,可使用 web3.eth.abi.decodeParameters 方式手工编解码函数。
最佳实践小贴士
- 安全 永远先于花哨功能:上线的每一步都先在本地 Hardhat 跑大量单元测试
- 版本管理 用。
.env文件存储私钥与 RPC 地址,切勿上传 Git - Gas 观测 deploy 前务必
estimateGas,矿工费波动大时能省不少 - 升级技巧 如果业务会变,可用 Beacon Proxy 模式(OpenZeppelin Upgrades),合约逻辑与代理地址分离
将以上流程走通,你就拥有了一张从合约代码到链上交互的「全链路通行证」。下一步,可以尝试把前端 React 用 ethers.js 或 Wagmi 结合起来,真正打造用户可见的 dApp。祝你开发愉快!