WalletConnect 協議完整實作指南:從原理到開發部署

WalletConnect 是一個開放的協議,用於實現去中心化應用(DApp)與加密貨幣錢包之間的安全連接。作為 Web3 生態系統中最廣泛採用的連接標準,WalletConnect 使錢包能夠與任何區塊鏈應用程式進行交互,無需用戶暴露私鑰或安裝特定的瀏覽器擴展。本文深入解析 WalletConnect 的技術架構、協定版本演進、實際開發步驟、以及常見問題的解決方案,幫助開發者從零開始構建支援 W

WalletConnect 協議完整實作指南:從原理到開發部署

概述

WalletConnect 是一個開放的協議,用於實現去中心化應用(DApp)與加密貨幣錢包之間的安全連接。作為 Web3 生態系統中最廣泛採用的連接標準,WalletConnect 使錢包能夠與任何區塊鏈應用程式進行交互,無需用戶暴露私鑰或安裝特定的瀏覽器擴展。本文深入解析 WalletConnect 的技術架構、協定版本演進、實際開發步驟、以及常見問題的解決方案,幫助開發者從零開始構建支援 WalletConnect 的應用。

一、WalletConnect 協議核心概念

1.1 設計目標與解決問題

傳統的 Web3 應用程式與錢包交互存在諸多痛點:用戶必須在瀏覽器中安裝擴展錢包(如 MetaMask),或者使用錢包內建的瀏覽器。這種模式限制了用戶的選擇權,也增加了安全風險。WalletConnect 的設計目標是建立一個錢包與應用程式之間的標準化通信協議,實現以下能力:

跨平台連接:無論是桌面瀏覽器、移動應用還是硬體錢包,只要有網絡連接能力,就可以通過 WalletConnect 與 DApp 建立安全會話。

無私鑰暴露:錢包的私鑰始終保留在用戶設備上,所有簽名請求都需要用戶在錢包端確認,避免了 Web 應用直接接觸私鑰的風險。

會話管理:支持建立長期會話,用戶一次授權後可以持續使用,無需每次操作都掃描二維碼。

多鏈支持:單一連接可以同時支持多條區塊鏈,簡化了跨鏈應用的開發。

1.2 架構組件解析

WalletConnect 的架構由三個核心組件構成:

WalletConnect 架構示意:

┌─────────────────────────────────────────────────────────────┐
│                    DApp(去中心化應用)                       │
│  - 發起連接請求                                            │
│  - 發送交易簽名請求                                         │
│  - 處理錢包回應                                            │
└────────────────────────┬────────────────────────────────────┘
                         │ WebSocket / HTTP
┌────────────────────────▼────────────────────────────────────┐
│              WalletConnect Bridge Server                    │
│  - 建立會話                                                │
│  - 消息路由                                                │
│  - 會話狀態管理                                            │
│  - 端對端加密                                             │
└────────────────────────┬────────────────────────────────────┘
                         │ 錢包特定協議
┌────────────────────────▼────────────────────────────────────┐
│                    Wallet(加密貨幣錢包)                    │
│  - 顯示連接請求                                            │
│  - 請求用戶授權                                            │
│  - 執行簽名操作                                            │
│  - 返回結果                                                │
└─────────────────────────────────────────────────────────────┘

Bridge Server:作為中繼伺服器,負責在 DApp 和錢包之間轉發加密消息。值得注意的是,Bridge Server 本身無法解密消息內容,因為所有消息都採用端對端加密(E2EE)機制。

DApp Client:DApp 端實現的 WalletConnect SDK,負責發起連接、管理會話、構建和發送各種區塊鏈請求。

Wallet Client:錢包端實現的 WalletConnect SDK,處理連接請求、管理已授權的會話列表、處理簽名和交易請求。

1.3 版本演進:從 v1.0 到 v2.0

WalletConnect 協議經歷了重大升級,從 v1.0 到 v2.0(現在被稱為「WalletConnect」)帶來了根本性的變化:

v1.0 特性

v2.0 核心改進

二、WalletConnect v2.0 技術詳解

2.1 會話建立流程

WalletConnect v2.0 的會話建立過程涉及複雜的密碼學操作,以下是完整流程:

WalletConnect v2.0 會話建立流程:

1. DApp 初始化
   │
   ▼
2. DApp 生成 topic、對稱密鑰
   │
   ▼
3. DApp 發送 pairing request(包含區塊鏈網絡、方法是)
   │
   ▼
4. Bridge Server 轉發配對請求到錢包
   │
   ▼
5. 錢包向用戶顯示連接請求(包含 DApp URL、權限)
   │
   ▼
6. 用戶批准連接
   │
   ▼
7. 錢包生成會話密鑰、回傳 response
   │
   ▼
8. 雙方建立加密會話
   │
   ▼
9. DApp 發送區塊鏈請求(如 eth_requestAccounts)
   │
   ▼
10. 錢包處理請求、返回結果

2.2 訊息格式與結構

WalletConnect v2.0 使用 JSON-RPC 2.0 作為消息格式標準。以下是典型的請求和回應結構:

Session Request(會話請求)

{
  "id": 1698765432100,
  "jsonrpc": "2.0",
  "method": "wc_sessionRequest",
  "params": {
    "requester": {
      "metadata": {
        "name": "My DeFi App",
        "description": "Decentralized Finance Application",
        "url": "https://my-defi-app.com",
        "icons": ["https://my-defi-app.com/icon.png"]
      },
      "publicKey": "0x1234567890abcdef..."
    },
    "chains": ["eip155:1", "eip155:137"],
    "methods": [
      "eth_requestAccounts",
      "eth_sendTransaction",
      "personal_sign",
      "eth_signTypedData_v4"
    ],
    "events": [
      "accountsChanged",
      "chainChanged"
    ]
  }
}

Response(回應)

{
  "id": 1698765432100,
  "jsonrpc": "2.0",
  "result": {
    "approved": true,
    "chainId": 1,
    "accounts": ["0x742d35Cc6634C0532925a3b844Bc9e7595f..."],
    "metadata": {
      "name": "MetaMask",
      "description": "Ethereum Wallet",
      "url": "https://metamask.io",
      "icons": ["https://metamask.io/icon.png"]
    }
  }
}

2.3 加密機制深度分析

WalletConnect v2.0 使用 X25519 橢圓曲線密鑰交換協議建立共享密鑰,然後使用 ChaCha20-Poly1305 對稱加密算法加密消息。這種組合提供了前向安全性(Forward Secrecy),即使長期密鑰被洩露,過往的會話內容也無法被解密。

密鑰交換過程

  1. DApp 生成臨時密鑰對(X25519)
  2. 將公鑰附加在配對 URI 中
  3. 錢包接收到配對請求後,生成自己的臨時密鑰對
  4. 雙方各自計算共享密鑰(使用對方的公鑰和自己的私鑰)
  5. 共享密鑰用於會話期間的所有消息加密

前向安全性保障:每次會話都使用臨時密鑰,會話結束後密鑰即被銷毀。即使攻擊者獲取了某一方的長期私鑰,也無法解密已經結束的會話內容。

2.4 多鏈架構設計

WalletConnect v2.0 支持「鏈 ID 命名空間」(Chain ID Namespace)機制,讓單一會話可以同時管理多條區塊鏈。命名空間格式為 {protocol}:{chainId},例如:

開發者可以通過 chains 參數指定應用支持的區塊鏈列表,錢包則會根據用戶持有的資產情況過濾顯示。

三、DApp 端開發實作

3.1 專案初始化

首先建立一個支援 WalletConnect 的 DApp 專案。我們使用 React 作為前端框架,配合 @walletconnect/ethereum-provider 實現連接功能:

# 建立 React 專案
npx create-react-app my-dapp
cd my-dapp

# 安裝 WalletConnect 相關依賴
npm install @walletconnect/ethereum-provider @web3modal/ethers ethers

3.2 錢包連接實現

以下程式碼展示如何在 DApp 中實現 WalletConnect 連接功能:

// src/walletConnect.js
import EthereumProvider from '@walletconnect/ethereum-provider';

let provider = null;

// 初始化 WalletConnect Provider
export async function initWalletConnect() {
  // 專案 ID 從 WalletConnect Cloud 註冊獲得
  const projectId = 'YOUR_PROJECT_ID';
  
  // 定義支援的鏈和方法
  const metadata = {
    name: 'My DeFi App',
    description: 'Decentralized Finance Application',
    url: 'https://my-defi-app.com',
    icons: ['https://my-defi-app.com/icon.png']
  };

  // 初始化 provider
  provider = await EthereumProvider.init({
    projectId,
    chains: [1], // Ethereum Mainnet
    methods: [
      'eth_requestAccounts',
      'eth_sendTransaction',
      'personal_sign',
      'eth_signTypedData_v4'
    ],
    events: [
      'accountsChanged',
      'chainChanged',
      'disconnect'
    ],
    metadata
  });

  // 監聽帳戶變化
  provider.on('accountsChanged', (accounts) => {
    console.log('帳戶變更:', accounts);
    if (accounts.length === 0) {
      // 用戶已斷開連接
      handleDisconnect();
    }
  });

  // 監聽鏈變化
  provider.on('chainChanged', (chainId) => {
    console.log('鏈變更:', chainId);
    window.location.reload();
  });

  // 監聽斷開連接
  provider.on('disconnect', (code, reason) => {
    console.log('斷開連接:', code, reason);
    handleDisconnect();
  });

  return provider;
}

// 發起連接請求
export async function connectWallet() {
  if (!provider) {
    await initWalletConnect();
  }

  // 發送連接請求
  const accounts = await provider.request({ 
    method: 'eth_requestAccounts' 
  });
  
  return accounts;
}

// 斷開連接
export async function disconnectWallet() {
  if (provider) {
    await provider.disconnect();
  }
}

// 發送交易
export async function sendTransaction(to, value) {
  const accounts = await provider.request({ method: 'eth_accounts' });
  const from = accounts[0];

  const tx = {
    from,
    to,
    value: '0x' + value.toString(16), // 轉換為十六進制
    gasLimit: '0x5208', // 21000 Gwei
    gasPrice: '0x4A817C800' // 20 Gwei
  };

  const txHash = await provider.request({
    method: 'eth_sendTransaction',
    params: [tx]
  });

  return txHash;
}

// 請求用戶簽名
export async function signMessage(message) {
  const accounts = await provider.request({ method: 'eth_accounts' });
  const from = accounts[0];

  // 使用 personal_sign 方法
  const signature = await provider.request({
    method: 'personal_sign',
    params: [message, from]
  });

  return signature;
}

3.3 React 整合範例

以下是完整的 React 元件範例,展示錢包連接的 UI 和狀態管理:

// src/App.jsx
import React, { useState, useEffect } from 'react';
import { 
  initWalletConnect, 
  connectWallet, 
  disconnectWallet,
  sendTransaction 
} from './walletConnect';

function App() {
  const [accounts, setAccounts] = useState([]);
  const [connecting, setConnecting] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 頁面載入時初始化
    initWalletConnect()
      .then(() => {
        // 檢查是否已有連接
        checkConnection();
      })
      .catch(err => {
        console.error('初始化失敗:', err);
        setError('WalletConnect 初始化失敗');
      });
  }, []);

  const checkConnection = async () => {
    try {
      const accounts = await window.ethereum?.request({ 
        method: 'eth_accounts' 
      });
      if (accounts && accounts.length > 0) {
        setAccounts(accounts);
      }
    } catch (err) {
      console.error('檢查連接失敗:', err);
    }
  };

  const handleConnect = async () => {
    setConnecting(true);
    setError(null);
    
    try {
      const accounts = await connectWallet();
      setAccounts(accounts);
    } catch (err) {
      setError(err.message || '連接失敗');
    } finally {
      setConnecting(false);
    }
  };

  const handleDisconnect = async () => {
    await disconnectWallet();
    setAccounts([]);
  };

  return (
    <div className="wallet-connect">
      <h1>WalletConnect DApp 範例</h1>
      
      {error && <div className="error">{error}</div>}
      
      {accounts.length === 0 ? (
        <button 
          onClick={handleConnect} 
          disabled={connecting}
        >
          {connecting ? '連接中...' : '連接錢包'}
        </button>
      ) : (
        <div className="connected">
          <p>已連接帳戶:</p>
          <code>{accounts[0]}</code>
          <button onClick={handleDisconnect}>斷開連接</button>
        </div>
      )}
    </div>
  );
}

export default App;

3.4 自訂配對 URI 處理

WalletConnect v2.0 支持通過 URI 進行配對,這在移動端應用中特別有用:

// 處理 WalletConnect URI
export async function handleWalletConnectUri(uri) {
  // URI 格式: wc:topic@2?symKey=symmetricKey&relayProtocol=irn
  if (!provider) {
    await initWalletConnect();
  }

  // 通過 URI 建立配對
  await provider.pair({ uri });
  
  // 配對成功後,錢包會顯示批准請求
  // 用戶批准後,會話即建立
}

四、Walle t端整合實作

4.1 錢包端職責概述

錢包端實現 WalletConnect 需要處理以下核心功能:

4.2 錢包 SDK 整合

以下是錢包端整合 WalletConnect 的基本架構:

// wallet/walletConnect.js
import { Core } from '@walletconnect/core';
import { WalletClient } from '@walletconnect/web3wallet';

let web3wallet = null;

// 初始化錢包客戶端
export async function initWalletClient(projectId) {
  const core = new Core({
    projectId
  });

  web3wallet = await WalletClient.init({
    core,
    metadata: {
      name: 'My Wallet',
      description: 'Secure Ethereum Wallet',
      url: 'https://my-wallet.com',
      icons: ['https://my-wallet.com/icon.png']
    }
  });

  // 監聽會話請求
  web3wallet.on('session_request', async (event) => {
    const { id, params, topic } = event;
    
    // 顯示批准 UI
    const approved = await showApprovalModal(params);
    
    if (approved) {
      // 授權會話
      await web3wallet.approveSession({
        id,
        chainId: 1,
        accounts: ['0x742d35Cc6634C0532925a3b844Bc9e7595f...']
      });
    } else {
      // 拒絕會話
      await web3wallet.rejectSession({
        id,
        message: 'User rejected'
      });
    }
  });

  // 監聽 RPC 請求
  web3wallet.on('session_event', async (event) => {
    const { topic, params, id } = event;
    
    switch (params.request.method) {
      case 'eth_sendTransaction':
        await handleTransactionRequest(id, params.request.params);
        break;
      case 'personal_sign':
        await handleSignMessage(id, params.request.params);
        break;
      case 'eth_signTypedData_v4':
        await handleSignTypedData(id, params.request.params);
        break;
    }
  });

  return web3wallet;
}

// 處理交易請求
async function handleTransactionRequest(id, params) {
  // 顯示交易確認 UI
  const tx = params[0];
  const confirmed = await showTransactionConfirm(tx);
  
  if (confirmed) {
    // 廣播交易
    const txHash = await broadcastTransaction(tx);
    
    // 返回結果
    await web3wallet.respondSessionRequest({
      id,
      result: txHash
    });
  } else {
    // 用戶拒絕
    await web3wallet.respondSessionRequest({
      id,
      error: { 
        code: 4001, 
        message: 'User rejected request' 
      }
    });
  }
}

// 處理簽名請求
async function handleSignMessage(id, params) {
  const [message, address] = params;
  
  // 顯示簽名確認 UI
  const confirmed = await showSignConfirm(message, address);
  
  if (confirmed) {
    const signature = await signMessage(message, address);
    
    await web3wallet.respondSessionRequest({
      id,
      result: signature
    });
  } else {
    await web3wallet.respondSessionRequest({
      id,
      error: { 
        code: 4001, 
        message: 'User rejected request' 
      }
    });
  }
}

4.3 會話管理策略

錢包需要實現完善的會話管理機制:

// 會話管理
export class SessionManager {
  constructor(web3wallet) {
    this.wallet = web3wallet;
    this.sessions = new Map();
  }

  // 載入所有會話
  async loadSessions() {
    const sessionNamespaces = this.wallet.getActiveSessions();
    
    for (const [topic, session] of Object.entries(sessionNamespaces)) {
      this.sessions.set(topic, {
        peer: session.peer.metadata,
        chains: session.namespaces.eip155?.chains || [],
        methods: session.namespaces.eip155?.methods || [],
        events: session.namespaces.eip155?.events || [],
        expiry: session.expiry
      });
    }
  }

  // 斷開特定會話
  async disconnectSession(topic) {
    await this.wallet.disconnectSession({
      topic,
      message: 'User disconnected'
    });
    this.sessions.delete(topic);
  }

  // 斷開所有會話
  async disconnectAll() {
    for (const topic of this.sessions.keys()) {
      await this.disconnectSession(topic);
    }
  }

  // 更新會話權限
  async updateSession(topic, newMethods, newEvents) {
    await this.wallet.updateSession({
      topic,
      namespaces: {
        eip155: {
          chains: this.sessions.get(topic).chains,
          methods: newMethods,
          events: newEvents,
          accounts: []
        }
      }
    });
  }

  // 檢查會話是否即將過期
  checkExpiry() {
    const now = Math.floor(Date.now() / 1000);
    const expiringSessions = [];
    
    for (const [topic, session] of this.sessions) {
      if (session.expiry - now < 3600) { // 小於 1 小時
        expiringSessions.push(topic);
      }
    }
    
    return expiringSessions;
  }
}

五、常見問題與最佳實踐

5.1 連接問題排查

問題:配對請求無回應

可能原因:

  1. Bridge Server 連接問題
  2. 錢包未正確初始化
  3. 網絡防火牆阻止

解決方案:

// 添加連接狀態監控
provider.on('connect', () => console.log('連接成功'));
provider.on('disconnect', (err) => {
  console.log('連接斷開:', err);
  // 嘗試重新連接
  if (err) {
    initWalletConnect();
  }
});
provider.on('error', (err) => console.error('連接錯誤:', err));

問題:交易請求超時

解決方案:

const txHash = await Promise.race([
  provider.request({ method: 'eth_sendTransaction', params: [tx] }),
  new Promise((_, reject) => 
    setTimeout(() => reject(new Error('超時')), 60000)
  )
]);

5.2 安全最佳實踐

驗證 DApp 元數據:錢包應始終向用戶顯示 DApp 的 URL 和描述,幫助用戶識別潛在的钓鱼攻擊。

限制授權範圍:錢包應實現「最小權限」原則,只授權 DApp 請求的方法,而非全部權限。

會話過期機制:設定合理的會話過期時間,定期要求用戶重新確認。

惡意網站識別:錢包應維護已知惡意網站的黑名單,在連接前向用戶發出警告。

5.3 錯誤碼處理

WalletConnect 定義了一組標準錯誤碼:

錯誤碼含義處理方式
4000無效請求記錄日誌並返回錯誤
4001用戶拒絕正常流程,通知用戶
4100未授權提示用戶連接錢包
4200不支援的方法向用戶說明並提供替代方案
4300超出配額提示稍後再試
4900斷開連接清理會話狀態

5.4 效能優化建議

連接池管理:對於需要同時管理多個會話的錢包,建議使用連接池來維護與 Bridge Server 的連接。

消息批量處理:當有多個待處理請求時,批量處理而非逐個發送。

本地緩存:緩存已授權的會話信息,減少重複的 Bridge Server 查詢。

// 會話緩存實現
const sessionCache = {
  get(key) {
    const data = localStorage.getItem(`wc_session_${key}`);
    return data ? JSON.parse(data) : null;
  },
  
  set(key, value) {
    localStorage.setItem(`wc_session_${key}`, JSON.stringify(value));
  },
  
  clear() {
    // 清理所有會話緩存
    Object.keys(localStorage)
      .filter(key => key.startsWith('wc_session_'))
      .forEach(key => localStorage.removeItem(key));
  }
};

六、WalletConnect 與其他標準的整合

6.1 與 ERC-4337 的整合

WalletConnect 可以與 ERC-4337 帳戶抽象標準無縫整合,實現更複雜的交易場景:

// 使用 WalletConnect 發送 ERC-4337 UserOperation
async function sendUserOperationWithWalletConnect(
  provider, 
  userOp, 
  entryPoint
) {
  // 通過 WalletConnect 發送
  const userOpHash = await provider.request({
    method: 'eth_sendUserOperation',
    params: [{
      userOp,
      entryPoint,
      chainId: 1
    }]
  });
  
  return userOpHash;
}

6.2 多鏈場景應用

WalletConnect v2.0 的多鏈支持使其特別適合 DeFi 聚合器等複雜應用:

// 多鏈配置
const chains = [
  { chainId: 1, name: 'Ethereum' },
  { chainId: 137, name: 'Polygon' },
  { chainId: 42161, name: 'Arbitrum' }
];

// 發送跨鏈請求
async function sendCrossChainRequest(provider, chainId, request) {
  const session = provider.session;
  const namespace = session.namespaces.eip155;
  
  // 檢查目標鏈是否在會話授權範圍內
  const targetChain = `eip155:${chainId}`;
  if (!namespace.chains.includes(targetChain)) {
    // 請求擴展授權
    await provider.extendSession({
      chainIds: [targetChain]
    });
  }
  
  return provider.request({
    method: request.method,
    params: request.params,
    chainId: targetChain
  });
}

6.3 與 Web3Modal 的整合

Web3Modal 提供了一個更簡化的 UI 框架,內建 WalletConnect 支援:

import { createWeb3Modal, defaultConfig } from '@web3modal/ethers/react';

// 初始化 Web3Modal
const metadata = {
  name: 'My DApp',
  description: 'My DApp Description',
  url: 'https://my-dapp.com',
  icons: ['https://my-dapp.com/icon.png']
};

const ethersConfig = defaultConfig({
  metadata,
  projectId: 'YOUR_PROJECT_ID'
});

createWeb3Modal({
  ethersConfig,
  projectId: 'YOUR_PROJECT_ID',
  chains: [
    {
      chainId: 1,
      name: 'Ethereum',
      currency: 'ETH',
      explorerUrl: 'https://etherscan.io',
      rpcUrl: 'https://cloudflare-eth.com'
    }
  ],
  enableAnalytics: true
});

結論

WalletConnect 已經成為 Web3 生態系統中不可或缺的基礎設施。通過本文的深入解析,開發者應該能夠全面理解 WalletConnect 協議的技術原理,並有能力在 DApp 和錢包兩端實現完整的整合功能。隨著以太坊帳戶抽象、意圖經濟等趨勢的發展,WalletConnect 的角色將更加重要,建議開發者持續關注其版本更新和生態演進。

參考資源

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!