隨著區(qū)塊鏈技術(shù)的飛速發(fā)展,去中心化應(yīng)用(DApps)正逐漸成為互聯(lián)網(wǎng)領(lǐng)域的新熱點(diǎn),而 UniApp 憑借其“一次開發(fā),多端發(fā)布”的優(yōu)勢,成為了許多開發(fā)者構(gòu)建跨平臺應(yīng)用的首選,將 UniApp 與以太坊這樣的主流區(qū)塊鏈平臺相結(jié)合,可以讓我們輕松構(gòu)建能夠與區(qū)塊鏈交互的跨平臺 DApps,本文將詳細(xì)介紹如何在 UniApp 項(xiàng)目中調(diào)用以太坊,包括環(huán)境搭建、錢包連接、數(shù)據(jù)交互及智能合約調(diào)用等關(guān)鍵步驟。
準(zhǔn)備工作:開發(fā)環(huán)境與依賴
在開始之前,我們需要準(zhǔn)備以下環(huán)境和工具:
- Node.js 和 npm/yarn:確保你的系統(tǒng)已安裝 Node.js(建議 LTS 版本)和相應(yīng)的包管理器。
- HBuilderX:UniApp 的官方 IDE,提供了完善的開發(fā)、調(diào)試和打包功能。
- MetaMask 錢包插件:在瀏覽器(如 Chrome、Firefox)中安裝 MetaMask 擴(kuò)展,這是與以太坊交互最常用的錢包,在 UniApp 的 H5 端調(diào)試時(shí),瀏覽器需要安裝 MetaMask。
- 以太坊節(jié)點(diǎn)或 Infura/Alchemy 等 RPC 服務(wù):你需要連接到一個(gè)以太坊節(jié)點(diǎn)來讀取鏈上數(shù)據(jù)和發(fā)送交易,可以使用本地節(jié)點(diǎn)(如 Geth),但對于開發(fā)來說,使用 Infura 或 Alchemy 提供的免費(fèi) RPC 服務(wù)更為便捷。
核心庫的選擇與集成
在 UniApp 中調(diào)用以太坊,通常依賴于 JavaScript 庫,目前最主流和推薦的是 web3.js (v4.x+) 或 ethers.js,兩者功能強(qiáng)大,但 ethers.js 以其更現(xiàn)代的 API、更小的體積和更好的 TypeScript 支持逐漸受到青睞,本文將以 ethers.js 為例進(jìn)行講解。
-
安裝 ethers.js: 在你的 UniApp 項(xiàng)目根目錄下,打開終端,運(yùn)行以下命令安裝
ethers.js:npm install ethers # 或者 yadd add ethers
-
配置 uni-app 項(xiàng)目以支持 npm: 在 HBuilderX 中,右鍵點(diǎn)擊項(xiàng)目根目錄,選擇“顯示 npm 模塊”,然后確保
ethers已正確顯示,如果項(xiàng)目沒有node_modules文件夾,HBuilderX 可能會(huì)提示你構(gòu)建 npm。
連接以太坊錢包(以 MetaMask 為例)
DApp 與區(qū)塊鏈交互的第一步通常是連接用戶的錢包,在 UniApp 的 H5 端,我們可以通過 window.ethereum 對象(由 MetaMask 注入)來實(shí)現(xiàn)。
-
檢查并請求賬戶授權(quán): 在頁面的 JavaScript 代碼中,你可以編寫如下函數(shù)來連接錢包:
// 在 pages/index/index.vue 或其他頁面中 async connectWallet() { try { // 檢查瀏覽器是否安裝了以太坊提供者(如 MetaMask) if (window.ethereum) { // 請求用戶授權(quán)連接錢包 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創(chuàng)建一個(gè) Ethers.js 的Provider對象,這是與以太坊網(wǎng)絡(luò)交互的入口。initProvider() { if (window.ethereum) { // 使用 Ethers.js 的 BrowserProvider 包裝 window.ethereum this.provider = new ethers.BrowserProvider(window.ethereum); console.log('Ethers Provider 初始化成功:', this.provider); } },
注意:
-
在 App 端和小程序端,由于瀏覽器環(huán)境的限制,直接使用
window.ethereum是不可行的,通常需要集成第三方 SDK(如 uni-web3-sdk 或 uni-ethers,這類 SDK 可能需要封裝原生插件或提供特定的適配方案)或引導(dǎo)用戶使用外部瀏覽器(如 DAppBrowser)打開鏈接進(jìn)行交互,這部分相對復(fù)雜,可能需要根據(jù)具體平臺進(jìn)行適配。
-
處理賬戶變化:監(jiān)聽
accountsChanged事件,以便在用戶切換賬戶時(shí)更新應(yīng)用狀態(tài)。// 在 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); } }); }
讀取以太坊鏈上數(shù)據(jù)
有了 Provider 對象,我們就可以輕松讀取以太坊鏈上的數(shù)據(jù),例如獲取賬戶余額、查詢區(qū)塊信息、調(diào)用只讀的智能合約方法等。
-
獲取賬戶余額:
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 需要轉(zhuǎn)換為可讀格式,如 Ether const balanceInEther = ethers.formatEther(balance); this.balance = balanceInEther; console.log('賬戶余額:', balanceInEther, 'ETH'); } catch (error) { console.error('獲取余額失敗:', error); } }, -
查詢智能合約只讀方法: 假設(shè)我們有一個(gè)已部署的智能合約,我們可以使用
Contract對象來調(diào)用其只讀(view或pure)方法。// 假設(shè)這是你的智能合約 ABI(Application Binary Interface)的一部分 const contractABI = [ // 一個(gè)獲取某個(gè)地址代幣余額的函數(shù) "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('合約查詢結(jié)果:', formattedBalance); uni.showToast({ title: `查詢成功,余額: ${formattedBalance}`, icon: 'success' }); } catch (error) { console.error('合約查詢失敗:', error); } },
發(fā)送交易與調(diào)用智能合約寫方法
與區(qū)塊鏈進(jìn)行寫交互(如轉(zhuǎn)賬、調(diào)用智能合約的修改方法)需要用戶使用錢包簽名并支付 Gas 費(fèi)。
-
獲取簽名者 (Signer): 發(fā)送交易需要
Signer對象,它代表一個(gè)能夠簽名的賬戶。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; } }, -
發(fā)送以太坊轉(zhuǎn)賬:
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 轉(zhuǎn)換為 wei }; const txResponse = await signer.sendTransaction(tx); console.log('交易已發(fā)送,等待確認(rèn):', txResponse.hash); uni.showLoading({ title: '交易中,請稍候...' }); // 等待交易被確認(rèn) const txReceipt