如何将自定义钱包接入React Native内嵌浏览器?
解决思路与实现步骤
首先明确:expo-web-browser是系统浏览器的封装,无法直接注入自定义JavaScript代码,所以要实现钱包提供商注入,必须替换为可控制的WebView组件(比如react-native-webview),因为它允许我们操作网页的上下文环境,实现双向通信。
以下是具体实现方案:
1. 替换为react-native-webview
先安装依赖(Expo项目直接用expo安装):
expo install react-native-webview
2. 注入EIP-1193标准的钱包Provider
Web3网站都是基于EIP-1193标准和钱包交互的,我们需要在WebView加载网页时,向网页注入一个符合该标准的window.ethereum对象,同时实现双向通信逻辑:
核心代码示例
import React, { useRef } from 'react'; import { WebView } from 'react-native-webview'; import { ethers } from 'ethers'; import * as SecureStore from 'expo-secure-store'; // 从安全存储获取私钥(示例) const getWallet = async () => { const privateKey = await SecureStore.getItemAsync('wallet_private_key'); return privateKey ? new ethers.Wallet(privateKey) : null; }; const Web3Browser = () => { const webviewRef = useRef(null); const [wallet, setWallet] = React.useState(null); React.useEffect(() => { (async () => { const loadedWallet = await getWallet(); setWallet(loadedWallet); })(); }, []); // 注入到网页的EIP-1193 Provider代码 const injectProviderScript = ` window.ethereum = { _requestPromises: new Map(), _nextId: 1, request: async (args) => { return new Promise((resolve, reject) => { const id = this._nextId++; this._requestPromises.set(id, { resolve, reject }); window.ReactNativeWebView.postMessage(JSON.stringify({ id, method: args.method, params: args.params || [] })); }); }, sendAsync: (payload, callback) => { this.request(payload) .then(result => callback(null, { id: payload.id, result })) .catch(error => callback(error, null)); }, on: (eventName, listener) => { // 后续可扩展实现账户/链变更事件监听 } }; window.addEventListener('message', (event) => { const data = JSON.parse(event.data); const promise = window.ethereum._requestPromises.get(data.id); if (!promise) return; window.ethereum._requestPromises.delete(data.id); if (data.error) { promise.reject(new Error(data.error)); } else { promise.resolve(data.result); } }); window.dispatchEvent(new Event('ethereum#initialized')); `; // 处理WebView发来的请求 const handleMessage = async (event) => { if (!wallet) { webviewRef.current.postMessage(JSON.stringify({ id: JSON.parse(event.nativeEvent.data).id, error: 'Wallet not loaded' })); return; } const { id, method, params } = JSON.parse(event.nativeEvent.data); let result; let error = null; switch (method) { case 'eth_requestAccounts': result = [wallet.address]; break; case 'eth_sign': try { const signature = await wallet.signMessage(params[1]); webviewRef.current.postMessage(JSON.stringify({ id, result: signature })); } catch (err) { webviewRef.current.postMessage(JSON.stringify({ id, error: err.message })); } return; case 'eth_sendTransaction': try { const tx = await wallet.sendTransaction(params[0]); webviewRef.current.postMessage(JSON.stringify({ id, result: tx.hash })); } catch (err) { webviewRef.current.postMessage(JSON.stringify({ id, error: err.message })); } return; case 'eth_chainId': result = '0x1'; // 以太坊主网,按需修改为目标链ID break; case 'eth_getBalance': const balance = await wallet.getBalance(); result = balance.toHexString(); break; default: error = `Unsupported method: ${method}`; } webviewRef.current.postMessage(JSON.stringify({ id, result, error })); }; return ( <WebView ref={webviewRef} source={{ uri: 'https://sparklesnft.com/' }} injectedJavaScriptBeforeContentLoaded={injectProviderScript} onMessage={handleMessage} style={{ flex: 1 }} // 可添加原expo-web-browser的类似配置,比如工具栏颜色等 /> ); }; export default Web3Browser;
3. 关键注意事项
- 安全存储:私钥绝对不能硬编码,必须用
expo-secure-store这类安全存储方案保存,避免泄露。 - EIP-1193兼容性:尽量实现标准中的核心方法和事件(比如
accountsChanged、chainChanged),确保Web3网站能正常识别并交互。 - 异步处理:签名、发送交易等操作是异步的,需在RN端处理完成后再将结果返回网页。
- 错误捕获:要捕获ethers.js的调用错误,将清晰的错误信息返回给网页,避免前端出现未处理异常。
内容的提问来源于stack exchange,提问作者xfscrypt
相关产品推荐
相关产品推荐

