Web3.js 入门:如何调用以太坊智能合约与交互**
在区块链的世界里,以太坊作为领先的智能合约平台,允许开发者构建去中心化应用(DApps),而要在前端应用(如网页)与以太坊网络进行交互,Web3.js 无疑是最核心、最常用的工具之一,本文将带你深入了解 Web3.js,并展示如何使用它来调用以太坊网络上的智能合约。
什么是 Web3.js
Web3.js 是一个 JavaScript 库,它封装了与以太坊节点进行通信的 JSON-RPC API,它就像一座桥梁,让你的网页应用能够“读懂”以太坊的语言,并与区块链上的数据进行交互,例如读取账户余额、查询智能合约状态、发起交易、甚至部署新的智能合约等。
Web3.js 的最新版本是 4.x(目前也有 1.x 的旧版本仍在使用,但新项目推荐使用 4.x),它提供了更现代、更模块化的 API 设计。
准备工作:在项目中引入 Web3.js
在使用 Web3.js 之前,你需要将其集成到你的项目中,如果你使用 npm 或 yarn:
npm install web3yarn add web3
然后在你的 JavaScript 文件中引入:
import Web3 from 'web3';
// 或者对于 CommonJS
// const Web3 = require('web3');
连接到以太坊网络
Web3.js 需要通过一个以太坊节点来与以太坊网络通信,你可以选择以下几种方式连接:
- 连接到本地节点:如果你在本地运行了以太坊客户端(如 Geth, Parity),节点通常运行在
http://localhost:8545。 - 连接到公共节点:Infura、Alchemy 等服务提供商提供公共的以太坊节点 RPC URL,你需要在它们的官网上注册获取 API Key。
- 连接到浏览器钱包:如 MetaMask,当用户安装了 MetaMask 并授权你的网站访问时,你可以通过
window.ethereum对象连接到用户的钱包节点,这实际上是通过 MetaMask 提供的节点服务。
示例:连接到 MetaMask 钱包
let web3;
// 检查是否安装了 MetaMask
if (typeof window.ethereum !== 'undefined') {
console.log('MetaMask is installed!');
// 使用 MetaMask 提供的 provider
web3 = new Web3(window.ethereum);
// 请求用户授权账户
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (error) {
console.error('User denied account access');
}
} else {
console.log('MetaMask is not installed. You should consider installing it!');
// 可以在这里提供连接到其他节点的选项,或提示用户安装
// 连接到 Infura 节点(需要替换为你的 Infura URL)
// const infuraUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
// web3 = new Web3(new Web3.providers.HttpProvider(infuraUrl));
}
调用智能合约
调用智能合约是 Web3.js 最核心的功能之一,这通常包括两个主要操作:
- 读取合约状态(常量函数):这些函数不会修改区块链上的状态,因此不需要用户签名交易,可以直接调用。
- 写入合约状态(非常量函数):这些函数会修改区块链上的状态,需要构建一笔交易,并由用户签名后发送到以太坊网络进行打包。
步骤 1:获取智能合约的 ABI 和地址
在调用智能合约之前,你需要知道:
- ABI (Application Binary Interface):这是智能合约的接口描述,定义了合约有哪些函数、参数类型、返回值类型等,通常在编译智能合约时(使用 Solidity 和 Remix IDE、Truffle、Hardhat 等工具)会生成 ABI 文件(通常是 JSON 格式)。
- 合约地址:这是智能合约部署到以太坊网络后的唯一标识符。
步骤 2:创建合约实例
有了 ABI 和合约地址后,你可以创建一个合约实例:
// 假设你已经有了 ABI 和合约地址
const contractABI = [
// 这里是 ABI 的 JSON 数组,
{
"inputs": [],
"name": "get",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"internalType": "uint256", "name": "_x", "type": "uint256"}],
"name": "set",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
// ... 其他 ABI 条目
];
const contractAddress = '0x1234567890123456789012345678901234567890'; // 替换为你的合约地址
// 创建合约实例
const contract = new web3.eth.Contract(contractABI, contractAddress);
步骤 3:调用合约函数
示例 1:读取合约状态(调用 get() 函数)
async function getContractValue() {
try {
// 调用 get 函数,注意不需要交易参数
const result = await contract.methods.get().call();
console.log('Contract value is:', result.toString());
return result;
} catch (error) {
console.error('Error calling get function:', error);
}
}
getContractValue();
示例 2:写入合约状态(调用 set() 函数)
async function setContractValue(newValue) {
try {
// 获取当前账户(通常是 MetaMask 首选账户)
const accounts = await web3.eth.getAccounts();
const fromAccount = accounts[0];
// 调用 set 函数,并指定发送交易的账户
const tx = contract.methods.set(newValue);
// 估算 gas(可选)
const gas = await tx.estimateGas({ from: fromAccount });
// 发送交易
const receipt = await tx.send({
from: fromAccount,
gas: gas,
// gasPrice: web3.utils.toWei('20', 'gwei'), // 可选,指定 gas 价格
});
console.log('Transaction hash:', receipt.transactionHash);
console.log('Transaction was mined in block:', receipt.blockNumber);
console.log('New contract value should be:', newValue);
} catch (
error) {
console.error('Error calling set function:', error);
}
}
// 调用写入函数,例如设置值为 42
// setContractValue(42);
其他常用 Web3.js 功能
除了调用智能合约,Web3.js 还提供了许多其他实用功能:
-
获取账户余额:
async function getBalance(address) { const balance = await web3.eth.getBalance(address); console.log(`Balance of ${address}: ${web3.utils.fromWei(balance, 'ether')} ETH`); return balance; } -
发送以太坊转账:
async function sendEth(fromAccount, toAddress, amountInEther) { const amountInWei = web3.utils.toWei(amountInEther.toString(), 'ether'); const tx = { from: fromAccount, to: toAddress, value: amountInWei, }; const receipt = await web3.eth.sendTransaction(tx); console.log('ETH sent, transaction hash:', receipt.transactionHash); } -
监听事件:
// 假设合约有一个名为 ValueChanged 的事件 contract.events.ValueChanged({ fromBlock: 'latest' // 从最新区块开始监听 }, function(error, event) { if (error) { console.error('Error listening to event:', error); } else { console.log('ValueChanged event:', event.returnValues); } }) .on('data', event => { console.log('New event data:', event); // 单个事件 }) .on('changed', changedEvent => { console.log('Changed event:', changedEvent); // 过滤掉的事件 }) .on('error', err => { console.error('Event error:', err); });
注意事项
- Gas 费用:所有修改区块链状态的操作都需要支付 Gas 费用,你需要确保调用账户有足够的 ETH 来支付 Gas。
- 网络状态:以太坊网络拥堵时,交易可能需要较长时间才能被打包,Gas 价格也可能较高。
- 安全性:始终验证用户输入,注意智能合约调用的安全性,避免重入攻击等常见漏洞,在前端进行必要的错误处理。
- ABI 的准确性:ABI 必须与实际部署的智能合约完全一致,否则调用会失败。
- **异步