Web3.js 全面解析
Web3.js 是以太坊官方推出的 JavaScript 库,用于与以太坊区块链交互。让我们深入解析其架构、核心功能和使用方法。
1. Web3.js 架构概述
核心模块结构
javascript
// Web3.js 模块架构
const web3Architecture = {
core: {
web3: "主入口类",
providers: "提供者模块 - 连接区块链",
eth: "以太坊区块链交互",
net: "网络信息",
utils: "工具函数库"
},
extended: {
contracts: "智能合约交互",
accounts: "账户管理",
personal: "账户操作(已弃用)",
bzz: "Swarm去中心化存储",
shh: "Whisper协议"
}
};
版本演进
javascript
// Web3.js 版本对比
const web3Versions = {
"0.x.x": {
type: "回调模式",
features: ["回调函数", "批量请求", "较慢"],
status: "已弃用"
},
"1.x.x": {
type: "Promise模式",
features: ["Promise支持", "TypeScript", "模块化"],
status: "当前稳定版"
},
"4.x.x": {
type: "现代版本",
features: ["ESM模块", "更好TypeScript", "性能优化"],
status: "最新版本"
}
};
2. 安装和初始化
安装
bash
# 使用 npm
npm install web3
# 使用 yarn
yarn add web3
# 使用 CDN
<script src="https://cdn.jsdelivr.net/npm/web3@4.0.3/dist/web3.min.js"></script>
初始化连接
javascript
import Web3 from 'web3';
// 多种初始化方式
class Web3Initialization {
// 1. 使用 MetaMask 或其他注入提供者
initWithInjectedProvider() {
if (typeof window.ethereum !== 'undefined') {
this.web3 = new Web3(window.ethereum);
console.log("使用注入的以太坊提供者");
} else {
console.error("请安装 MetaMask!");
}
}
// 2. 使用 HTTP 提供者
initWithHttpProvider(rpcUrl = 'http://localhost:8545') {
this.web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl));
console.log("使用 HTTP 提供者连接:", rpcUrl);
}
// 3. 使用 WebSocket 提供者
initWithWebSocketProvider(wsUrl = 'ws://localhost:8546') {
this.web3 = new Web3(new Web3.providers.WebSocketProvider(wsUrl));
console.log("使用 WebSocket 提供者连接:", wsUrl);
}
// 4. 使用 Infura 或 Alchemy
initWithInfura(projectId, network = 'mainnet') {
const infuraUrl = `https://${network}.infura.io/v3/${projectId}`;
this.web3 = new Web3(new Web3.providers.HttpProvider(infuraUrl));
console.log("使用 Infura 连接:", network);
}
}
3. 核心模块详解
3.1 账户管理 (web3.eth.accounts)
javascript
class AccountManager {
constructor(web3) {
this.web3 = web3;
}
// 创建新账户
createAccount(password = '') {
try {
const account = this.web3.eth.accounts.create();
console.log("新账户地址:", account.address);
console.log("私钥:", account.privateKey);
return account;
} catch (error) {
console.error("创建账户失败:", error);
}
}
// 从私钥恢复账户
recoverAccount(privateKey) {
try {
const account = this.web3.eth.accounts.privateKeyToAccount(privateKey);
console.log("恢复的账户地址:", account.address);
return account;
} catch (error) {
console.error("恢复账户失败:", error);
}
}
// 加密钱包
encryptAccount(privateKey, password) {
try {
const keystore = this.web3.eth.accounts.encrypt(privateKey, password);
return keystore;
} catch (error) {
console.error("加密账户失败:", error);
}
}
// 解密钱包
decryptAccount(keystoreJson, password) {
try {
const account = this.web3.eth.accounts.decrypt(keystoreJson, password);
return account;
} catch (error) {
console.error("解密账户失败:", error);
}
}
// 签名消息
async signMessage(message, privateKey) {
try {
const signature = this.web3.eth.accounts.sign(message, privateKey);
return signature;
} catch (error) {
console.error("签名失败:", error);
}
}
// 验证签名
verifySignature(message, signature, address) {
try {
const recoveredAddress = this.web3.eth.accounts.recover(message, signature);
return recoveredAddress.toLowerCase() === address.toLowerCase();
} catch (error) {
console.error("验证签名失败:", error);
return false;
}
}
}
3.2 区块链交互 (web3.eth)
javascript
class BlockchainInteractor {
constructor(web3) {
this.web3 = web3;
}
// 获取区块信息
async getBlockInfo(blockNumberOrHash = 'latest') {
try {
const block = await this.web3.eth.getBlock(blockNumberOrHash);
return {
number: block.number,
hash: block.hash,
timestamp: new Date(block.timestamp * 1000),
transactions: block.transactions.length,
gasUsed: block.gasUsed,
gasLimit: block.gasLimit
};
} catch (error) {
console.error("获取区块信息失败:", error);
}
}
// 获取账户余额
async getBalance(address, unit = 'ether') {
try {
const balanceWei = await this.web3.eth.getBalance(address);
const balance = this.web3.utils.fromWei(balanceWei, unit);
return { wei: balanceWei, formatted: balance + ' ' + unit };
} catch (error) {
console.error("获取余额失败:", error);
}
}
// 获取交易信息
async getTransaction(txHash) {
try {
const tx = await this.web3.eth.getTransaction(txHash);
if (!tx) return null;
return {
hash: tx.hash,
from: tx.from,
to: tx.to,
value: this.web3.utils.fromWei(tx.value, 'ether'),
gas: tx.gas,
gasPrice: this.web3.utils.fromWei(tx.gasPrice, 'gwei'),
nonce: tx.nonce,
blockNumber: tx.blockNumber
};
} catch (error) {
console.error("获取交易失败:", error);
}
}
// 获取交易收据
async getTransactionReceipt(txHash) {
try {
const receipt = await this.web3.eth.getTransactionReceipt(txHash);
return receipt;
} catch (error) {
console.error("获取交易收据失败:", error);
}
}
// 估算交易 Gas
async estimateGas(transactionObject) {
try {
const gas = await this.web3.eth.estimateGas(transactionObject);
return gas;
} catch (error) {
console.error("估算 Gas 失败:", error);
}
}
// 获取当前 Gas 价格
async getGasPrice() {
try {
const gasPriceWei = await this.web3.eth.getGasPrice();
const gasPriceGwei = this.web3.utils.fromWei(gasPriceWei, 'gwei');
return { wei: gasPriceWei, gwei: gasPriceGwei };
} catch (error) {
console.error("获取 Gas 价格失败:", error);
}
}
}
3.3 交易操作
javascript
class TransactionHandler {
constructor(web3) {
this.web3 = web3;
}
// 发送以太币交易
async sendEther(fromAddress, privateKey, toAddress, amount, options = {}) {
try {
// 创建交易对象
const txObject = {
from: fromAddress,
to: toAddress,
value: this.web3.utils.toWei(amount.toString(), 'ether'),
gas: options.gas || 21000,
gasPrice: options.gasPrice || await this.web3.eth.getGasPrice(),
nonce: options.nonce || await this.web3.eth.getTransactionCount(fromAddress, 'latest')
};
// 估算 Gas(可选)
if (!options.gas) {
txObject.gas = await this.web3.eth.estimateGas(txObject);
}
// 签名交易
const signedTx = await this.web3.eth.accounts.signTransaction(txObject, privateKey);
// 发送交易
const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
return {
success: true,
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed
};
} catch (error) {
console.error("发送交易失败:", error);
return { success: false, error: error.message };
}
}
// 监听待处理交易
subscribePendingTransactions(callback) {
try {
const subscription = this.web3.eth.subscribe('pendingTransactions', (error, txHash) => {
if (!error) {
callback(txHash);
}
});
return subscription;
} catch (error) {
console.error("监听交易失败:", error);
}
}
// 获取交易数量
async getTransactionCount(address, blockTag = 'latest') {
try {
const nonce = await this.web3.eth.getTransactionCount(address, blockTag);
return nonce;
} catch (error) {
console.error("获取交易数量失败:", error);
}
}
}
3.4 智能合约交互
javascript
class ContractInteractor {
constructor(web3) {
this.web3 = web3;
this.contracts = new Map();
}
// 初始化合约实例
initContract(contractAddress, abi) {
try {
const contract = new this.web3.eth.Contract(abi, contractAddress);
this.contracts.set(contractAddress, contract);
return contract;
} catch (error) {
console.error("初始化合约失败:", error);
}
}
// 调用只读方法(不消耗 Gas)
async callContractMethod(contractAddress, methodName, params = [], options = {}) {
try {
const contract = this.contracts.get(contractAddress);
if (!contract) throw new Error("合约未初始化");
const result = await contract.methods[methodName](...params).call(options);
return result;
} catch (error) {
console.error(`调用合约方法 ${methodName} 失败:`, error);
}
}
// 发送交易到合约(消耗 Gas)
async sendContractTransaction(contractAddress, methodName, params = [], fromAddress, privateKey, options = {}) {
try {
const contract = this.contracts.get(contractAddress);
if (!contract) throw new Error("合约未初始化");
// 构建交易
const method = contract.methods[methodName](...params);
const encodedData = method.encodeABI();
const txObject = {
from: fromAddress,
to: contractAddress,
data: encodedData,
gas: options.gas || await method.estimateGas({ from: fromAddress }),
gasPrice: options.gasPrice || await this.web3.eth.getGasPrice(),
nonce: options.nonce || await this.web3.eth.getTransactionCount(fromAddress, 'latest')
};
// 签名并发送
const signedTx = await this.web3.eth.accounts.signTransaction(txObject, privateKey);
const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
return {
success: true,
transactionHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed,
events: receipt.events
};
} catch (error) {
console.error(`发送合约交易 ${methodName} 失败:`, error);
return { success: false, error: error.message };
}
}
// 监听合约事件
subscribeToContractEvent(contractAddress, eventName, callback) {
try {
const contract = this.contracts.get(contractAddress);
if (!contract) throw new Error("合约未初始化");
const subscription = contract.events[eventName]({
fromBlock: 'latest'
}, (error, event) => {
if (!error) {
callback(event);
}
});
return subscription;
} catch (error) {
console.error(`监听合约事件 ${eventName} 失败:`, error);
}
}
// 获取过去事件
async getPastEvents(contractAddress, eventName, options = {}) {
try {
const contract = this.contracts.get(contractAddress);
if (!contract) throw new Error("合约未初始化");
const events = await contract.getPastEvents(eventName, {
fromBlock: options.fromBlock || 0,
toBlock: options.toBlock || 'latest',
filter: options.filter || {}
});
return events;
} catch (error) {
console.error(`获取历史事件 ${eventName} 失败:`, error);
}
}
}
3.5 工具函数 (web3.utils)
javascript
class Web3Utils {
constructor(web3) {
this.web3 = web3;
}
// 单位转换
unitConversions() {
const weiValue = '1000000000000000000'; // 1 ETH
return {
weiToEther: this.web3.utils.fromWei(weiValue, 'ether'), // "1.0"
etherToWei: this.web3.utils.toWei('1', 'ether'), // "1000000000000000000"
weiToGwei: this.web3.utils.fromWei(weiValue, 'gwei'), // "1000000000.0"
gweiToWei: this.web3.utils.toWei('1', 'gwei') // "1000000000"
};
}
// 地址工具
addressTools() {
const address = '0x742d35Cc6634C0532925a3b8Dc2388e46F6E2F8E';
return {
isValidAddress: this.web3.utils.isAddress(address), // true
checkAddressChecksum: this.web3.utils.checkAddressChecksum(address),
toChecksumAddress: this.web3.utils.toChecksumAddress(address.toLowerCase())
};
}
// 哈希函数
hashFunctions() {
const message = 'Hello Web3';
return {
keccak256: this.web3.utils.keccak256(message),
sha3: this.web3.utils.sha3(message),
soliditySha3: this.web3.utils.soliditySha3(
{ type: 'string', value: message }
)
};
}
// 编码解码
encodingDecoding() {
const number = 255;
const hexString = '0xff';
return {
numberToHex: this.web3.utils.numberToHex(number), // "0xff"
hexToNumber: this.web3.utils.hexToNumber(hexString), // 255
utf8ToHex: this.web3.utils.utf8ToHex('Hello'), // "0x48656c6c6f"
hexToUtf8: this.web3.utils.hexToUtf8('0x48656c6c6f') // "Hello"
};
}
// 随机数生成
randomGeneration() {
return {
randomHex: this.web3.utils.randomHex(32), // 32字节随机十六进制
randomBytes: Array.from(this.web3.utils.randomBytes(16)) // 16字节随机数组
};
}
}
4. 实际应用示例
完整的 DApp 集成示例
javascript
class CompleteDApp {
constructor(provider) {
this.web3 = new Web3(provider);
this.accountManager = new AccountManager(this.web3);
this.blockchainInteractor = new BlockchainInteractor(this.web3);
this.transactionHandler = new TransactionHandler(this.web3);
this.contractInteractor = new ContractInteractor(this.web3);
this.utils = new Web3Utils(this.web3);
this.currentAccount = null;
}
// 连接钱包
async connectWallet() {
try {
if (window.ethereum) {
// 请求账户访问
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
this.currentAccount = accounts[0];
console.log("连接的账户:", this.currentAccount);
// 监听账户变化
window.ethereum.on('accountsChanged', (accounts) => {
this.currentAccount = accounts[0] || null;
console.log("账户切换为:", this.currentAccount);
});
// 监听网络变化
window.ethereum.on('chainChanged', (chainId) => {
console.log("网络切换:", parseInt(chainId));
window.location.reload();
});
return { success: true, account: this.currentAccount };
} else {
return { success: false, error: "请安装 MetaMask" };
}
} catch (error) {
return { success: false, error: error.message };
}
}
// 获取完整账户信息
async getAccountInfo() {
if (!this.currentAccount) {
return { error: "请先连接钱包" };
}
try {
const [balance, transactionCount, chainId] = await Promise.all([
this.blockchainInteractor.getBalance(this.currentAccount),
this.transactionHandler.getTransactionCount(this.currentAccount),
this.web3.eth.getChainId()
]);
return {
address: this.currentAccount,
balance: balance.formatted,
transactionCount: transactionCount,
networkId: chainId,
isConnected: true
};
} catch (error) {
return { error: error.message };
}
}
// 完整的代币转账流程
async tokenTransfer(tokenContractAddress, tokenABI, toAddress, amount, decimals = 18) {
if (!this.currentAccount) {
return { success: false, error: "请先连接钱包" };
}
try {
// 初始化代币合约
const tokenContract = this.contractInteractor.initContract(tokenContractAddress, tokenABI);
// 获取代币余额
const balance = await this.contractInteractor.callContractMethod(
tokenContractAddress,
'balanceOf',
[this.currentAccount]
);
// 检查余额是否足够
const amountInWei = BigInt(amount) * (10n ** BigInt(decimals));
if (BigInt(balance) < amountInWei) {
return { success: false, error: "代币余额不足" };
}
// 执行转账(这里需要私钥,实际应用中应该使用钱包签名)
// const result = await this.contractInteractor.sendContractTransaction(
// tokenContractAddress,
// 'transfer',
// [toAddress, amountInWei.toString()],
// this.currentAccount,
// privateKey
// );
// 在实际 DApp 中,应该使用钱包签名而不是直接使用私钥
const transferData = tokenContract.methods.transfer(toAddress, amountInWei.toString()).encodeABI();
return {
success: true,
message: "准备好转账",
data: transferData,
from: this.currentAccount,
to: tokenContractAddress,
value: "0",
gasLimit: await tokenContract.methods.transfer(toAddress, amountInWei.toString()).estimateGas({ from: this.currentAccount })
};
} catch (error) {
return { success: false, error: error.message };
}
}
}
5. 错误处理和最佳实践
javascript
class Web3BestPractices {
constructor(web3) {
this.web3 = web3;
}
// 错误处理
handleCommonErrors(error) {
const errorMessages = {
'User denied transaction signature': '用户取消了交易',
'insufficient funds for transfer': '余额不足',
'nonce too low': 'nonce 值过低',
'gas required exceeds allowance': 'Gas 不足',
'execution reverted': '交易执行失败',
'invalid address': '无效的地址格式'
};
for (const [key, value] of Object.entries(errorMessages)) {
if (error.message.includes(key)) {
return value;
}
}
return error.message;
}
// 安全检查
securityChecks(address, amount) {
const checks = {
isValidAddress: this.web3.utils.isAddress(address),
isZeroAddress: address === '0x0000000000000000000000000000000000000000',
isAmountValid: amount > 0 && !isNaN(amount),
isContractAddress: this.isContract(address) // 需要异步检查
};
return checks;
}
async isContract(address) {
try {
const code = await this.web3.eth.getCode(address);
return code !== '0x';
} catch (error) {
return false;
}
}
// Gas 优化策略
async optimizeGas(transactionObject) {
try {
// 估算 Gas
const estimatedGas = await this.web3.eth.estimateGas(transactionObject);
// 获取当前 Gas 价格
const currentGasPrice = await this.web3.eth.getGasPrice();
// 添加缓冲(10%)
const bufferedGas = Math.floor(estimatedGas * 1.1);
return {
gas: bufferedGas,
gasPrice: currentGasPrice,
maxFeePerGas: currentGasPrice,
maxPriorityFeePerGas: this.web3.utils.toWei('1', 'gwei')
};
} catch (error) {
console.error("Gas 优化失败:", error);
return null;
}
}
}
6. 与 ethers.js 对比
javascript
const web3VsEthers = {
web3js: {
advantages: [
"官方以太坊库",
"功能全面",
"文档完善",
"社区庞大"
],
disadvantages: [
"包体积较大",
"API 较复杂",
"Tree-shaking 支持一般"
],
useCases: [
"企业级应用",
"需要全面功能",
"传统 Web 开发背景"
]
},
ethersjs: {
advantages: [
"轻量级",
"API 简洁",
"TypeScript 支持好",
"Tree-shaking 友好"
],
disadvantages: [
"功能相对较少",
"某些高级功能缺失"
],
useCases: [
"前端重点的应用",
"需要更好性能",
"现代前端开发"
]
}
};
总结
Web3.js 是以太坊生态中最成熟的 JavaScript 库,提供了:
- 完整的区块链交互 - 账户、交易、合约、事件
- 丰富的工具函数 - 单位转换、哈希计算、编码解码
- 多种连接方式 - HTTP、WebSocket、注入提供者
- 强大的合约支持 - 完整的 ABI 处理、事件监听
- 企业级可靠性 - 经过大量项目验证
虽然新兴的 ethers.js 在某些场景下更有优势,但 Web3.js 凭借其功能全面性和稳定性,仍然是许多企业和大型项目的首选。