开发工具与框架
Web3 开发需要一套完整的工具链,包括编译器、测试框架、部署工具和前端交互库。本章将介绍最流行的开发工具和框架,帮助你高效地构建去中心化应用。
Hardhat
Hardhat 是目前最流行的以太坊开发环境,提供编译、测试、部署和调试功能。
安装和初始化
# 创建项目目录
mkdir my-dapp && cd my-dapp
# 初始化 npm 项目
npm init -y
# 安装 Hardhat
npm install --save-dev hardhat
# 初始化 Hardhat 项目
npx hardhat init
初始化时选择 "Create a JavaScript project",会生成以下结构:
my-dapp/
├── contracts/ # 智能合约
│ └── Lock.sol
├── scripts/ # 部署脚本
│ └── deploy.js
├── test/ # 测试文件
│ └── Lock.js
├── hardhat.config.js # 配置文件
└── package.json
配置文件
hardhat.config.js 是 Hardhat 的核心配置文件:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
// 本地开发网络
hardhat: {
chainId: 31337
},
// Sepolia 测试网
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
// 以太坊主网
mainnet: {
url: process.env.MAINNET_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts"
}
};
编译合约
# 编译所有合约
npx hardhat compile
# 强制重新编译
npx hardhat compile --force
编译产物存储在 artifacts/ 目录,包括:
- ABI(应用二进制接口)
- 字节码
- 调试信息
编写部署脚本
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying contracts with account:", deployer.address);
console.log("Account balance:", (await deployer.provider.getBalance(deployer.address)).toString());
const Token = await hre.ethers.getContractFactory("MyToken");
const token = await Token.deploy("My Token", "MTK", 1000000);
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
运行部署脚本:
# 部署到本地网络
npx hardhat run scripts/deploy.js
# 部署到测试网
npx hardhat run scripts/deploy.js --network sepolia
编写测试
Hardhat 支持 Mocha 测试框架和 Chai 断言库:
// test/Token.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
let token;
let owner;
let addr1;
let addr2;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy("My Token", "MTK", 1000000);
await token.waitForDeployment();
});
describe("Deployment", function () {
it("Should set the right owner", async function () {
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign the total supply to the owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
await token.transfer(addr1.address, 50);
const addr1Balance = await token.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
});
it("Should fail if sender doesn't have enough tokens", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
await expect(
token.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("ERC20: transfer amount exceeds balance");
expect(await token.balanceOf(owner.address)).to.equal(initialOwnerBalance);
});
it("Should update balances after transfers", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
await token.transfer(addr1.address, 100);
await token.transfer(addr2.address, 50);
const finalOwnerBalance = await token.balanceOf(owner.address);
expect(finalOwnerBalance).to.equal(initialOwnerBalance - 150n);
expect(await token.balanceOf(addr1.address)).to.equal(100);
expect(await token.balanceOf(addr2.address)).to.equal(50);
});
});
});
运行测试:
# 运行所有测试
npx hardhat test
# 运行特定测试文件
npx hardhat test test/Token.test.js
# 显示 Gas 消耗
REPORT_GAS=true npx hardhat test
Hardhat 网络
Hardhat 内置了一个本地以太坊网络,用于开发和测试:
# 启动本地节点
npx hardhat node
这会启动一个本地节点,提供 20 个预置账户,每个账户有 10000 ETH。
在另一个终端中,可以部署到本地节点:
npx hardhat run scripts/deploy.js --network localhost
控制台
Hardhat 提供交互式控制台,方便调试:
# 启动控制台
npx hardhat console
# 在控制台中
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy("My Token", "MTK", 1000000);
await token.name();
常用插件
# 安装常用插件
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install --save-dev @nomiclabs/hardhat-etherscan
npm install --save-dev hardhat-gas-reporter
npm install --save-dev solidity-coverage
ethers.js
ethers.js 是一个完整的以太坊 JavaScript 库,用于与区块链交互。
安装
npm install ethers
连接网络
const { ethers } = require("ethers");
// 连接到以太坊节点
const provider = new ethers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
// 获取网络信息
const network = await provider.getNetwork();
console.log("Network:", network.name, network.chainId);
// 获取区块高度
const blockNumber = await provider.getBlockNumber();
console.log("Block number:", blockNumber);
// 获取账户余额
const balance = await provider.getBalance("0x...");
console.log("Balance:", ethers.formatEther(balance), "ETH");
// 获取 Gas 价格
const gasPrice = await provider.getFeeData();
console.log("Gas price:", ethers.formatUnits(gasPrice.gasPrice, "gwei"), "gwei");
管理账户
const { ethers } = require("ethers");
// 从私钥创建钱包
const privateKey = "0x...";
const wallet = new ethers.Wallet(privateKey, provider);
console.log("Address:", wallet.address);
// 从助记词创建钱包
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const walletFromMnemonic = ethers.Wallet.fromPhrase(mnemonic, provider);
// 签名消息
const message = "Hello, Ethereum!";
const signature = await wallet.signMessage(message);
console.log("Signature:", signature);
// 验证签名
const recoveredAddress = ethers.verifyMessage(message, signature);
console.log("Recovered address:", recoveredAddress);
// 发送交易
const tx = await wallet.sendTransaction({
to: "0x...",
value: ethers.parseEther("0.01")
});
console.log("Transaction hash:", tx.hash);
// 等待确认
const receipt = await tx.wait();
console.log("Transaction confirmed in block:", receipt.blockNumber);
与合约交互
const { ethers } = require("ethers");
// 合约 ABI
const abi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 value)"
];
// 合约地址
const contractAddress = "0x...";
// 创建合约实例(只读)
const contract = new ethers.Contract(contractAddress, abi, provider);
// 读取数据
const name = await contract.name();
const symbol = await contract.symbol();
const decimals = await contract.decimals();
const totalSupply = await contract.totalSupply();
console.log("Token:", name, symbol);
console.log("Total supply:", ethers.formatUnits(totalSupply, decimals));
// 写入数据(需要签名者)
const wallet = new ethers.Wallet(privateKey, provider);
const contractWithSigner = contract.connect(wallet);
// 发送交易
const tx = await contractWithSigner.transfer(
"0x...",
ethers.parseUnits("100", decimals)
);
console.log("Transfer tx:", tx.hash);
// 等待确认
await tx.wait();
console.log("Transfer confirmed");
// 监听事件
contract.on("Transfer", (from, to, value, event) => {
console.log("Transfer:", from, "=>", to, ethers.formatUnits(value, decimals));
});
使用 Ethers.js 部署合约
const { ethers } = require("ethers");
async function deploy() {
// 连接到本地节点
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
// 获取签名者
const wallet = new ethers.Wallet(privateKey, provider);
// 合约字节码和 ABI
const bytecode = "0x...";
const abi = [...];
// 创建合约工厂
const factory = new ethers.ContractFactory(abi, bytecode, wallet);
// 部署合约
const contract = await factory.deploy("My Token", "MTK", 1000000);
console.log("Deploying to:", await contract.getAddress());
// 等待部署完成
await contract.waitForDeployment();
console.log("Contract deployed!");
}
deploy();
Web3.js
Web3.js 是另一个流行的以太坊 JavaScript 库,功能与 ethers.js 类似。
安装
npm install web3
基本使用
const Web3 = require("web3");
// 连接到以太坊节点
const web3 = new Web3("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
// 获取区块高度
const blockNumber = await web3.eth.getBlockNumber();
console.log("Block number:", blockNumber);
// 获取账户余额
const balance = await web3.eth.getBalance("0x...");
console.log("Balance:", web3.utils.fromWei(balance, "ether"), "ETH");
// 获取 Gas 价格
const gasPrice = await web3.eth.getGasPrice();
console.log("Gas price:", web3.utils.fromWei(gasPrice, "gwei"), "gwei");
// 发送交易
const tx = await web3.eth.sendTransaction({
from: "0x...",
to: "0x...",
value: web3.utils.toWei("0.01", "ether"),
gas: 21000
});
console.log("Transaction hash:", tx.transactionHash);
与合约交互
const Web3 = require("web3");
const web3 = new Web3("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
const abi = [...];
const contractAddress = "0x...";
const contract = new web3.eth.Contract(abi, contractAddress);
// 读取数据
const name = await contract.methods.name().call();
const balance = await contract.methods.balanceOf("0x...").call();
// 发送交易
const tx = contract.methods.transfer("0x...", web3.utils.toWei("100", "ether"));
const gas = await tx.estimateGas({ from: "0x..." });
await tx.send({ from: "0x...", gas });
Truffle Suite
Truffle 是最早的以太坊开发框架之一,包含多个工具。
安装
npm install -g truffle
创建项目
mkdir my-dapp && cd my-dapp
truffle init
项目结构:
my-dapp/
├── contracts/ # 智能合约
├── migrations/ # 部署脚本
├── test/ # 测试文件
└── truffle-config.js # 配置文件
配置文件
// truffle-config.js
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
},
sepolia: {
provider: () => new HDWalletProvider(mnemonic, `https://sepolia.infura.io/v3/${projectId}`),
network_id: 11155111,
confirmations: 2,
timeoutBlocks: 200
}
},
compilers: {
solc: {
version: "0.8.20"
}
}
};
编译和部署
# 编译合约
truffle compile
# 部署到开发网络
truffle migrate
# 部署到测试网
truffle migrate --network sepolia
编写测试
// test/Token.test.js
const MyToken = artifacts.require("MyToken");
contract("MyToken", accounts => {
let token;
const [owner, addr1] = accounts;
beforeEach(async () => {
token = await MyToken.new("My Token", "MTK", 1000000);
});
it("should set the right owner", async () => {
const tokenOwner = await token.owner();
assert.equal(tokenOwner, owner, "Owner is incorrect");
});
it("should transfer tokens", async () => {
await token.transfer(addr1, 100, { from: owner });
const balance = await token.balanceOf(addr1);
assert.equal(balance.toNumber(), 100, "Balance is incorrect");
});
});
Ganache
Ganache 是一个本地以太坊区块链,用于开发和测试。
安装
# 命令行版本
npm install -g ganache
# 或下载 GUI 版本
# https://trufflesuite.com/ganache/
使用
# 启动本地区块链
ganache
# 指定端口
ganache --port 8545
# 指定账户数量
ganache --accounts 20
# 指定初始余额
ganache --defaultBalanceEther 100
Remix IDE
Remix 是一个在线 Solidity IDE,无需安装即可使用。
访问
打开 https://remix.ethereum.org/
主要功能
- 代码编辑器(语法高亮、自动补全)
- Solidity 编译器
- 部署和交互界面
- 调试器
- 静态分析工具
- 单元测试框架
使用流程
- 在文件管理器中创建
.sol文件 - 在编译器面板选择版本并编译
- 在部署面板选择环境(JavaScript VM、Injected Provider、外部节点)
- 部署合约并交互
OpenZeppelin
OpenZeppelin 提供了一套经过安全审计的智能合约库。
安装
npm install @openzeppelin/contracts
使用 ERC20 代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) Ownable(msg.sender) {
_mint(msg.sender, initialSupply * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
使用 ERC721 NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 private _tokenIdCounter;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter++;
_safeMint(to, tokenId);
}
}
使用访问控制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant USER_ROLE = keccak256("USER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
}
function adminFunction() public onlyRole(ADMIN_ROLE) {
// 管理员功能
}
function userFunction() public onlyRole(USER_ROLE) {
// 用户功能
}
}
小结
本章介绍了 Web3 开发的核心工具和框架。Hardhat 是目前最推荐的开发环境,ethers.js 是最流行的交互库,OpenZeppelin 提供了安全的合约模板。掌握这些工具,你就可以开始构建完整的 DApp 了。下一章我们将学习 DeFi 和 NFT 的开发实践。