Web3.js 全面解析
Web3.js 全面解析
Web3.js 是以太坊官方推出的 JavaScript 库,用于与以太坊区块链交互。让我们深入解析其架构、核心功能和使用方法。
1. Web3.js 架构概述
核心模块结构
// Web3.js 模块架构
const web3Architecture = {core: {web3: "主入口类",providers: "提供者模块 - 连接区块链",eth: "以太坊区块链交互",net: "网络信息",utils: "工具函数库"},extended: {contracts: "智能合约交互",accounts: "账户管理", personal: "账户操作(已弃用)",bzz: "Swarm去中心化存储",shh: "Whisper协议"}
};
版本演进
// 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. 安装和初始化
安装
# 使用 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>
初始化连接
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 或 AlchemyinitWithInfura(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)
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)
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);}}// 估算交易 Gasasync 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 交易操作
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 智能合约交互
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)
class Web3Utils {constructor(web3) {this.web3 = web3;}// 单位转换unitConversions() {const weiValue = '1000000000000000000'; // 1 ETHreturn {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), // truecheckAddressChecksum: 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), // 255utf8ToHex: 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 集成示例
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. 错误处理和最佳实践
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 {// 估算 Gasconst 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 对比
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 凭借其功能全面性和稳定性,仍然是许多企业和大型项目的首选。
