隨著區(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)境和工具:

  1. Node.js 和 npm/yarn:確保你的系統(tǒng)已安裝 Node.js(建議 LTS 版本)和相應(yīng)的包管理器。
  2. HBuilderX:UniApp 的官方 IDE,提供了完善的開發(fā)、調(diào)試和打包功能。
  3. MetaMask 錢包插件:在瀏覽器(如 Chrome、Firefox)中安裝 MetaMask 擴(kuò)展,這是與以太坊交互最常用的錢包,在 UniApp 的 H5 端調(diào)試時(shí),瀏覽器需要安裝 MetaMask。
  4. 以太坊節(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)行講解。

  1. 安裝 ethers.js: 在你的 UniApp 項(xiàng)目根目錄下,打開終端,運(yùn)行以下命令安裝 ethers.js

    npm install ethers
    # 或者
    yadd add ethers
  2. 配置 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)。

  1. 檢查并請求賬戶授權(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'
        });
      }
    },
  2. 初始化 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-sdkuni-ethers,這類 SDK 可能需要封裝原生插件或提供特定的適配方案)或引導(dǎo)用戶使用外部瀏覽器(如 DAppBrowser)打開鏈接進(jìn)行交互,這部分相對復(fù)雜,可能需要根據(jù)具體平臺

    隨機(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)用只讀的智能合約方法等。

  1. 獲取賬戶余額

    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);
      }
    },
  2. 查詢智能合約只讀方法: 假設(shè)我們有一個(gè)已部署的智能合約,我們可以使用 Contract 對象來調(diào)用其只讀(viewpure)方法。

    // 假設(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)。

  1. 獲取簽名者 (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;
      }
    },
  2. 發(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