uniapp 跨平台开发中集成以太坊全指南,从环境搭建到智能合约交互
随着区块链技术的飞速发展,去中心化应用(DApps)正逐渐成为互联网领域的新热点,而 UniApp 凭借其“一次开发,多端发布”的优势,成为了许多开发者构建跨平台应用的首选,将 UniApp 与以太坊这样的主流区块链平台相结合,可以让我们轻松构建能够与区块链交互的跨平台 DApps,本文将详细介绍如何在 UniApp 项目中调用以太坊,包括环境搭建、钱包连接、数据交互及智能合约调用等关键步骤。
准备工作:开发环境与依赖
在开始之前,我们需要准备以下环境和工具:
- Node.js 和 npm/yarn:确保你的系统已安装 Node.js(建议 LTS 版本)和相应的包管理器。
- HBuilderX:UniApp 的官方 IDE,提供了完善的开发、调试和打包功能。
- MetaMask 钱包插件:在浏览器(如 Chrome、Firefox)中安装 MetaMask 扩展,这是与以太坊交互最常用的钱包,在 UniApp 的 H5 端调试时,浏览器需要安装 MetaMask。
- 以太坊节点或 Infura/Alchemy 等 RPC 服务:你需要连接到一个以太坊节点来读取链上数据和发送交易,可以使用本地节点(如 Geth),但对于开发来说,使用 Infura 或 Alchemy 提供的免费 RPC 服务更为便捷。
核心库的选择与集成
在 UniApp 中调用以太坊,通常依赖于 JavaScript 库,目前最主流和推荐的是 web3.js (v4.x+) 或 ethers.js,两者功能强大,但 ethers.js 以其更现代的 API、更小的体积和更好的 TypeScript 支持逐渐受到青睐,本文将以 ethers.js 为例进行讲解。
-
安装 ethers.js: 在你的 UniApp 项目根目录下,打开终端,运行以下命令安装
ethers.js:npm install ethers # 或者 yadd add ethers
-
配置 uni-app 项目以支持 npm: 在 HBuilderX 中,右键点击项目根目录,选择“显示 npm 模块”,然后确保
ethers已正确显示,如果项目没有node_modules文件夹,HBuilderX 可能会提示你构建 npm。
连接以太坊钱包(以 MetaMask 为例)
DApp 与区块链交互的第一步通常是连接用户的钱包,在 UniApp 的 H5 端,我们可以通过 window.ethereum 对象(由 MetaMask 注入)来实现。
-
检查并请求账户授权: 在页面的 JavaScript 代码中,你可以编写如下函数来连接钱包:
// 在 pages/index/index.vue 或其他页面中 async connectWallet() { try { // 检查浏览器是否安装了以太坊提供者(如 MetaMask) if (window.ethereum) { // 请求用户授权连接钱包 const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); if (accounts.length > 0) { this.currentAccount = accounts[0]; console.log('已连接钱包:', this.currentAccount); // 可以在这里初始化 web3 或 ethers provider this.initProvider(); } } else { uni.showToast({ title: '请安装 MetaMask 钱包', icon: 'none' }); } } catch (error) { console.error('连接钱包失败:', error); uni.showToast({ title: '连接钱包失败', icon: 'none' }); } }, -
初始化 Ethers Provider: 连接成功后,我们可以使用
window.ethereum创建一个 Ethers.js 的Provider对象,这是与以太坊网络交互的入口。initProvider() { if (window.ethereum) { // 使用 Ethers.js 的 BrowserProvider 包装 window.ethereum this.provider = new ethers.BrowserProvider(window.ethereum); console.log('Ethers Provider 初始化成功:', this.provider); } },
注意:
-
在 App 端和小程序端,由于浏览器环境的限制,直接使用
window.ethereum是不可行的,通常需要集成第三方 SDK(如 uni-web3-sdk 或 uni-ethers,这类 SDK 可能需要封装原生插件或提供特定的适配方案)或引导用户使用外部浏览器(如 DAppBrowser)打开链接进行交互,这部分相对复杂,可能需要根据具体平台进行适配。 -
处理账户变化:监听
accountsChanged事件,以便在用户切换账户时更新应用状态。// 在 initProvider 中或连接成功后添加 if (window.ethereum) { window.ethereum.on('accountsChanged', (accounts) => { if (accounts.length === 0) { // 用户断开了连接 this.currentAccount = null; console.log('用户已断开钱包连接'); } else { // 用户切换了账户 this.currentAccount = accounts[0]; console.log('用户切换账户至:', this.currentAccount); } }); }
读取以太坊链上数据
有了 Provider 对象,我们就可以轻松读取以太坊链上的数据,例如获取账户余额、查询区块信息、调用只读的智能合约方法等。
-
获取账户余额:
async getBalance() { if (!this.provider || !this.currentAccount) { uni.showToast({ title: '请先连接钱包', icon: 'none' }); return; } try { const balance = await this.provider.getBalance(this.currentAccount); // ethers.js 的 BigNumber 需要转换为可读格式,如 Ether const balanceInEther = ethers.formatEther(balance); this.balance = balanceInEther; console.log('账户余额:', balanceInEther, 'ETH'); } catch (error) { console.error('获取余额失败:', error); } }, -
查询智能合约只读方法: 假设我们有一个已部署的智能合约,我们可以使用
Contract对象来调用其只读(view或pure)方法。// 假设这是你的智能合约 ABI(Application Binary Interface)的一部分 const contractABI = [ // 一个获取某个地址代币余额的函数 "function balanceOf(address owner) view returns (uint256)" ]; // 合约地址 const contractAddress = "0x...YourContractAddress..."; async queryContractReadMethod() { if (!this.provider) { uni.showToast({ title: 'Provider 未初始化', icon: 'none' }); return; } try { const contract = new ethers.Contract(contractAddress, contractABI, this.provider); const balance = await contract.balanceOf(this.currentAccount); const formattedBalance = ethers.formatEther(balance); console.log('合约查询结果:', formattedBalance); uni.showToast({ title: `查询成功,余额: ${formattedBalance}`, icon: 'success' }); } catch (error) { console.error('合约查询失败:', error); } },
发送交易与调用智能合约写方法
与区块链进行写交互(如转账、调用智能合约的修改方法)需要用户使用钱包签名并支付 Gas 费。
-
获取签名者 (Signer): 发送交易需要
Signer对象,它代表一个能够签名的账户。async getSigner() { if (!this.provider) { uni.showToast({ title: 'Provider 未初始化', icon: 'none' }); return null; } try { const signer = await this.provider.getSigner(); console.log('Signer 获取成功:', signer); return signer; } catch (error) { console.error('获取 Signer 失败:', error); return null; } }, -
发送以太坊转账:
async sendTransaction(toAddress, amountInEther) { if (!this.currentAccount) { uni.showToast({ title: '请先连接钱包', icon: 'none' }); return; } try { const signer = await this.getSigner(); if (!signer) return; const tx = { to: toAddress, value: ethers.parseEther(amountInEther) // 将 ETH 转换为 wei }; const txResponse = await signer.sendTransaction(tx); console.log('交易已发送,等待确认:', txResponse.hash); uni.showLoading({ title: '交易中,请稍候...' }); // 等待交易被确认 const txReceipt