Web3.js速成:新手友好型以太坊DApp开发指南
以太坊 Web3.js 开发教程
简介
Web3.js 是一个功能强大的 JavaScript 库,它充当了您的应用程序与以太坊区块链之间的桥梁,允许您与本地或远程的以太坊节点无缝交互。它提供了一套全面的应用程序编程接口(API),极大地简化了与以太坊区块链的各种操作。 使用 Web3.js,您可以轻松地执行关键任务,例如部署智能合约到区块链上,调用智能合约中定义的函数来读取或修改数据,安全地发送以太币(Ether)进行交易,以及实时监听区块链上发生的特定事件。这些事件可能表示合约状态的改变、交易的确认或其他相关的链上活动。 本教程旨在引导您逐步了解 Web3.js 的使用,最终目标是帮助您构建一个简单但功能完备的以太坊应用程序。您将学习如何设置 Web3.js 环境、连接到以太坊网络以及使用其核心功能与智能合约交互,为后续开发更复杂的去中心化应用(DApps)打下坚实的基础。理解 Web3.js 的工作原理对于任何希望在以太坊生态系统中构建应用程序的开发人员来说至关重要。
前提条件
在开始之前,为了确保开发流程的顺利进行,请确认你的开发环境中已经具备以下必要的工具和环境配置:
-
Node.js 和 npm (或 yarn):
- Node.js: 作为一个基于 Chrome V8 引擎的 JavaScript 运行环境,Node.js 使得我们可以在服务器端运行 JavaScript 代码,这对于构建前端工具链和运行智能合约的测试脚本至关重要。建议安装最新稳定版本,以获得最佳的性能和安全性。
- npm (Node Package Manager) 或 yarn: 作为 Node.js 的默认包管理器,npm 能够帮助我们轻松安装、更新和管理项目依赖的各种软件包。Yarn 是另一个流行的包管理器,它在性能和依赖管理方面提供了一些优化。你可以选择其中一个使用,两者功能类似。你需要使用它们来安装 web3.js 或者 ethers.js 等以太坊开发库。
-
Ganache (可选):
- Ganache: 这是一个快速且便捷的工具,用于在本地计算机上搭建一个私有的以太坊测试网络。它模拟了真实的以太坊环境,允许你免费部署和测试智能合约,而无需支付实际的 gas 费用。对于开发初期和调试阶段,Ganache 是一个非常有价值的工具。你可以选择 Ganache UI 版本,拥有更友好的界面。
- 替代方案: 如果不想使用 Ganache,你也可以选择连接到公共的以太坊测试网络,例如 Goerli 或 Sepolia。这些测试网络允许你使用真实的以太坊环境进行测试,但需要获取测试用的 ETH (通常可以通过水龙头网站免费获取)。
-
MetaMask (可选):
- MetaMask: 这是一个浏览器扩展程序,充当了你与以太坊区块链之间的桥梁。它可以管理你的以太坊账户,并安全地与去中心化应用程序 (DApp) 进行交互。通过 MetaMask,你可以签署交易、发送 ETH 或其他代币,以及与智能合约进行交互。
- 作用: 虽然 MetaMask 主要用于与部署在公共测试网或主网上的 DApp 交互,但它也可以连接到本地的 Ganache 网络,方便你测试和调试 DApp 的前端界面。
环境搭建
-
创建项目目录:
你需要创建一个专门用于存放 Web3.js 教程相关文件的项目目录。这能帮助你更好地组织代码和依赖项。使用以下命令创建目录并进入该目录:
mkdir web3js-tutorial cd web3js-tutorial
mkdir
命令用于创建名为 `web3js-tutorial` 的新目录。cd
命令用于更改当前工作目录到新创建的 `web3js-tutorial` 目录中。 -
初始化项目:
接下来,你需要初始化项目,以便使用 npm 管理依赖。运行以下命令:
npm init -y
npm init -y
命令会创建一个 `package.` 文件,其中包含了项目的元数据,如名称、版本和依赖项。 `-y` 标志会自动接受所有默认选项,从而简化初始化过程。 你可以稍后根据需要修改 `package.` 文件。
账户创建或导入
在开始与区块链交互之前,你需要一个以太坊账户。可以使用MetaMask、Remix IDE,或者通过编程方式生成或导入账户。如果使用MetaMask,请确保已正确安装并连接到目标网络(如测试网络Goerli或主网络Mainnet)。
如果选择编程方式,可以使用Web3.js的
web3.eth.accounts.create()
方法创建新账户,或使用
web3.eth.accounts.privateKeyToAccount()
方法从私钥导入现有账户。
务必妥善保管你的私钥,切勿泄露给他人。
项目初始化
使用
yarn init -y
命令初始化一个新的Node.js项目。这会创建一个
package.
文件,用于管理项目的依赖和元数据。
-y
标志表示使用默认配置,避免交互式提示。
示例:
yarn init -y
安装 Web3.js
Web3.js是一个用于与以太坊区块链交互的JavaScript库。使用npm或yarn安装Web3.js到你的项目依赖中。建议安装最新版本的Web3.js,以便使用最新的功能和安全补丁。
使用npm:
npm install web3
或者,使用yarn:
yarn add web3
安装完成后,你就可以在你的JavaScript代码中引入Web3.js,并开始与以太坊网络进行交互,例如连接到区块链节点、创建交易、部署智能合约等。
示例代码:
const Web3 = require('web3');
// 连接到Ganache或其他以太坊节点
const web3 = new Web3('http://localhost:8545');
// 打印当前连接的区块链ID
web3.eth.net.getId().then(console.log);
或者
在你的项目中安装 Web3.js 库,你需要使用包管理器。Yarn 是一个流行的选择,可以高效地管理项目依赖。
使用 Yarn 安装 Web3.js 的命令如下:
yarn add web3
此命令会将 Web3.js 添加到你的项目依赖中,并且更新
package.
文件。安装完成后,你就可以在你的 JavaScript 代码中引入 Web3.js 并开始与以太坊区块链进行交互。
连接到以太坊节点
与以太坊区块链交互的第一步是创建一个 Web3 实例,并将其连接到可访问的以太坊节点。 这可以通过多种方式实现,包括连接到本地运行的 Ganache 实例、使用 Infura 或 Alchemy 等远程节点提供商,或者连接到你自己的完整以太坊节点。
以下代码展示了如何在 JavaScript 中实例化 Web3 对象,并连接到不同的以太坊节点:
const Web3 = require('web3');
连接到 Ganache 本地网络:
Ganache 是一个用于本地以太坊开发的流行工具,它提供了一个快速、私有的区块链,用于测试和调试你的智能合约。默认情况下,Ganache 在
http://127.0.0.1:7545
监听。
// 连接到 Ganache 本地网络
const web3 = new Web3('http://127.0.0.1:7545');
连接到 Sepolia 测试网络(使用 Infura 或 Alchemy):
Infura 和 Alchemy 是流行的以太坊节点基础设施提供商,它们允许你连接到以太坊主网和测试网络,而无需运行自己的节点。 要使用 Infura 或 Alchemy,你需要注册一个账户并获取一个 API 密钥。
// 连接到 Sepolia 测试网络 (需要 Infura 或 Alchemy API 密钥)
// const web3 = new Web3('https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY');
// const web3 = new Web3('https://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY');
请务必将
YOUR_INFURA_API_KEY
或
YOUR_ALCHEMY_API_KEY
替换为你自己的 API 密钥。
在浏览器中使用 Web3 (MetaMask):
如果在浏览器环境中使用 Web3,通常情况下,MetaMask 等浏览器扩展会注入一个
web3
对象到页面中。你需要检查
window.ethereum
是否存在,以确定是否已经注入了 Web3 提供程序。 如果存在,则需要请求用户授权你的应用程序访问他们的以太坊账户。
let web3;
if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
// Modern dapp browsers...
try {
// Request account access if needed
await window.ethereum.request({ method: "eth_requestAccounts" });
// Acccounts now exposed
web3 = new Web3(window.ethereum);
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
} else if (typeof window !== 'undefined' && typeof window.web3 !== 'undefined') {
// Legacy dapp browsers...
web3 = new Web3(window.web3.currentProvider);
} else {
// Fallback to localhost; use dev console port by default...
const provider = new Web3.providers.HttpProvider(
"http://127.0.0.1:7545"
);
web3 = new Web3(provider);
console.log("No web3 instance injected, using Local web3.");
}
这段代码首先检查
window.ethereum
是否存在。如果存在,它会请求用户授权你的应用程序访问他们的账户。 然后,它使用
window.ethereum
创建一个新的 Web3 实例。如果
window.ethereum
不存在,它会尝试使用旧的
window.web3
对象,如果两者都不存在,它会回退到连接到本地 Ganache 实例。
日志输出 Web3 版本:
为了验证 Web3 是否已成功连接到以太坊节点,你可以打印 Web3 对象的版本:
console.log('Web3 版本:', web3.version);
获取账户
现在你可以使用 Web3.js 获取以太坊账户的地址。
javascript async function getAccounts() { const accounts = await web3.eth.getAccounts(); console.log('账户:', accounts); return accounts; }
getAccounts();
这个函数会打印出连接的以太坊节点上的可用账户。
获取余额
你可以使用 Web3.js 这类与以太坊区块链交互的 JavaScript 库,获取指定账户的以太币余额。Web3.js 提供了一系列方法,允许你连接到以太坊节点并查询链上数据,例如账户余额。
以下 JavaScript 代码示例展示了如何利用 Web3.js 获取账户余额并将其从 Wei 转换为 Ether。Wei 是以太币的最小单位,而 Ether 是更常用的单位。转换有助于提高可读性。
async function getBalance(account) {
try {
const balance = await web3.eth.getBalance(account);
if (balance) {
const etherBalance = web3.utils.fromWei(balance, 'ether');
console.log(`账户 ${account} 的余额: ${etherBalance} ETH`);
return etherBalance; // 可以选择返回余额
} else {
console.error(`无法获取账户 ${account} 的余额`);
return null;
}
} catch (error) {
console.error("获取余额时发生错误:", error);
return null;
}
}
上述代码段定义了一个名为
getBalance
的异步函数,该函数接受一个账户地址作为参数。 它使用
web3.eth.getBalance()
方法从以太坊节点检索指定账户的余额(以 Wei 为单位)。 如果成功获取到余额,它会使用
web3.utils.fromWei()
方法将其转换为 Ether,然后将结果输出到控制台。 为了处理潜在的错误,我们使用了
try...catch
块。如果获取余额的过程中发生任何错误(例如,节点连接问题或无效的账户地址),错误消息将被记录到控制台。 为了增强可用性,该函数现在返回余额,以便在其他函数或组件中使用。
以下代码演示了如何调用
getBalance
函数,使用
getAccounts()
函数获取连接的以太坊账户数组中的第一个账户,并将其余额打印到控制台。这需要你的Web3 Provider已经连接到了以太坊网络。
web3.eth.getAccounts()
.then(accounts => {
if (accounts.length > 0) {
getBalance(accounts[0]);
} else {
console.log("没有可用的以太坊账户.");
}
})
.catch(error => {
console.error("获取账户时发生错误:", error);
});
这段代码首先调用
web3.eth.getAccounts()
来获取当前连接的以太坊钱包中的账户列表。然后,它检查账户列表是否为空。如果存在账户(即
accounts.length > 0
),它会调用先前定义的
getBalance()
函数,并将列表中的第一个账户 (
accounts[0]
) 作为参数传递给它。如果账户列表为空,它会在控制台中输出一条消息,指示没有可用的以太坊账户。它还包括一个错误处理块(
.catch()
),用于捕获在获取账户过程中可能发生的任何错误,并将错误消息记录到控制台中。 对错误进行适当的检查和处理对于健壮的应用程序至关重要。
部署智能合约
要将编写好的智能合约部署到区块链上,首先需要对合约的源代码进行编译。编译过程会将高级编程语言(如 Solidity)编写的合约代码转换为区块链虚拟机(例如以太坊的 EVM)可以执行的字节码 (bytecode)。同时,还会生成一份应用程序二进制接口 (ABI),用于描述合约的函数、事件以及数据结构,方便外部应用与合约进行交互。可以使用 Remix IDE 这种集成开发环境进行在线编译,也可以使用 solc 命令行编译器或其他专业的开发工具。
假设你已经使用
solc
编译器或 Remix IDE 成功编译了一个名为
SimpleStorage.sol
的智能合约。编译完成后,你会得到该合约的 ABI 和 bytecode。ABI 通常是一个 JSON 格式的数组,描述了合约的接口,而 bytecode 则是合约的二进制代码,会被存储到区块链上。
javascript // 智能合约 ABI 和 bytecode,这只是一个简单的示例,实际的 ABI 和 bytecode 会更长更复杂 const abi = [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor", "payable": false, "signature": "constructor" }, { "inputs": [], "name": "get", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function", "constant": true, "signature": "0x6d4ce63c" }, { "inputs": [ { "internalType": "uint256", "name": "_x", "type": "uint256" } ], "name": "set", "outputs": [], "stateMutability": "nonpayable", "type": "function", "payable": false, "signature": "0x60fe47b1" } ]; const bytecode = '0x608060405234801561001057600080fd5b506004361061002b5760003560e01c806360fe47b1146100305780636d4ce63c1461004e575b600080fd5b61003861006c565b6040518082815260200191505060405180910390f35b61005661006c565b60005481565b600080fd5b6000819050919050565b600080fd5b600081905091905056';
async function deployContract(account) { // 使用 web3.js 创建合约实例,传入 ABI const contract = new web3.eth.Contract(abi);
// 使用合约实例的 deploy 方法,传入 bytecode const deployment = contract.deploy({ data: bytecode });
// 使用 send 方法发送交易来部署合约 const contractInstance = await deployment.send({ from: account, // 部署合约的账户地址 gas: 4700000, // 交易的 gas 限制,根据合约复杂程度调整 gasPrice: '1500000000', // 交易的 gas 价格,单位是 wei });
// 打印合约地址,合约部署成功后,会返回合约实例,从中可以获取合约的地址 console.log('合约地址:', contractInstance.options.address); return contractInstance; }
getAccounts().then(accounts => { // 调用 deployContract 函数,传入第一个账户地址 deployContract(accounts[0]); });
这段 JavaScript 代码使用 web3.js 库创建了一个智能合约实例,并通过
deploy()
方法来准备部署合约。
deploy()
方法会将合约的 bytecode 打包成一个部署交易。然后,使用
send()
方法将部署交易发送到区块链网络。你需要提供部署者的账户地址(
from
),并仔细设置
gas
限制和
gasPrice
。Gas 限制设置得太低可能会导致交易失败,而 gasPrice 越高,交易被矿工打包的速度就越快。部署成功后,控制台会输出合约的地址,这个地址是合约在区块链上的唯一标识。请注意,实际部署时,请根据网络拥堵情况合理设置 gasPrice,并确保账户有足够的余额支付 gas 费用。
调用智能合约函数
一旦智能合约成功部署到区块链网络,例如以太坊,你就可以与它进行交互并调用其定义的函数。这通常通过使用Web3库或其他类似的以太坊开发库来实现,这些库提供了与智能合约交互所需的API。
以下 JavaScript 代码片段演示了如何使用 Web3.js 库调用智能合约的函数。请注意,这仅仅是一个示例,你需要根据你的合约 ABI(应用程序二进制接口)、合约地址以及你使用的区块链环境(如 Ganache、Rinkeby、Mainnet 等)进行相应的调整。
async function interactWithContract(contractInstance, account) {
// 调用 `set` 函数,设置合约中的值为 123。
// `send()` 方法用于发送交易,因此需要支付 Gas 费用。
// `from` 指定交易的发送者账户,`gas` 指定 Gas 限制。
await contractInstance.methods.set(123).send({
from: account,
gas: 4700000 // 建议根据合约的复杂度调整 Gas Limit
});
// 调用 `get` 函数,获取合约中存储的值。
// `call()` 方法用于读取数据,不需要发送交易,因此无需支付 Gas 费用。
const value = await contractInstance.methods.get().call();
console.log('合约中的值:', value);
}
上面的
interactWithContract
函数接受一个合约实例
contractInstance
和一个账户地址
account
作为参数。它首先调用合约的
set
函数,将合约中的值设置为 123。 然后,它调用
get
函数来读取合约中存储的当前值,并将该值打印到控制台。
在调用这些函数之前,你需要先获取一个账户,并部署你的合约。以下是调用
deployContract
函数部署合约,并在部署成功后调用
interactWithContract
函数的示例:
web3.eth.getAccounts().then(accounts => {
deployContract(accounts[0]).then(contractInstance => {
interactWithContract(contractInstance, accounts[0]);
});
});
在这个示例中,
getAccounts()
函数用于获取可用的以太坊账户。 然后,使用第一个账户 (
accounts[0]
) 来部署合约,并在合约部署完成后,将合约实例传递给
interactWithContract
函数,以便与该合约进行交互。请确保
deployContract
函数已正确定义,并且能够正确部署你的智能合约。
这段代码演示了与已部署的智能合约进行基本交互的过程,包括如何调用合约的
set
函数来修改状态,以及如何调用
get
函数来读取状态。 在实际应用中,你需要根据你的合约接口和逻辑进行相应的调整。 例如,你可能需要处理更复杂的数据类型,或者处理交易的确认和错误。
监听事件
智能合约的核心功能之一是其与外部世界交互的能力。事件(Events)是智能合约提供的一种关键机制,允许合约在特定状态改变时发出通知。你的去中心化应用 (DApp) 可以注册监听这些事件,并根据事件的内容和类型采取相应的行动,从而实现链上和链下世界的有效连接。
事件监听是构建响应式 DApp 的基础。通过监听事件,你的 DApp 可以实时更新用户界面、执行链下计算、触发其他智能合约的调用,以及执行其他各种操作,从而提供更动态和用户友好的体验。
下面是一个使用 JavaScript 和 Web3.js 库监听智能合约事件的示例代码:
javascript
async function listenToEvents(contractInstance) {
// 监听名为 'MyEvent' 的事件,并从区块 0 开始监听所有历史事件。
contractInstance.events.MyEvent({
fromBlock: 0 // 可以指定开始监听的区块高度,0 表示从创世区块开始。
}, function(error, event){
// 初始事件处理函数:处理来自区块链的原始事件数据。
if (error) {
console.error("监听事件出错:", error);
return;
}
console.log("原始事件数据:", event);
})
.on('data', function(event){
// 'data' 事件处理器:当接收到新事件数据时触发。注意:一些 Web3.js 版本可能会触发两次,请根据你的库版本进行处理。
console.log("事件数据 (data):", event); // 相同的事件可能会被触发两次,取决于 Web3.js 的版本和配置。
// 在这里编写处理事件数据的逻辑,例如更新 UI 或执行其他操作。
})
.on('changed', function(event){
// 'changed' 事件处理器:当事件由于区块链重组(reorganization)而被从区块链中删除时触发。
// 这种情况相对罕见,但你的 DApp 应该能够处理这种情况,以保持数据一致性。
console.log("事件被移除 (changed):", event);
// 在这里编写处理事件被移除的逻辑,例如回滚 UI 更新或撤销之前的操作。
})
.on('error', function(error){
// 'error' 事件处理器:当监听过程中发生错误时触发。
console.error("事件监听出错 (error):", error);
// 在这里编写处理错误的逻辑,例如记录错误日志或通知用户。
});
}
请注意,你需要将
MyEvent
替换为你智能合约中实际定义的事件名称。事件名称区分大小写,必须与合约中定义的事件名称完全匹配。 确保你的合约实例
contractInstance
已经正确初始化,并且连接到了正确的区块链网络。
除了
fromBlock
选项之外,还可以使用其他选项来过滤事件,例如
filter
选项,允许你根据事件参数的值来筛选事件。请查阅 Web3.js 文档以获取更多关于事件监听选项的信息。
本教程只是一个 Web3.js 开发的入门指南。 Web3.js 提供了更多功能,可以用来构建更复杂的 DApp。 建议你阅读 Web3.js 的官方文档以了解更多信息。