进阶 docs.web3js.org 2026-10-19 02:32:29 · 15 阅读

第9章 智能合约开发实战

部署与调用智能合约 本教程将带你走一遍完整流程:把智能合约部署到开发网络、生成合约的 ABI 和字节码,并用 Web3.js 与合约交互。内容涵盖以太坊基础概念、Solidity 合约编程语言、Hardhat 开发环境以及 Web3.js 的用法。 概览 以下是本教程的主要步骤: - 确认前置条件 - 新建目录并初始化 Node.js 项目 - 编写智能合约的 Solidity 代码并保存为文件 - 用 Solidity 编译器编译代码,获取 ABI 和字节码 - 配置 Web3.js 和 Hardhat - 用 Web3.js 部署智能合约 - 用 Web3.js 与智能合约交互 提示:如果在跟着这份指南操作时遇到问题或有疑问,别犹豫,随时寻求帮助。我们的友好社区乐意为你解答!加入我们的 Discord 服务器,前往 #web3js-general 频道,与其他开发者交流、获取所需支持。 步骤 1:前置条件 本教程假定你熟悉命令行操作,并了解 JavaScript 和 Node.js。开始前请确保已安装 Node.js 及其包管理器 npm。 $: node -v
# 你的版本可能不同,但建议使用当前稳定版
v18.16.1
$: npm -v
9.5.1
步骤 2:新建目录并初始化 Node.js 项目 首先为项目创建一个新目录并进入其中: mkdir smart-contract-tutorial
cd smart-contract-tutorial
接着用 npm 初始化一个新的 Node.js 项目: npm init -y
这会在项目目录下生成一个新的 package.json 文件。 步骤 3:编写智能合约的 Solidity 代码并保存为文件 本步骤将编写智能合约的 Solidity 代码,并在项目目录下保存为文件。 在项目目录中创建名为 MyContract.sol 的新文件,并加入以下 Solidity 代码: // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
uint256 public myNumber;

constructor(uint256 _myNumber) {
myNumber = _myNumber;
}

function setMyNumber(uint256 _myNumber) public {
myNumber = _myNumber;
}
}
这个简单的智能合约定义了一个名为 myNumber 的变量,可以通过调用 setMyNumber 函数来设置其值。 第 4 步:使用 Solidity 编译器编译代码并获取 ABI 和字节码 在本步骤中,我们将使用 Solidity 编译器(solc)来编译合约的 Solidity 代码,并输出编译器生成的字节码和 ABI。 首先,使用 npm 安装 solc 包。请确保 solc 的版本与智能合约中通过 pragma solidity 指令指定的 Solidity 版本兼容。 npm i solc@^0.8.0
接下来,在项目目录中新建一个名为 compile.js 的文件,并添加以下代码: const solc = require('solc');
const path = require('path');
const fs = require('fs');

const contractName = 'MyContract';
const fileName = `${contractName}.sol`;

// 从文件系统读取 Solidity 源代码
const contractPath = path.join(__dirname, fileName);
const sourceCode = fs.readFileSync(contractPath, 'utf8');

// solc 编译器配置
const input = {
language: 'Solidity',
sources: {
[fileName]: {
content: sourceCode,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};

// 使用 solc 编译 Solidity 代码
const compiledCode = JSON.parse(solc.compile(JSON.stringify(input)));

// 从编译后的合约中获取字节码
const bytecode = compiledCode.contracts[fileName][contractName].evm.bytecode.object;

// 将字节码写入新文件
const bytecodePath = path.join(__dirname, 'MyContractBytecode.bin');
fs.writeFileSync(bytecodePath, bytecode);

// 在控制台输出编译后的合约字节码
console.log('Contract Bytecode:\n', bytecode);

// 从编译后的合约中获取 ABI
const abi = compiledCode.contracts[fileName][contractName].abi;

// 将合约 ABI 写入新文件
const abiPath = path.join(__dirname, 'MyContractAbi.json');
fs.writeFileSync(abiPath, JSON.stringify(abi, null, '\t'));

// 在控制台输出合约 ABI
console.log('Contract ABI:\n', abi);
这段代码会从 MyContract.sol 文件中读取 Solidity 代码,用 solc 编译,并生成智能合约的 ABI 和字节码。随后,字节码会写入新文件 MyContractBytecode.bin,合约 ABI 写入 MyContractAbi.json(这些值同时也会输出到控制台)。 运行以下命令编译 Solidity 代码: node compile.js
如果一切正常,你应该能在控制台看到合约的字节码和 ABI,并在项目目录中看到两个新文件(MyContractAbi.json 和 MyContractBytecode.bin)。 tip另外,也可以用 npm i solc && npx solcjs MyContract.sol --bin --abi 来生成字节码和 ABI。注意,这样生成的文件名与本教程中使用的不同,如果想完全按照本指南继续操作,需要把它们重命名为 MyContractBytecode.bin 和 MyContractAbi.json。更多关于 solc-js 的信息请查看 https://github.com/ethereum/solc-js。 Step 5: Set Up Web3.js and Hardhat​ 这一步我们来安装配置 Web3.js 和 Hardhat。 首先用 npm 安装所需的依赖包: npm i web3 hardhat
接着初始化 Hardhat 项目: npx hardhat init
初始化过程中会弹出若干提示,每一步都选择默认选项即可。初始化完成后,项目目录中会生成一批新文件和目录。 运行以下命令启动 Hardhat 开发网络: npx hardhat node
执行后会输出如下信息,其中包含连接开发网络所需的 URL 以及开发网络的测试账户: Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.

Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
Private Key:

...

Account #19: 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199 (10000 ETH)
Private Key:

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
Hardhat 开发网络需要保持终端一直处于运行状态,该终端即最初启动网络时所使用的终端。请在项目目录下打开一个新的终端实例,以执行本教程中剩余的命令。 接下来,在项目目录中创建一个名为 index.js 的新文件,并添加以下代码: const { Web3 } = require('web3');

const web3 = new Web3('http://127.0.0.1:8545/');

// Log the chain ID to the console
web3.eth
.getChainId()
.then(result => {
console.log('Chain ID: ' + result);
})
.catch(error => {
console.error(error);
});
这段代码用于建立 Web3.js 与 Hardhat 开发网络之间的连接,并在控制台输出链 ID。 note 本教程使用 Hardhat 开发网络的默认 URL(http://127.0.0.1:8545/)。请务必使用启动 Hardhat 开发网络时实际提供的 URL。 运行以下命令以测试连接: node index.js
如果一切正常,你应该会在控制台中看到如下日志: Chain ID: 31337
步骤 6:使用 Web3.js 部署智能合约 在这一步中,我们将使用 Web3.js 将智能合约部署到开发网络。 创建一个名为 deploy.js 的文件,并填入以下代码: const { Web3 } = require('web3');
const path = require('path');
const fs = require('fs');

const web3 = new Web3('http://127.0.0.1:8545/');

const bytecodePath = path.join(__dirname, 'MyContractBytecode.bin');
const bytecode = fs.readFileSync(bytecodePath, 'utf8');

const abi = require('./MyContractAbi.json');
const myContract = new web3.eth.Contract(abi);
myContract.handleRevert = true;

async function deploy() {
const providersAccounts = await web3.eth.getAccounts();
const defaultAccount = providersAccounts[0];
console.log('Deployer account:', defaultAccount);

const contractDeployer = myContract.deploy({
data: '0x' + bytecode,
arguments: [1],
});

const gas = await contractDeployer.estimateGas({
from: defaultAccount,
});
console.log('Estimated gas:', gas);

try {
const tx = await contractDeployer.send({
from: defaultAccount,
gas,
gasPrice: 10000000000,
});
console.log('Contract deployed at address: ' + tx.options.address);

const deployedAddressPath = path.join(__dirname, 'MyContractAddress.txt');
fs.writeFileSync(deployedAddressPath, tx.options.address);
} catch (error) {
console.error(error);
}
}

deploy();
上述代码从 MyContractBytecode.bin 文件读取字节码,从 MyContractAbi.json 文件读取 ABI,并使用这些值实例化一个新的合约对象。该示例还包含了估算部署智能合约所需 Gas 的可选步骤。随后,它将合约部署到开发网络。最后,它将已部署合约的地址保存在 MyContractAddress.txt 文件中,以便后续与该合约交互时使用。 运行以下命令来部署智能合约: node deploy.js
如果一切正常,你应该会看到类似以下内容(注意:具体数值可能略有不同): Deployer account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Estimated gas: 141681n
Contract deployed at address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
第 7 步:使用 Web3.js 与智能合约交互 在这一步,我们将使用 Web3.js 与开发网络上的智能合约进行交互。 创建一个名为 interact.js 的文件,并填入以下代码: const { Web3 } = require('web3');
const path = require('path');
const fs = require('fs');

const web3 = new Web3('http://127.0.0.1:8545/');

// 从文件系统读取合约地址
const deployedAddressPath = path.join(__dirname, 'MyContractAddress.txt');
const deployedAddress = fs.readFileSync(deployedAddressPath, 'utf8');

// 使用 ABI 和地址创建一个新的合约对象
const abi = require('./MyContractAbi.json');
const myContract = new web3.eth.Contract(abi, deployedAddress);
myContract.handleRevert = true;

async function interact() {
const accounts = await web3.eth.getAccounts();
const defaultAccount = accounts[0];

try {
// 获取 my number 的当前值
const myNumber = await myContract.methods.myNumber().call();
console.log('myNumber value: ' + myNumber);

// 递增 my number
const receipt = await myContract.methods.setMyNumber(BigInt(myNumber) + 1n).send({
from: defaultAccount,
gas: 1000000,
gasPrice: '10000000000',
});
console.log('Transaction Hash: ' + receipt.transactionHash);

// 获取更新后的 my number 值
const myNumberUpdated = await myContract.methods.myNumber().call();
console.log('myNumber updated value: ' + myNumberUpdated);
} catch (error) {
console.error(error);
}
}

interact();
这段代码使用之前生成的 ABI 和合约地址实例化一个 Contract 对象,用来与 MyContract 智能合约交互。它先从 MyContract 中读取 myNumber 的当前值并打印出来,然后更新该值,再读取更新后的值。最后,它会把更新后的 myNumber 值和交易哈希输出到控制台。 info在本教程中,为了演示如何与智能合约交互,上述脚本使用了较为随意的 gas 和 gasPrice 值。要了解如何正确设置这些值,请参阅 Gas and Priority Fees 指南。 运行以下命令与智能合约交互: node interact.js
如果一切正常,你应该会在控制台中看到当前计数器值,然后是交易回执,最后是更新后的计数器值。输出大致如下: myNumber value: 1
Transaction Hash: 0x956bf08a0ba71a768fdbf9879531e52e9b6a0e2802ad92f66387fc30fa939eb8
myNumber updated value: 2
总结​ 在本教程中,你学习了如何生成智能合约的 ABI 和字节码、将其部署到 Hardhat 开发网络,以及如何使用 Web3.js 与之交互。 掌握了这些知识后,你就可以开始尝试编写智能合约,用 Web3.js 在以太坊网络上构建去中心化应用(dApp)。请记住,这只是入门,关于以太坊和 Web3.js 还有更多内容值得学习,继续探索、继续构建,享受其中的乐趣吧! 技巧与最佳实践​ 将智能合约部署到生产环境之前,务必先在 Hardhat 等开发网络上充分测试。 使用最新版本的 Web3.js 和 Solidity,以便享受最新特性和安全补丁。 在将交易发送到网络之前,先使用 Web3.js 的 estimateGas 函数估算交易所需的 gas。

评论 (0)