From 29ae9f7319a853ea6da974be067d108ae56b10a0 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:35:25 +0200 Subject: [PATCH 01/17] feat: add BrowserScreen and integrate MetaMask webview - Added a new `BrowserScreen` to the navigation stack for enhanced browsing capabilities. - Updated `package.json` to include `@metamask/react-native-webview` version 14.0.4 for improved webview functionality. - Modified `PRODUCTS` to include a new product entry for the browser feature. - Updated type definitions to accommodate the new `BrowserScreen` in navigation and product paths. --- package.json | 1 + src/appTypes/navigation/wallets.ts | 1 + src/features/products/entities/_products.tsx | 12 +++++++++++ src/features/products/utils/product.ts | 7 ++++++- src/navigation/stacks/Tabs/HomeStack.tsx | 2 ++ src/screens/Browser/index.tsx | 21 ++++++++++++++++++++ yarn.lock | 10 +++++++++- 7 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/screens/Browser/index.tsx diff --git a/package.json b/package.json index b96553b8b..f09f6ee44 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@babel/preset-env": "^7.21.5", "@craftzdog/react-native-buffer": "^6.0.5", "@ethersproject/shims": "^5.7.0", + "@metamask/react-native-webview": "^14.0.4", "@metamask/safe-event-emitter": "^3.0.0", "@morrowdigital/watermelondb-expo-plugin": "^2.3.1", "@mymonero/mymonero-paymentid-utils": "^3.0.0", diff --git a/src/appTypes/navigation/wallets.ts b/src/appTypes/navigation/wallets.ts index c444c8692..123905b8e 100644 --- a/src/appTypes/navigation/wallets.ts +++ b/src/appTypes/navigation/wallets.ts @@ -37,6 +37,7 @@ export type HomeParamsList = { }; Wallets: { screen: string }; Harbor: undefined; + BrowserScreen: undefined; } & CommonStackParamsList; export type HomeNavigationProp = CompositeNavigationProp< diff --git a/src/features/products/entities/_products.tsx b/src/features/products/entities/_products.tsx index 4e2736cd2..43431eee0 100644 --- a/src/features/products/entities/_products.tsx +++ b/src/features/products/entities/_products.tsx @@ -13,6 +13,18 @@ export const PRODUCTS = (t: TFunction): SectionizedProducts[] => { { title: t('products.title.trade'), data: [ + { + id: 0, + name: 'Browser', + description: t('products.swap.description'), + icon: ( + + ), + background: ['rgba(132, 224, 255, 0.2)', 'rgba(160, 99, 221, 0.2)'], + color: 'rgba(52, 27, 104, 1)', + route: 'BrowserScreen', + firebaseEvent: CustomAppEvents.products_swap + }, { id: 0, name: t('token.actions.swap'), diff --git a/src/features/products/utils/product.ts b/src/features/products/utils/product.ts index 62c252f46..714eb6f78 100644 --- a/src/features/products/utils/product.ts +++ b/src/features/products/utils/product.ts @@ -3,7 +3,12 @@ import { CustomAppEvents } from '@lib/firebaseEventAnalytics'; type AvailableProductsPath = keyof Pick< HomeParamsList, - 'SwapScreen' | 'Bridge' | 'StakingPools' | 'KosmosScreen' | 'Harbor' + | 'SwapScreen' + | 'Bridge' + | 'StakingPools' + | 'KosmosScreen' + | 'Harbor' + | 'BrowserScreen' >; export type Product = { diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index 954d50f63..6171d8218 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -10,6 +10,7 @@ import { AMBMarket } from '@screens/AMBMarket'; import { AssetScreen } from '@screens/Asset'; import { Bridge, BridgeTransferError } from '@screens/Bridge'; import { BridgeHistory } from '@screens/BridgeHistory'; +import { BrowserScreen } from '@screens/Browser'; import { KosmosOrderList } from '@screens/KosmosOrderList'; import { NFTScreen } from '@screens/NFTScreen'; import { Notifications } from '@screens/Notifications'; @@ -65,6 +66,7 @@ export const HomeStack = () => { {/* HARBOR ROUTES */} + {getCommonStack(Stack as any)} diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx new file mode 100644 index 000000000..305220ba4 --- /dev/null +++ b/src/screens/Browser/index.tsx @@ -0,0 +1,21 @@ +import React, { useMemo } from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { WebView } from '@metamask/react-native-webview'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +const SOURCE = { uri: 'https://www.google.com' }; + +export const BrowserScreen = () => { + const containerStyle = useMemo>( + () => ({ + flex: 1 + }), + [] + ); + + return ( + + + + ); +}; diff --git a/yarn.lock b/yarn.lock index 5ffe43075..14800ed2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3401,6 +3401,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@metamask/react-native-webview@^14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@metamask/react-native-webview/-/react-native-webview-14.0.4.tgz#2bcb3300d8cd38748765063a0847c5f4ac960838" + integrity sha512-hECL9HTHByNb4tU/jnRmw8DGhK+x1QXbYWq5ySAiE3Z2FB67ddgD84UjELN1EG/6t2GKAtWXmfclR+MsjR/f6w== + dependencies: + escape-string-regexp "^4.0.0" + invariant "2.2.4" + "@metamask/safe-event-emitter@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.0.0.tgz#8c2b9073fe0722d48693143b0dc8448840daa3bd" @@ -10133,7 +10141,7 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" -invariant@*, invariant@^2.2.4: +invariant@*, invariant@2.2.4, invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== From 20190d52f9c9502be13728882282760725a3fabf Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Mon, 20 Jan 2025 18:12:10 +0200 Subject: [PATCH 02/17] feat: enhance BrowserScreen with wallet integration and message handling - Updated `createAMBProvider` to accept `CHAIN_ID` for improved provider configuration. - Enhanced `BrowserScreen` to include wallet functionality, allowing for message signing and account management. - Implemented message handling for various Ethereum methods (e.g., `personal_sign`, `eth_requestAccounts`, `eth_chainId`, etc.) to facilitate interaction with the webview. - Added reload and back navigation buttons for better user experience in the webview. - Integrated `ethers` library for wallet operations, ensuring secure message signing and account retrieval. --- src/features/browser/lib/index.ts | 1 + src/features/browser/lib/injected.ts | 78 ++++++++++++ .../swap/utils/contracts/instances.ts | 5 +- src/screens/Browser/index.tsx | 115 +++++++++++++++++- 4 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 src/features/browser/lib/index.ts create mode 100644 src/features/browser/lib/injected.ts diff --git a/src/features/browser/lib/index.ts b/src/features/browser/lib/index.ts new file mode 100644 index 000000000..dab0c6473 --- /dev/null +++ b/src/features/browser/lib/index.ts @@ -0,0 +1 @@ +export * from './injected'; diff --git a/src/features/browser/lib/injected.ts b/src/features/browser/lib/injected.ts new file mode 100644 index 000000000..dc792c9b6 --- /dev/null +++ b/src/features/browser/lib/injected.ts @@ -0,0 +1,78 @@ +export const injectedJavaScript = ` +(function () { + if (window.ethereum) { + console.log('Ethereum provider already injected.'); + return; + } + + // Define the Ethereum provider object + const ethereum = { + isMetaMask: false, + + // Network information (Mainnet or custom network) + chainId: '0x414e', // Example custom chainId, change based on actual network + networkVersion: '16718', // Example custom networkVersion + + // Request method compliant with EIP-1193 + request: async ({ method, params }) => { + return new Promise((resolve, reject) => { + window.ReactNativeWebView.postMessage( + JSON.stringify({ method, params }) + ); + + const handleMessage = (event) => { + try { + const response = JSON.parse(event.data); + if (response.method === method) { + window.removeEventListener('message', handleMessage); + + if (response.error) { + reject(response.error); + } else { + resolve(response.result); + } + } + } catch (error) { + window.removeEventListener('message', handleMessage); + reject(error); + } + }; + + window.addEventListener('message', handleMessage); + }); + }, + + // EIP-6693 connect method + connect: async (options) => { + return new Promise((resolve, reject) => { + window.ReactNativeWebView.postMessage( + JSON.stringify({ method: 'connect', params: options }) + ); + + const handleMessage = (event) => { + try { + const response = JSON.parse(event.data); + if (response.method === 'connect') { + window.removeEventListener('message', handleMessage); + + if (response.error) { + reject(response.error); + } else { + resolve(response.result); + } + } + } catch (error) { + window.removeEventListener('message', handleMessage); + reject(error); + } + }; + + window.addEventListener('message', handleMessage); + }); + } + }; + + // Attach the Ethereum provider to the global window object + window.ethereum = ethereum; +})(); +`; diff --git a/src/features/swap/utils/contracts/instances.ts b/src/features/swap/utils/contracts/instances.ts index 3f4372195..d4ee26bdd 100644 --- a/src/features/swap/utils/contracts/instances.ts +++ b/src/features/swap/utils/contracts/instances.ts @@ -5,7 +5,10 @@ import { FACTORY_ABI } from '@features/swap/lib/abi'; type ProviderOrSigner = ethers.providers.JsonRpcProvider | ethers.Signer; export function createAMBProvider() { - return new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); + return new ethers.providers.JsonRpcProvider( + Config.NETWORK_URL, + Config.CHAIN_ID + ); } export function createSigner( diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 305220ba4..38db61127 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -1,11 +1,24 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } from 'react'; import { StyleProp, ViewStyle } from 'react-native'; import { WebView } from '@metamask/react-native-webview'; +import { ethers } from 'ethers'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { Button, Row, Text } from '@components/base'; +import { useWalletPrivateKey } from '@entities/wallet'; +import { injectedJavaScript } from '@features/browser/lib'; +import { createAMBProvider } from '@features/swap/utils/contracts/instances'; -const SOURCE = { uri: 'https://www.google.com' }; +const SOURCE = { + uri: 'https://7z2dkc.csb.app/' +}; export const BrowserScreen = () => { + const { _extractPrivateKey } = useWalletPrivateKey(); + const webViewRef = useRef(null); + + const reload = () => webViewRef.current?.reload(); + const back = () => webViewRef.current?.goBack(); + const containerStyle = useMemo>( () => ({ flex: 1 @@ -13,9 +26,105 @@ export const BrowserScreen = () => { [] ); + const handleMessage = async (event: { nativeEvent: { data: string } }) => { + const { method, params } = JSON.parse(event.nativeEvent.data); + + const privateKey = await _extractPrivateKey(); + const wallet = new ethers.Wallet(privateKey, createAMBProvider()); + + switch (method) { + case 'personal_sign': + if (params.length < 2) { + sendResponse(method, { + error: 'Invalid parameters for personal_sign' + }); + return; + } + try { + const message = params[0]; + const walletAddress = params[1]; + const signature = await signMessage(message, walletAddress); + sendResponse(method, { result: signature }); + } catch (error) { + sendResponse(method, { + error: (error as { message: string }).message + }); + } + break; + + case 'eth_requestAccounts': + const accounts = [wallet.address]; + sendResponse(method, { result: accounts }); + break; + + case 'eth_chainId': + // Respond with chainId + sendResponse(method, { result: '0x414e' }); + break; + + case 'eth_networkVersion': + // Respond with network version + sendResponse(method, { result: '16718' }); + break; + + case 'eth_blockNumber': + // Respond with block number + sendResponse(method, { result: '0x10d4f' }); + break; + case 'eth_getBalance': + // Respond with block number + sendResponse(method, { result: await wallet.getBalance() }); + break; + + default: + sendResponse(method, { error: `Unsupported method: ${method}` }); + } + }; + + const sendResponse = ( + method: any, + response: { error?: any; result?: string | ethers.BigNumber | string[] } + ) => { + const message = JSON.stringify({ method, ...response }); + webViewRef.current?.postMessage(message); + }; + + const signMessage = async ( + message: string | ethers.utils.Bytes, + walletAddress: string + ) => { + const privateKey = await _extractPrivateKey(); // Replace with your wallet's private key + const wallet = new ethers.Wallet(privateKey, createAMBProvider()); + const signature = await wallet.signMessage(message); + + // Verify that the signed message corresponds to the wallet address + const recoveredAddress = ethers.utils.verifyMessage(message, signature); + if (recoveredAddress.toLowerCase() !== walletAddress.toLowerCase()) { + throw new Error('Recovered address does not match the wallet address.'); + } + + return signature; + }; + return ( - + + + + + ); }; From 81392fa975f4e3b6685186eff3a8de0966c24252 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:49:58 +0200 Subject: [PATCH 03/17] refactor: remove deprecated injected JavaScript and enhance createAMBProvider - Deleted the `injected.ts` file and its export, as it is no longer needed for the current implementation. - Removed the `index.ts` file which previously exported the injected JavaScript. - Updated `createAMBProvider` to accept an optional `networkUrl` parameter for improved flexibility in provider configuration. - Enhanced the `BrowserScreen` to accommodate the removal of the injected JavaScript, ensuring continued functionality with the updated provider. --- src/features/browser/lib/index.ts | 1 - src/features/browser/lib/injected.ts | 78 ----- .../swap/utils/contracts/instances.ts | 4 +- src/screens/Browser/index.tsx | 296 +++++++++++++----- 4 files changed, 225 insertions(+), 154 deletions(-) delete mode 100644 src/features/browser/lib/index.ts delete mode 100644 src/features/browser/lib/injected.ts diff --git a/src/features/browser/lib/index.ts b/src/features/browser/lib/index.ts deleted file mode 100644 index dab0c6473..000000000 --- a/src/features/browser/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './injected'; diff --git a/src/features/browser/lib/injected.ts b/src/features/browser/lib/injected.ts deleted file mode 100644 index dc792c9b6..000000000 --- a/src/features/browser/lib/injected.ts +++ /dev/null @@ -1,78 +0,0 @@ -export const injectedJavaScript = ` -(function () { - if (window.ethereum) { - console.log('Ethereum provider already injected.'); - return; - } - - // Define the Ethereum provider object - const ethereum = { - isMetaMask: false, - - // Network information (Mainnet or custom network) - chainId: '0x414e', // Example custom chainId, change based on actual network - networkVersion: '16718', // Example custom networkVersion - - // Request method compliant with EIP-1193 - request: async ({ method, params }) => { - return new Promise((resolve, reject) => { - window.ReactNativeWebView.postMessage( - JSON.stringify({ method, params }) - ); - - const handleMessage = (event) => { - try { - const response = JSON.parse(event.data); - if (response.method === method) { - window.removeEventListener('message', handleMessage); - - if (response.error) { - reject(response.error); - } else { - resolve(response.result); - } - } - } catch (error) { - window.removeEventListener('message', handleMessage); - reject(error); - } - }; - - window.addEventListener('message', handleMessage); - }); - }, - - // EIP-6693 connect method - connect: async (options) => { - return new Promise((resolve, reject) => { - window.ReactNativeWebView.postMessage( - JSON.stringify({ method: 'connect', params: options }) - ); - - const handleMessage = (event) => { - try { - const response = JSON.parse(event.data); - if (response.method === 'connect') { - window.removeEventListener('message', handleMessage); - - if (response.error) { - reject(response.error); - } else { - resolve(response.result); - } - } - } catch (error) { - window.removeEventListener('message', handleMessage); - reject(error); - } - }; - - window.addEventListener('message', handleMessage); - }); - } - }; - - // Attach the Ethereum provider to the global window object - window.ethereum = ethereum; -})(); -`; diff --git a/src/features/swap/utils/contracts/instances.ts b/src/features/swap/utils/contracts/instances.ts index d4ee26bdd..86fb8660b 100644 --- a/src/features/swap/utils/contracts/instances.ts +++ b/src/features/swap/utils/contracts/instances.ts @@ -4,9 +4,9 @@ import { FACTORY_ABI } from '@features/swap/lib/abi'; type ProviderOrSigner = ethers.providers.JsonRpcProvider | ethers.Signer; -export function createAMBProvider() { +export function createAMBProvider(networkUrl?: string) { return new ethers.providers.JsonRpcProvider( - Config.NETWORK_URL, + networkUrl || Config.NETWORK_URL, Config.CHAIN_ID ); } diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 38db61127..184db578a 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -1,20 +1,53 @@ -import React, { useMemo, useRef } from 'react'; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState +} from 'react'; import { StyleProp, ViewStyle } from 'react-native'; -import { WebView } from '@metamask/react-native-webview'; +import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; import { ethers } from 'ethers'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Text } from '@components/base'; +import Config from '@constants/config'; import { useWalletPrivateKey } from '@entities/wallet'; -import { injectedJavaScript } from '@features/browser/lib'; import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { - uri: 'https://7z2dkc.csb.app/' + uri: 'https://metamask.github.io/test-dapp/' }; +interface JsonRpcRequest { + id: number; + jsonrpc: string; + method: string; + params: any[]; +} + +interface JsonRpcResponse { + id: number; + jsonrpc: string; + result?: any; + error?: { + code: number; + message: string; + }; +} + export const BrowserScreen = () => { const { _extractPrivateKey } = useWalletPrivateKey(); const webViewRef = useRef(null); + const [connectedAddress, setConnectedAddress] = useState(null); + const requestsInProgress = useRef(new Set()); + + const extractWallet = useCallback(async () => { + const privateKey = await _extractPrivateKey(); + return new ethers.Wallet(privateKey, createAMBProvider()); + }, [_extractPrivateKey]); + + const INJECTED_PROVIDER_JS = ''; const reload = () => webViewRef.current?.reload(); const back = () => webViewRef.current?.goBack(); @@ -26,86 +59,197 @@ export const BrowserScreen = () => { [] ); - const handleMessage = async (event: { nativeEvent: { data: string } }) => { - const { method, params } = JSON.parse(event.nativeEvent.data); + const getCurrentAddress = useCallback(async () => { + try { + const wallet = await extractWallet(); + return await wallet.getAddress(); + } catch (error) { + console.error('Error getting address:', error); + return null; + } + }, [extractWallet]); - const privateKey = await _extractPrivateKey(); - const wallet = new ethers.Wallet(privateKey, createAMBProvider()); - - switch (method) { - case 'personal_sign': - if (params.length < 2) { - sendResponse(method, { - error: 'Invalid parameters for personal_sign' - }); - return; + useEffect(() => { + getCurrentAddress().then((address) => { + if (address) { + setConnectedAddress(address); + } + }); + }, [getCurrentAddress]); + + const handleWebViewMessage = async (event: WebViewMessageEvent) => { + try { + const request: JsonRpcRequest = JSON.parse(event.nativeEvent.data); + const { id, method, params } = request; + + if (requestsInProgress.current.has(id)) { + return; + } + requestsInProgress.current.add(id); + + console.warn('Incoming request:', { id, method, params }); + + const response: JsonRpcResponse = { + id, + jsonrpc: '2.0', + // @ts-ignore + method + }; + + try { + switch (method) { + case 'eth_requestAccounts': { + const address = await getCurrentAddress(); + if (!address) { + throw new Error('No account available'); + } + response.result = [address]; + setConnectedAddress(address); + break; + } + + case 'eth_accounts': { + response.result = connectedAddress ? [connectedAddress] : []; + break; + } + + case 'eth_chainId': + response.result = await handleChainIdRequest(); + break; + + case 'eth_sendTransaction': + response.result = await handleSendTransaction(params[0]); + break; + + case 'eth_signTransaction': + response.result = await handleSignTransaction(params[0]); + break; + + case 'personal_sign': + response.result = await handlePersonalSign(params); + break; + + case 'eth_sign': + response.result = await handleEthSign(params); + break; + + case 'wallet_switchEthereumChain': + response.result = await handleSwitchChain(params[0]); + break; + + case 'wallet_addEthereumChain': + response.result = await handleAddChain(params[0]); + break; + + case 'eth_getBalance': + response.result = await handleGetBalance(params[0], params[1]); + break; + + default: + response.error = { + code: 4200, + message: `Method ${method} not supported` + }; } - try { - const message = params[0]; - const walletAddress = params[1]; - const signature = await signMessage(message, walletAddress); - sendResponse(method, { result: signature }); - } catch (error) { - sendResponse(method, { - error: (error as { message: string }).message - }); + + sendResponse(response); + } finally { + requestsInProgress.current.delete(id); + } + } catch (error) { + console.error('Failed to handle WebView message:', error); + sendResponse({ + id: -1, + jsonrpc: '2.0', + error: { + code: 4001, + message: + (error as { message?: string })?.message || 'Unknown error occurred' } - break; - - case 'eth_requestAccounts': - const accounts = [wallet.address]; - sendResponse(method, { result: accounts }); - break; - - case 'eth_chainId': - // Respond with chainId - sendResponse(method, { result: '0x414e' }); - break; - - case 'eth_networkVersion': - // Respond with network version - sendResponse(method, { result: '16718' }); - break; - - case 'eth_blockNumber': - // Respond with block number - sendResponse(method, { result: '0x10d4f' }); - break; - case 'eth_getBalance': - // Respond with block number - sendResponse(method, { result: await wallet.getBalance() }); - break; - - default: - sendResponse(method, { error: `Unsupported method: ${method}` }); + }); } }; - const sendResponse = ( - method: any, - response: { error?: any; result?: string | ethers.BigNumber | string[] } - ) => { - const message = JSON.stringify({ method, ...response }); - webViewRef.current?.postMessage(message); + // Handler implementations + const handleChainIdRequest = async () => { + // Get current chain ID in hex format + return `0x${Number(16718).toString(16)}`; }; - const signMessage = async ( - message: string | ethers.utils.Bytes, - walletAddress: string - ) => { - const privateKey = await _extractPrivateKey(); // Replace with your wallet's private key - const wallet = new ethers.Wallet(privateKey, createAMBProvider()); - const signature = await wallet.signMessage(message); + const handleSendTransaction = async (txParams: any) => { + // Handle transaction sending + const wallet = await extractWallet(); + const tx = await wallet.sendTransaction(txParams); + return tx.hash; + }; - // Verify that the signed message corresponds to the wallet address - const recoveredAddress = ethers.utils.verifyMessage(message, signature); - if (recoveredAddress.toLowerCase() !== walletAddress.toLowerCase()) { - throw new Error('Recovered address does not match the wallet address.'); - } + const handleSignTransaction = async (txParams: any) => { + // Handle transaction signing + const wallet = await extractWallet(); + const signedTx = await wallet.signTransaction(txParams); + return signedTx; + }; + const handlePersonalSign = async ([message, address, password]: any[]) => { + // Handle personal_sign + const wallet = await extractWallet(); + const signature = await wallet.signMessage(message); return signature; }; + const handleEthSign = async ([address, message]: any[]) => { + // Handle eth_sign + const wallet = await extractWallet(); + const signature = await wallet.signMessage(message); + return signature; + }; + + const handleSwitchChain = async (params: { chainId: string }) => { + // Handle chain switching + const wallet = await extractWallet(); + return null; + }; + + const handleAddChain = async (chainParams: any) => { + // Handle adding new chain + const wallet = await extractWallet(); + return null; + }; + + const handleGetBalance = async (address: string, blockTag = 'latest') => { + // Get account balance + const provider = new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); + const balance = await provider.getBalance(address, blockTag); + return balance.toHexString(); + }; + + const sendResponse = (response: JsonRpcResponse) => { + if (webViewRef.current) { + const messageString = JSON.stringify(response); + console.warn('Sending response:', messageString); + webViewRef.current.postMessage(messageString); + } + }; + + useEffect(() => { + if (webViewRef.current) { + const updateScript = ` + if (window.ethereum) { + window.ethereum.selectedAddress = ${ + connectedAddress ? `'${connectedAddress}'` : 'null' + }; + const listeners = window.ethereum._events.get('accountsChanged'); + if (listeners) { + listeners.forEach(listener => listener(${ + connectedAddress ? `['${connectedAddress}']` : '[]' + })); + } + } + `; + webViewRef.current.injectJavaScript(updateScript); + } + }, [connectedAddress]); + return ( @@ -115,15 +259,21 @@ export const BrowserScreen = () => { + {connectedAddress && ( + Connected: {connectedAddress} + )} ); From 1ba39bbb9145011fee0064d96504309e95d01334 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:53:13 +0200 Subject: [PATCH 04/17] refactor: update createAMBProvider to deprecate networkUrl parameter - Added a deprecation notice for the `networkUrl` parameter in `createAMBProvider`, recommending the use of `Config.NETWORK_URL` instead. - Enhanced documentation for better clarity on the function's usage and future changes. --- src/features/swap/utils/contracts/instances.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/features/swap/utils/contracts/instances.ts b/src/features/swap/utils/contracts/instances.ts index 86fb8660b..e73107c8f 100644 --- a/src/features/swap/utils/contracts/instances.ts +++ b/src/features/swap/utils/contracts/instances.ts @@ -4,6 +4,12 @@ import { FACTORY_ABI } from '@features/swap/lib/abi'; type ProviderOrSigner = ethers.providers.JsonRpcProvider | ethers.Signer; +/** + * Creates an AMB Chain provider instance + * @param networkUrl - @deprecated This parameter is deprecated and will be removed in the next major version. + * Use Config.NETWORK_URL instead. + * @returns ethers.providers.JsonRpcProvider + */ export function createAMBProvider(networkUrl?: string) { return new ethers.providers.JsonRpcProvider( networkUrl || Config.NETWORK_URL, From c5285e62c37277e2ac02c1773dc5bc4cf1849e77 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:26:28 +0200 Subject: [PATCH 05/17] feat: enhance BrowserScreen with wallet permissions and request handling - Added support for wallet permissions management, including `wallet_requestPermissions`, `wallet_revokePermissions`, and `wallet_getPermissions` methods. - Updated the handling of Ethereum methods to include permission-related responses and improved error handling. - Refactored the `extractWallet` function to return the wallet instance for better integration with permission requests. - Enhanced logging for incoming requests and responses to aid in debugging and monitoring. - Updated the chain ID handling to use a constant for improved maintainability. --- src/features/browser/constants/index.ts | 1 + src/features/browser/constants/rpc.ts | 2 + src/features/browser/lib/index.ts | 1 + .../browser/lib/injectable.provider.ts | 123 +++++++++++++++ .../browser/lib/rpc-middleware-methods.ts | 93 +++++++++++ src/screens/Browser/index.tsx | 146 ++++++++++++++++-- 6 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 src/features/browser/constants/index.ts create mode 100644 src/features/browser/constants/rpc.ts create mode 100644 src/features/browser/lib/index.ts create mode 100644 src/features/browser/lib/injectable.provider.ts create mode 100644 src/features/browser/lib/rpc-middleware-methods.ts diff --git a/src/features/browser/constants/index.ts b/src/features/browser/constants/index.ts new file mode 100644 index 000000000..e2c825252 --- /dev/null +++ b/src/features/browser/constants/index.ts @@ -0,0 +1 @@ +export * from './rpc'; diff --git a/src/features/browser/constants/rpc.ts b/src/features/browser/constants/rpc.ts new file mode 100644 index 000000000..dd3f4970d --- /dev/null +++ b/src/features/browser/constants/rpc.ts @@ -0,0 +1,2 @@ +export const AMB_CHAIN_ID_HEX = '0x414e'; +export const AMB_CHAIN_ID_DEC = 16718; diff --git a/src/features/browser/lib/index.ts b/src/features/browser/lib/index.ts new file mode 100644 index 000000000..93535b572 --- /dev/null +++ b/src/features/browser/lib/index.ts @@ -0,0 +1 @@ +export * from './injectable.provider'; diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts new file mode 100644 index 000000000..122ed6fb4 --- /dev/null +++ b/src/features/browser/lib/injectable.provider.ts @@ -0,0 +1,123 @@ +export const INJECTED_PROVIDER_JS = ` + (function() { + if (window.ethereum) return; + + let isInitialized = false; + let requestCounter = 0; + const pendingRequests = new Map(); + + // EIP-6963 + const providerInfo = { + uuid: 'c7df86fd-339b-4d51-8ad6-6a600535d86a', + name: 'AMB Wallet', + icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', + rdns: 'io.airdao.app' + }; + + // Create the provider object + const provider = { + isMetaMask: true, + isConnected: () => true, + _metamask: { + isUnlocked: () => true, + }, + selectedAddress: null, + chainId: '0x414e', + networkVersion: '16718', + + request: function(args) { + return new Promise((resolve, reject) => { + const { method, params } = args; + const id = requestCounter++; + + pendingRequests.set(id, { resolve, reject }); + + window.ReactNativeWebView.postMessage(JSON.stringify({ + id, + jsonrpc: '2.0', + method, + params: params || [] + })); + }); + }, + + // Simplified event system + _events: new Map(), + + on: function(eventName, callback) { + if (!this._events.has(eventName)) { + this._events.set(eventName, new Set()); + } + this._events.get(eventName).add(callback); + return () => this._events.get(eventName).delete(callback); + }, + + removeListener: function(eventName, callback) { + if (this._events.has(eventName)) { + this._events.get(eventName).delete(callback); + } + }, + + enable: function() { + return this.request({ method: 'eth_requestAccounts' }); + }, + + info: providerInfo, + + announceProvider: function() { + window.dispatchEvent( + new CustomEvent('eip6963:announceProvider', { + detail: { + info: this.info, + provider: this + } + }) + ); + } + }; + + window.addEventListener('message', function(event) { + try { + const response = JSON.parse(event.data); + const { id, result, error } = response; + + const pendingRequest = pendingRequests.get(id); + if (pendingRequest) { + pendingRequests.delete(id); + + if (error) { + pendingRequest.reject(new Error(error.message)); + } else { + if (response.method === 'eth_requestAccounts' || response.method === 'eth_accounts') { + provider.selectedAddress = result[0]; + const listeners = provider._events.get('accountsChanged'); + if (listeners) { + listeners.forEach(listener => listener(result)); + } + } + pendingRequest.resolve(result); + } + } + } catch (error) { + console.error('Failed to process message:', error); + } + }); + + window.ethereum = provider; + + if (!isInitialized) { + isInitialized = true; + + // Announce provider immediately + provider.announceProvider(); + + // Listen for provider requests + window.addEventListener('eip6963:requestProvider', function() { + provider.announceProvider(); + }); + + // Dispatch ethereum#initialized for backward compatibility + window.dispatchEvent(new Event('ethereum#initialized')); + } + })(); + `; diff --git a/src/features/browser/lib/rpc-middleware-methods.ts b/src/features/browser/lib/rpc-middleware-methods.ts new file mode 100644 index 000000000..6b832598f --- /dev/null +++ b/src/features/browser/lib/rpc-middleware-methods.ts @@ -0,0 +1,93 @@ +import { ethers } from 'ethers'; +import { createAMBProvider } from '@features/swap/utils/contracts/instances'; + +export class RpcMiddlewareMethods { + private wallet: ethers.Wallet | null = null; + private initPromise: Promise | null = null; + + constructor(private privateKey: string) { + const provider = createAMBProvider(); + this.wallet = new ethers.Wallet(privateKey, provider); + } + + /** + * Ensures the wallet is initialized before executing any method + */ + private async ensureWallet(): Promise { + if (this.initPromise) { + await this.initPromise; + this.initPromise = null; + } + + if (!this.wallet) { + throw new Error('Wallet not initialized'); + } + + return this.wallet; + } + + /** + * Gets the current wallet address + */ + async getAddress(): Promise { + const wallet = await this.ensureWallet(); + return wallet.address; + } + + /** + * Signs a transaction + */ + async signTransaction( + transaction: ethers.providers.TransactionRequest + ): Promise { + const wallet = await this.ensureWallet(); + return wallet.signTransaction(transaction); + } + + /** + * Signs a message + */ + async signMessage(message: string): Promise { + const wallet = await this.ensureWallet(); + return wallet.signMessage(message); + } + + /** + * Sends a transaction + */ + async sendTransaction( + transaction: ethers.providers.TransactionRequest + ): Promise { + const wallet = await this.ensureWallet(); + return wallet.sendTransaction(transaction); + } + + /** + * Gets the balance for an address + */ + async getBalance(address: string, blockTag = 'latest'): Promise { + const wallet = await this.ensureWallet(); + const balance = await wallet.provider.getBalance(address, blockTag); + return balance.toHexString(); + } + + /** + * Gets the chain ID + */ + async getChainId(): Promise { + const wallet = await this.ensureWallet(); + const network = await wallet.provider.getNetwork(); + return `0x${network.chainId.toString(16)}`; + } +} + +// Example usage: +/* +const rpcMethods = new RpcMiddlewareMethods(async () => { + // Your logic to get private key + return privateKey; +}); + +// Methods will automatically ensure wallet is initialized +const address = await rpcMethods.getAddress(); +*/ diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 184db578a..56ca42c7b 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -1,4 +1,6 @@ +// @ts-nocheck /* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-console */ import React, { useCallback, useEffect, @@ -13,9 +15,16 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Text } from '@components/base'; import Config from '@constants/config'; import { useWalletPrivateKey } from '@entities/wallet'; + +import { + AMB_CHAIN_ID_DEC, + AMB_CHAIN_ID_HEX +} from '@features/browser/constants'; +import { INJECTED_PROVIDER_JS } from '@features/browser/lib'; import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { + // uri: 'https://7z2dkc.csb.app/' uri: 'https://metamask.github.io/test-dapp/' }; @@ -44,11 +53,10 @@ export const BrowserScreen = () => { const extractWallet = useCallback(async () => { const privateKey = await _extractPrivateKey(); - return new ethers.Wallet(privateKey, createAMBProvider()); + const wallet = new ethers.Wallet(privateKey, createAMBProvider()); + return wallet; }, [_extractPrivateKey]); - const INJECTED_PROVIDER_JS = ''; - const reload = () => webViewRef.current?.reload(); const back = () => webViewRef.current?.goBack(); @@ -87,17 +95,20 @@ export const BrowserScreen = () => { } requestsInProgress.current.add(id); - console.warn('Incoming request:', { id, method, params }); + console.log('Incoming request:', { id, method, params }); const response: JsonRpcResponse = { id, jsonrpc: '2.0', - // @ts-ignore method }; try { switch (method) { + case 'net_version': + response.result = AMB_CHAIN_ID_DEC; + break; + case 'eth_requestAccounts': { const address = await getCurrentAddress(); if (!address) { @@ -113,6 +124,120 @@ export const BrowserScreen = () => { break; } + case 'wallet_requestPermissions': { + const permissions = params[0]; + if (permissions?.eth_accounts) { + const address = await getCurrentAddress(); + if (!address) { + throw new Error('No account available'); + } + response.result = [ + { + // eth_accounts permission + parentCapability: 'eth_accounts', + date: Date.now(), + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [connectedAddress] + } + ] + }, + { + // permitted chains permission + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; + setConnectedAddress(address); + } else { + response.error = { + code: 4200, + message: 'Requested permission is not available' + }; + } + break; + } + + case 'wallet_revokePermissions': { + const permissions = params[0]; + if (permissions?.eth_accounts) { + // Set a flag to prevent re-rendering loops + const wasConnected = !!connectedAddress; + + // Update local state only if needed + if (wasConnected) { + setConnectedAddress(null); + + // Send a single update to the WebView + const updateScript = ` + (function() { + if (window.ethereum && window.ethereum.selectedAddress) { + window.ethereum.selectedAddress = null; + // Emit event only once + const event = new CustomEvent('accountsChanged', { detail: [] }); + window.dispatchEvent(event); + } + })(); + true; // Required for iOS + `; + + // Use requestAnimationFrame to prevent multiple updates + webViewRef.current?.injectJavaScript(` + requestAnimationFrame(() => { + ${updateScript} + }); + `); + } + + response.result = null; + } else { + response.error = { + code: 4200, + message: 'Permission to revoke is not recognized' + }; + } + break; + } + + case 'wallet_getPermissions': { + if (connectedAddress) { + response.result = [ + { + // eth_accounts permission + parentCapability: 'eth_accounts', + date: Date.now(), + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [connectedAddress] + } + ] + }, + { + // permitted chains permission + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: ['AMB_CHAIN_ID_HEX'] // AMB Chain ID + } + ] + } + ]; + } else { + response.result = []; + } + break; + } + case 'eth_chainId': response.result = await handleChainIdRequest(); break; @@ -159,12 +284,11 @@ export const BrowserScreen = () => { } catch (error) { console.error('Failed to handle WebView message:', error); sendResponse({ - id: -1, + id: request?.id || -1, jsonrpc: '2.0', error: { code: 4001, - message: - (error as { message?: string })?.message || 'Unknown error occurred' + message: error.message || 'Unknown error occurred' } }); } @@ -173,7 +297,7 @@ export const BrowserScreen = () => { // Handler implementations const handleChainIdRequest = async () => { // Get current chain ID in hex format - return `0x${Number(16718).toString(16)}`; + return `0x${Number(AMB_CHAIN_ID_DEC).toString(16)}`; }; const handleSendTransaction = async (txParams: any) => { @@ -207,12 +331,14 @@ export const BrowserScreen = () => { const handleSwitchChain = async (params: { chainId: string }) => { // Handle chain switching const wallet = await extractWallet(); + await wallet.switchEthereumChain(parseInt(params.chainId, 16)); return null; }; const handleAddChain = async (chainParams: any) => { // Handle adding new chain const wallet = await extractWallet(); + await wallet.addChain(chainParams); return null; }; @@ -226,7 +352,7 @@ export const BrowserScreen = () => { const sendResponse = (response: JsonRpcResponse) => { if (webViewRef.current) { const messageString = JSON.stringify(response); - console.warn('Sending response:', messageString); + console.log('Sending response:', response); webViewRef.current.postMessage(messageString); } }; From 9e8cdf2d9c43c8a9387b4ed0f0625ea558bdc942 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:00:10 +0200 Subject: [PATCH 06/17] feat: enhance BrowserScreen with console logging and message signing confirmation - Introduced a custom console implementation to log messages from the webview to the React Native environment. - Updated the BrowserScreen to include user confirmation dialogs for signing messages and typed data, improving user experience and security. - Refactored the handling of Ethereum methods to ensure address verification and error handling during signing processes. - Changed the webview source URL to 'https://airquest.xyz/' for updated functionality. - Enhanced logging for incoming webview messages to aid in debugging and monitoring. --- .../browser/lib/injectable.provider.ts | 8 + src/screens/Browser/index.tsx | 255 ++++++++++++++---- 2 files changed, 216 insertions(+), 47 deletions(-) diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index 122ed6fb4..8f0fa33a2 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -1,4 +1,12 @@ export const INJECTED_PROVIDER_JS = ` + const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}})); + console = { + log: (log) => consoleLog('log', log), + debug: (log) => consoleLog('debug', log), + info: (log) => consoleLog('info', log), + warn: (log) => consoleLog('warn', log), + error: (log) => consoleLog('error', log), + }; (function() { if (window.ethereum) return; diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 56ca42c7b..b8496c7e6 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -8,7 +8,7 @@ import React, { useRef, useState } from 'react'; -import { StyleProp, ViewStyle } from 'react-native'; +import { StyleProp, ViewStyle, Alert } from 'react-native'; import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; import { ethers } from 'ethers'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -24,8 +24,8 @@ import { INJECTED_PROVIDER_JS } from '@features/browser/lib'; import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { - // uri: 'https://7z2dkc.csb.app/' - uri: 'https://metamask.github.io/test-dapp/' + uri: 'https://airquest.xyz/' + // uri: 'https://metamask.github.io/test-dapp/' }; interface JsonRpcRequest { @@ -77,16 +77,9 @@ export const BrowserScreen = () => { } }, [extractWallet]); - useEffect(() => { - getCurrentAddress().then((address) => { - if (address) { - setConnectedAddress(address); - } - }); - }, [getCurrentAddress]); - const handleWebViewMessage = async (event: WebViewMessageEvent) => { try { + logger(event); const request: JsonRpcRequest = JSON.parse(event.nativeEvent.data); const { id, method, params } = request; @@ -110,12 +103,34 @@ export const BrowserScreen = () => { break; case 'eth_requestAccounts': { + console.log('eth_requestAccounts called'); const address = await getCurrentAddress(); if (!address) { throw new Error('No account available'); } response.result = [address]; setConnectedAddress(address); + + const updateScript = ` + (function() { + try { + if (window.ethereum) { + window.ethereum.selectedAddress = '${address}'; + window.ethereum.isConnected = () => true; + window.ethereum.chainId = '${AMB_CHAIN_ID_HEX}'; + + // Emit proper events + window.ethereum.emit('connect', { chainId: '${AMB_CHAIN_ID_HEX}' }); + window.ethereum.emit('accountsChanged', ['${address}']); + window.ethereum.emit('chainChanged', '${AMB_CHAIN_ID_HEX}'); + } + } catch(e) { + console.error('Injection error:', e); + } + return true; + })(); + `; + webViewRef.current?.injectJavaScript(updateScript); break; } @@ -250,13 +265,100 @@ export const BrowserScreen = () => { response.result = await handleSignTransaction(params[0]); break; - case 'personal_sign': - response.result = await handlePersonalSign(params); + case 'personal_sign': { + try { + console.log('Personal sign request:', params); + const [message, address] = params; + + // Verify address matches + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + throw new Error('Address mismatch'); + } + + // Create a promise that resolves when user confirms + const userConfirmation = new Promise((resolve, reject) => { + Alert.alert( + 'Sign Message', + `Do you want to sign this message?\n\nMessage: ${message}\n\nAddress: ${address}`, + [ + { + text: 'Cancel', + onPress: () => reject(new Error('User rejected signing')), + style: 'cancel' + }, + { + text: 'Sign', + onPress: () => resolve(true), + style: 'default' + } + ], + { cancelable: false } + ); + }); + + // Wait for user confirmation before signing + await userConfirmation; + + const wallet = await extractWallet(); + const signature = await wallet.signMessage( + ethers.utils.isHexString(message) + ? ethers.utils.arrayify(message) + : message + ); + + console.log('Signature generated:', signature); + response.result = signature; + } catch (error) { + console.error('Personal sign error:', error); + throw error; + } break; + } + + case 'eth_sign': { + try { + const [address, message] = params; + + // Verify address matches + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + throw new Error('Address mismatch'); + } - case 'eth_sign': - response.result = await handleEthSign(params); + // Create a promise that resolves when user confirms + const userConfirmation = new Promise((resolve, reject) => { + Alert.alert( + 'Sign Message', + `Do you want to sign this message?\n\nMessage: ${message}\n\nAddress: ${address}`, + [ + { + text: 'Cancel', + onPress: () => reject(new Error('User rejected signing')), + style: 'cancel' + }, + { + text: 'Sign', + onPress: () => resolve(true), + style: 'default' + } + ], + { cancelable: false } + ); + }); + + // Wait for user confirmation before signing + await userConfirmation; + + const wallet = await extractWallet(); + const signature = await wallet.signMessage(message); + + console.log('Signature generated:', signature); + response.result = signature; + } catch (error) { + console.error('Eth sign error:', error); + throw error; + } break; + } case 'wallet_switchEthereumChain': response.result = await handleSwitchChain(params[0]); @@ -270,6 +372,66 @@ export const BrowserScreen = () => { response.result = await handleGetBalance(params[0], params[1]); break; + case 'eth_signTypedData_v4': + case 'eth_signTypedData': { + try { + console.log('Sign typed data request:', params); + const [address, typedData] = params; + + // Verify address matches + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + throw new Error('Address mismatch'); + } + + const data = + typeof typedData === 'string' + ? JSON.parse(typedData) + : typedData; + + // Create a promise that resolves when user confirms + const userConfirmation = new Promise((resolve, reject) => { + Alert.alert( + 'Sign Typed Data', + `Do you want to sign this data?\n\nFrom: ${address}\n\nData: ${JSON.stringify( + data, + null, + 2 + )}`, + [ + { + text: 'Cancel', + onPress: () => reject(new Error('User rejected signing')), + style: 'cancel' + }, + { + text: 'Sign', + onPress: () => resolve(true), + style: 'default' + } + ], + { cancelable: false } + ); + }); + + // Wait for user confirmation before signing + await userConfirmation; + + const wallet = await extractWallet(); + const signature = await wallet._signTypedData( + data.domain, + data.types, + data.message + ); + + console.log('Signature generated:', signature); + response.result = signature; + } catch (error) { + console.error('Failed to sign typed data:', error); + throw error; + } + break; + } + default: response.error = { code: 4200, @@ -282,13 +444,12 @@ export const BrowserScreen = () => { requestsInProgress.current.delete(id); } } catch (error) { - console.error('Failed to handle WebView message:', error); sendResponse({ - id: request?.id || -1, + id: -1, jsonrpc: '2.0', error: { code: 4001, - message: error.message || 'Unknown error occurred' + message: (error as Error).message || 'Unknown error occurred' } }); } @@ -296,7 +457,6 @@ export const BrowserScreen = () => { // Handler implementations const handleChainIdRequest = async () => { - // Get current chain ID in hex format return `0x${Number(AMB_CHAIN_ID_DEC).toString(16)}`; }; @@ -314,20 +474,6 @@ export const BrowserScreen = () => { return signedTx; }; - const handlePersonalSign = async ([message, address, password]: any[]) => { - // Handle personal_sign - const wallet = await extractWallet(); - const signature = await wallet.signMessage(message); - return signature; - }; - - const handleEthSign = async ([address, message]: any[]) => { - // Handle eth_sign - const wallet = await extractWallet(); - const signature = await wallet.signMessage(message); - return signature; - }; - const handleSwitchChain = async (params: { chainId: string }) => { // Handle chain switching const wallet = await extractWallet(); @@ -358,24 +504,39 @@ export const BrowserScreen = () => { }; useEffect(() => { - if (webViewRef.current) { + if (webViewRef.current && connectedAddress) { const updateScript = ` - if (window.ethereum) { - window.ethereum.selectedAddress = ${ - connectedAddress ? `'${connectedAddress}'` : 'null' - }; - const listeners = window.ethereum._events.get('accountsChanged'); - if (listeners) { - listeners.forEach(listener => listener(${ - connectedAddress ? `['${connectedAddress}']` : '[]' - })); + (function() { + try { + if (window.ethereum) { + window.ethereum.selectedAddress = '${connectedAddress}'; + window.ethereum.emit('accountsChanged', ['${connectedAddress}']); + window.ethereum.emit('connect', { chainId: '${AMB_CHAIN_ID_HEX}' }); + } + } catch(e) { + console.error('Connection update error:', e); } - } + return true; + })(); `; webViewRef.current.injectJavaScript(updateScript); } }, [connectedAddress]); + // Add this to monitor all WebView messages + const logger = (event: WebViewMessageEvent) => { + try { + const data = JSON.parse(event.nativeEvent.data); + console.log('WebView message:', { + method: data.method, + params: data.params, + id: data.id + }); + } catch (e) { + console.log('Raw WebView message:', event.nativeEvent.data); + } + }; + return ( @@ -390,11 +551,11 @@ export const BrowserScreen = () => { )} Date: Wed, 22 Jan 2025 17:14:55 +0200 Subject: [PATCH 07/17] feat: enhance BrowserScreen with Ethereum state management and permissions handling - Introduced new JavaScript functions for revoking permissions and updating Ethereum state, improving interaction with the webview. - Refactored the BrowserScreen to utilize these new functions, streamlining the process of managing Ethereum accounts and permissions. - Updated the handling of wallet permissions to ensure proper state updates and event emissions, enhancing user experience and security. - Improved error handling and logging for Ethereum-related actions, aiding in debugging and monitoring. - Changed the webview source URL to 'https://metamask.github.io/test-dapp/' for testing purposes. --- .../browser/lib/injectable.provider.ts | 90 +++++++++++ src/screens/Browser/index.tsx | 152 ++++++++---------- 2 files changed, 159 insertions(+), 83 deletions(-) diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index 8f0fa33a2..a452826ab 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -1,3 +1,5 @@ +import { AMB_CHAIN_ID_HEX } from '../constants'; + export const INJECTED_PROVIDER_JS = ` const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}})); console = { @@ -129,3 +131,91 @@ export const INJECTED_PROVIDER_JS = ` } })(); `; +export const REVOKE_PERMISSIONS_JS = ` + (function() { + try { + if (window.ethereum) { + // Update state first + window.ethereum.selectedAddress = null; + + // Then emit events once + if (window.ethereum._events && window.ethereum._events.get('accountsChanged')) { + const listeners = window.ethereum._events.get('accountsChanged'); + const accounts = []; + listeners.forEach(listener => { + try { + listener(accounts); + } catch (e) { + console.error('Listener error:', e); + } + }); + } + + // Keep chainId and chain permissions intact + window.ethereum.chainId = '${AMB_CHAIN_ID_HEX}'; + } + return true; + } catch (e) { + console.error('Revoke update error:', e); + return true; + } + })(); +`; + +export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` + (function() { + try { + if (window.ethereum) { + // Update provider state + window.ethereum.selectedAddress = '${address}'; + window.ethereum.isConnected = () => true; + window.ethereum.chainId = '${chainId}'; + + // Emit events in correct order + const events = window.ethereum._events || new Map(); + + // 1. Connect event + if (events.has('connect')) { + events.get('connect').forEach(listener => { + try { + listener({ chainId: '${chainId}' }); + } catch(e) { + console.error('Connect event error:', e); + } + }); + } + + // 2. Chain changed event + if (events.has('chainChanged')) { + events.get('chainChanged').forEach(listener => { + try { + listener('${chainId}'); + } catch(e) { + console.error('Chain change event error:', e); + } + }); + } + + // 3. Accounts changed event + if (events.has('accountsChanged')) { + events.get('accountsChanged').forEach(listener => { + try { + listener(['${address}']); + } catch(e) { + console.error('Account change event error:', e); + } + }); + } + + console.log('Ethereum state updated:', { + address: '${address}', + chainId: '${chainId}', + isConnected: true + }); + } + } catch(e) { + console.error('State update error:', e); + } + return true; + })(); +`; diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index b8496c7e6..db49445f7 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -20,12 +20,16 @@ import { AMB_CHAIN_ID_DEC, AMB_CHAIN_ID_HEX } from '@features/browser/constants'; -import { INJECTED_PROVIDER_JS } from '@features/browser/lib'; +import { + INJECTED_PROVIDER_JS, + REVOKE_PERMISSIONS_JS, + UPDATE_ETHEREUM_STATE_JS +} from '@features/browser/lib'; import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { - uri: 'https://airquest.xyz/' - // uri: 'https://metamask.github.io/test-dapp/' + // uri: 'https://airquest.xyz/' + uri: 'https://metamask.github.io/test-dapp/' }; interface JsonRpcRequest { @@ -111,26 +115,10 @@ export const BrowserScreen = () => { response.result = [address]; setConnectedAddress(address); - const updateScript = ` - (function() { - try { - if (window.ethereum) { - window.ethereum.selectedAddress = '${address}'; - window.ethereum.isConnected = () => true; - window.ethereum.chainId = '${AMB_CHAIN_ID_HEX}'; - - // Emit proper events - window.ethereum.emit('connect', { chainId: '${AMB_CHAIN_ID_HEX}' }); - window.ethereum.emit('accountsChanged', ['${address}']); - window.ethereum.emit('chainChanged', '${AMB_CHAIN_ID_HEX}'); - } - } catch(e) { - console.error('Injection error:', e); - } - return true; - })(); - `; - webViewRef.current?.injectJavaScript(updateScript); + webViewRef.current?.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + break; } @@ -148,18 +136,16 @@ export const BrowserScreen = () => { } response.result = [ { - // eth_accounts permission parentCapability: 'eth_accounts', date: Date.now(), caveats: [ { type: 'restrictReturnedAccounts', - value: [connectedAddress] + value: [address] } ] }, { - // permitted chains permission parentCapability: 'endowment:permitted-chains', date: Date.now(), caveats: [ @@ -170,6 +156,13 @@ export const BrowserScreen = () => { ] } ]; + + if (webViewRef.current) { + webViewRef.current.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + } + setConnectedAddress(address); } else { response.error = { @@ -183,35 +176,43 @@ export const BrowserScreen = () => { case 'wallet_revokePermissions': { const permissions = params[0]; if (permissions?.eth_accounts) { - // Set a flag to prevent re-rendering loops - const wasConnected = !!connectedAddress; - - // Update local state only if needed - if (wasConnected) { + if (connectedAddress) { setConnectedAddress(null); + response.result = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; - // Send a single update to the WebView - const updateScript = ` - (function() { - if (window.ethereum && window.ethereum.selectedAddress) { - window.ethereum.selectedAddress = null; - // Emit event only once - const event = new CustomEvent('accountsChanged', { detail: [] }); - window.dispatchEvent(event); - } - })(); - true; // Required for iOS - `; - - // Use requestAnimationFrame to prevent multiple updates - webViewRef.current?.injectJavaScript(` - requestAnimationFrame(() => { - ${updateScript} - }); - `); + if (webViewRef.current) { + webViewRef.current.injectJavaScript(REVOKE_PERMISSIONS_JS); + } + } else { + response.result = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; } - - response.result = null; + } else if (permissions?.['endowment:permitted-chains']) { + response.error = { + code: 4200, + message: 'Chain permissions cannot be revoked' + }; } else { response.error = { code: 4200, @@ -222,10 +223,22 @@ export const BrowserScreen = () => { } case 'wallet_getPermissions': { + const basePermissions = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; + if (connectedAddress) { response.result = [ { - // eth_accounts permission parentCapability: 'eth_accounts', date: Date.now(), caveats: [ @@ -235,20 +248,10 @@ export const BrowserScreen = () => { } ] }, - { - // permitted chains permission - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: ['AMB_CHAIN_ID_HEX'] // AMB Chain ID - } - ] - } + ...basePermissions ]; } else { - response.result = []; + response.result = basePermissions; } break; } @@ -270,12 +273,10 @@ export const BrowserScreen = () => { console.log('Personal sign request:', params); const [message, address] = params; - // Verify address matches if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { throw new Error('Address mismatch'); } - // Create a promise that resolves when user confirms const userConfirmation = new Promise((resolve, reject) => { Alert.alert( 'Sign Message', @@ -296,7 +297,6 @@ export const BrowserScreen = () => { ); }); - // Wait for user confirmation before signing await userConfirmation; const wallet = await extractWallet(); @@ -319,12 +319,10 @@ export const BrowserScreen = () => { try { const [address, message] = params; - // Verify address matches if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { throw new Error('Address mismatch'); } - // Create a promise that resolves when user confirms const userConfirmation = new Promise((resolve, reject) => { Alert.alert( 'Sign Message', @@ -345,7 +343,6 @@ export const BrowserScreen = () => { ); }); - // Wait for user confirmation before signing await userConfirmation; const wallet = await extractWallet(); @@ -378,7 +375,6 @@ export const BrowserScreen = () => { console.log('Sign typed data request:', params); const [address, typedData] = params; - // Verify address matches if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { throw new Error('Address mismatch'); } @@ -388,7 +384,6 @@ export const BrowserScreen = () => { ? JSON.parse(typedData) : typedData; - // Create a promise that resolves when user confirms const userConfirmation = new Promise((resolve, reject) => { Alert.alert( 'Sign Typed Data', @@ -413,7 +408,6 @@ export const BrowserScreen = () => { ); }); - // Wait for user confirmation before signing await userConfirmation; const wallet = await extractWallet(); @@ -455,41 +449,34 @@ export const BrowserScreen = () => { } }; - // Handler implementations const handleChainIdRequest = async () => { return `0x${Number(AMB_CHAIN_ID_DEC).toString(16)}`; }; const handleSendTransaction = async (txParams: any) => { - // Handle transaction sending const wallet = await extractWallet(); const tx = await wallet.sendTransaction(txParams); return tx.hash; }; const handleSignTransaction = async (txParams: any) => { - // Handle transaction signing const wallet = await extractWallet(); - const signedTx = await wallet.signTransaction(txParams); - return signedTx; + return await wallet.signTransaction(txParams); }; const handleSwitchChain = async (params: { chainId: string }) => { - // Handle chain switching const wallet = await extractWallet(); await wallet.switchEthereumChain(parseInt(params.chainId, 16)); return null; }; const handleAddChain = async (chainParams: any) => { - // Handle adding new chain const wallet = await extractWallet(); await wallet.addChain(chainParams); return null; }; const handleGetBalance = async (address: string, blockTag = 'latest') => { - // Get account balance const provider = new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); const balance = await provider.getBalance(address, blockTag); return balance.toHexString(); @@ -523,7 +510,6 @@ export const BrowserScreen = () => { } }, [connectedAddress]); - // Add this to monitor all WebView messages const logger = (event: WebViewMessageEvent) => { try { const data = JSON.parse(event.nativeEvent.data); From aceeb361cc87e9b9739ecc5b4f4f6365b12537e7 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:20:54 +0200 Subject: [PATCH 08/17] feat: enhance injected provider with improved event handling and state management - Refactored the event handling system to ensure events are emitted only when state changes, reducing unnecessary calls and improving performance. - Introduced a new state management structure to track the last emitted state for accounts, chain ID, and connection status. - Updated the revoke permissions and update Ethereum state functions to utilize the new event system, enhancing reliability and clarity. - Changed the webview source URL to 'https://metamask.github.io/test-dapp/' for testing purposes. --- .../browser/lib/injectable.provider.ts | 138 ++++++++++-------- src/screens/Browser/index.tsx | 2 +- 2 files changed, 77 insertions(+), 63 deletions(-) diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index a452826ab..44fb50a55 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -1,5 +1,3 @@ -import { AMB_CHAIN_ID_HEX } from '../constants'; - export const INJECTED_PROVIDER_JS = ` const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}})); console = { @@ -20,7 +18,7 @@ export const INJECTED_PROVIDER_JS = ` const providerInfo = { uuid: 'c7df86fd-339b-4d51-8ad6-6a600535d86a', name: 'AMB Wallet', - icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', + icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6TSdt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', rdns: 'io.airdao.app' }; @@ -51,17 +49,78 @@ export const INJECTED_PROVIDER_JS = ` }); }, - // Simplified event system + // Improved event handling system _events: new Map(), + _lastEmittedState: { + accounts: null, + chainId: null, + connected: false + }, on: function(eventName, callback) { if (!this._events.has(eventName)) { this._events.set(eventName, new Set()); } this._events.get(eventName).add(callback); + + // Immediately emit current state for certain events + // but only if it's different from last emitted state + switch(eventName) { + case 'accountsChanged': + if (this.selectedAddress !== this._lastEmittedState.accounts) { + const accounts = this.selectedAddress ? [this.selectedAddress] : []; + this._lastEmittedState.accounts = this.selectedAddress; + callback(accounts); + } + break; + case 'chainChanged': + if (this.chainId !== this._lastEmittedState.chainId) { + this._lastEmittedState.chainId = this.chainId; + callback(this.chainId); + } + break; + case 'connect': + if (this.isConnected() !== this._lastEmittedState.connected) { + this._lastEmittedState.connected = this.isConnected(); + callback({ chainId: this.chainId }); + } + break; + } + return () => this._events.get(eventName).delete(callback); }, + emit: function(eventName, data) { + // Only emit if state has changed + switch(eventName) { + case 'accountsChanged': + const accounts = Array.isArray(data) ? data[0] : null; + if (accounts === this._lastEmittedState.accounts) return; + this._lastEmittedState.accounts = accounts; + break; + case 'chainChanged': + if (data === this._lastEmittedState.chainId) return; + this._lastEmittedState.chainId = data; + break; + case 'connect': + const connected = !!data; + if (connected === this._lastEmittedState.connected) return; + this._lastEmittedState.connected = connected; + break; + } + + const listeners = this._events.get(eventName); + if (listeners) { + listeners.forEach(listener => { + try { + listener(data); + } catch(e) { + console.error('Event listener error:', e); + } + }); + } + }, + removeListener: function(eventName, callback) { if (this._events.has(eventName)) { this._events.get(eventName).delete(callback); @@ -135,30 +194,18 @@ export const REVOKE_PERMISSIONS_JS = ` (function() { try { if (window.ethereum) { - // Update state first + // Update state window.ethereum.selectedAddress = null; - // Then emit events once - if (window.ethereum._events && window.ethereum._events.get('accountsChanged')) { - const listeners = window.ethereum._events.get('accountsChanged'); - const accounts = []; - listeners.forEach(listener => { - try { - listener(accounts); - } catch (e) { - console.error('Listener error:', e); - } - }); - } - - // Keep chainId and chain permissions intact - window.ethereum.chainId = '${AMB_CHAIN_ID_HEX}'; + // Emit single accountsChanged event + window.ethereum.emit('accountsChanged', []); + + console.log('Permissions revoked'); } - return true; - } catch (e) { - console.error('Revoke update error:', e); - return true; + } catch(e) { + console.error('Revoke permissions error:', e); } + return true; })(); `; @@ -168,49 +215,16 @@ export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` if (window.ethereum) { // Update provider state window.ethereum.selectedAddress = '${address}'; - window.ethereum.isConnected = () => true; window.ethereum.chainId = '${chainId}'; - // Emit events in correct order - const events = window.ethereum._events || new Map(); - - // 1. Connect event - if (events.has('connect')) { - events.get('connect').forEach(listener => { - try { - listener({ chainId: '${chainId}' }); - } catch(e) { - console.error('Connect event error:', e); - } - }); - } - - // 2. Chain changed event - if (events.has('chainChanged')) { - events.get('chainChanged').forEach(listener => { - try { - listener('${chainId}'); - } catch(e) { - console.error('Chain change event error:', e); - } - }); - } - - // 3. Accounts changed event - if (events.has('accountsChanged')) { - events.get('accountsChanged').forEach(listener => { - try { - listener(['${address}']); - } catch(e) { - console.error('Account change event error:', e); - } - }); - } + // Emit events only if state has changed + window.ethereum.emit('connect', { chainId: '${chainId}' }); + window.ethereum.emit('chainChanged', '${chainId}'); + window.ethereum.emit('accountsChanged', ['${address}']); console.log('Ethereum state updated:', { address: '${address}', - chainId: '${chainId}', - isConnected: true + chainId: '${chainId}' }); } } catch(e) { diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index db49445f7..00befc8f5 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -28,7 +28,7 @@ import { import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { - // uri: 'https://airquest.xyz/' + // uri: 'https://x3na.com/' uri: 'https://metamask.github.io/test-dapp/' }; From e41a58aae8e9bfa37b1e69927d5b58a3d325775c Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:23:47 +0200 Subject: [PATCH 09/17] fix: update AMB Wallet icon in injectable provider - Replaced the existing base64 encoded icon for the AMB Wallet with a new one to ensure accurate representation in the browser feature. - Maintained the structure of the provider information while enhancing the visual aspect of the wallet integration. --- src/features/browser/lib/injectable.provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index 44fb50a55..195669eb9 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -18,7 +18,7 @@ export const INJECTED_PROVIDER_JS = ` const providerInfo = { uuid: 'c7df86fd-339b-4d51-8ad6-6a600535d86a', name: 'AMB Wallet', - icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6TSdt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', + icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', rdns: 'io.airdao.app' }; From cfcafc67e7416da0546bbdff518cc782e38ceae9 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:52:04 +0200 Subject: [PATCH 10/17] feat: enhance injectable provider and BrowserScreen with improved state management and user interactions - Updated the injected provider to rename it to 'AirDAO Wallet' and improved event handling for better state management. - Introduced a last emitted state tracking system to prevent unnecessary event emissions, enhancing performance and reliability. - Enhanced the BrowserScreen to handle Ethereum state updates and permissions more effectively, including user confirmation dialogs for transaction signing. - Implemented duplicate request handling and optimized state updates to prevent loops during address changes. - Improved error handling and logging for Ethereum-related actions, aiding in debugging and user experience. --- .../browser/lib/injectable.provider.ts | 177 ++++++++---------- src/screens/Browser/index.tsx | 160 +++++++++++----- 2 files changed, 199 insertions(+), 138 deletions(-) diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index 195669eb9..cfffeb1ff 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -1,43 +1,48 @@ +import { AMB_CHAIN_ID_DEC, AMB_CHAIN_ID_HEX } from '../constants'; + export const INJECTED_PROVIDER_JS = ` - const consoleLog = (type, log) => window.ReactNativeWebView.postMessage(JSON.stringify({'type': 'Console', 'data': {'type': type, 'log': log}})); - console = { - log: (log) => consoleLog('log', log), - debug: (log) => consoleLog('debug', log), - info: (log) => consoleLog('info', log), - warn: (log) => consoleLog('warn', log), - error: (log) => consoleLog('error', log), - }; (function() { - if (window.ethereum) return; + if (window.ethereum) return true; let isInitialized = false; let requestCounter = 0; const pendingRequests = new Map(); + let lastEmittedState = { + address: null, + chainId: null, + connected: false + }; // EIP-6963 const providerInfo = { uuid: 'c7df86fd-339b-4d51-8ad6-6a600535d86a', - name: 'AMB Wallet', + name: 'AirDAO Wallet', icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', rdns: 'io.airdao.app' }; - // Create the provider object const provider = { isMetaMask: true, + selectedAddress: null, + chainId: '${AMB_CHAIN_ID_HEX}', + networkVersion: '${AMB_CHAIN_ID_DEC}', + _events: new Map(), + isConnected: () => true, _metamask: { isUnlocked: () => true, }, - selectedAddress: null, - chainId: '0x414e', - networkVersion: '16718', - + request: function(args) { return new Promise((resolve, reject) => { const { method, params } = args; const id = requestCounter++; + // Special handling for eth_requestAccounts on page refresh + if (method === 'eth_requestAccounts' && this.selectedAddress) { + return resolve([this.selectedAddress]); + } + pendingRequests.set(id, { resolve, reject }); window.ReactNativeWebView.postMessage(JSON.stringify({ @@ -48,64 +53,55 @@ export const INJECTED_PROVIDER_JS = ` })); }); }, - - // Improved event handling system - _events: new Map(), - _lastEmittedState: { - accounts: null, - chainId: null, - connected: false - }, - + on: function(eventName, callback) { if (!this._events.has(eventName)) { this._events.set(eventName, new Set()); } this._events.get(eventName).add(callback); - - // Immediately emit current state for certain events - // but only if it's different from last emitted state - switch(eventName) { - case 'accountsChanged': - if (this.selectedAddress !== this._lastEmittedState.accounts) { - const accounts = this.selectedAddress ? [this.selectedAddress] : []; - this._lastEmittedState.accounts = this.selectedAddress; - callback(accounts); - } - break; - case 'chainChanged': - if (this.chainId !== this._lastEmittedState.chainId) { - this._lastEmittedState.chainId = this.chainId; - callback(this.chainId); - } - break; - case 'connect': - if (this.isConnected() !== this._lastEmittedState.connected) { - this._lastEmittedState.connected = this.isConnected(); - callback({ chainId: this.chainId }); - } - break; + + // Only emit initial state if it's different from last emitted + if (this.selectedAddress && this.selectedAddress !== lastEmittedState.address) { + switch(eventName) { + case 'connect': + if (!lastEmittedState.connected) { + callback({ chainId: this.chainId }); + lastEmittedState.connected = true; + } + break; + case 'accountsChanged': + if (this.selectedAddress !== lastEmittedState.address) { + callback([this.selectedAddress]); + lastEmittedState.address = this.selectedAddress; + } + break; + case 'chainChanged': + if (this.chainId !== lastEmittedState.chainId) { + callback(this.chainId); + lastEmittedState.chainId = this.chainId; + } + break; + } } return () => this._events.get(eventName).delete(callback); }, - + emit: function(eventName, data) { - // Only emit if state has changed + // Prevent duplicate emissions switch(eventName) { + case 'connect': + if (lastEmittedState.connected) return; + lastEmittedState.connected = true; + break; case 'accountsChanged': - const accounts = Array.isArray(data) ? data[0] : null; - if (accounts === this._lastEmittedState.accounts) return; - this._lastEmittedState.accounts = accounts; + const newAddress = Array.isArray(data) ? data[0] : null; + if (newAddress === lastEmittedState.address) return; + lastEmittedState.address = newAddress; break; case 'chainChanged': - if (data === this._lastEmittedState.chainId) return; - this._lastEmittedState.chainId = data; - break; - case 'connect': - const connected = !!data; - if (connected === this._lastEmittedState.connected) return; - this._lastEmittedState.connected = connected; + if (data === lastEmittedState.chainId) return; + lastEmittedState.chainId = data; break; } @@ -120,13 +116,13 @@ export const INJECTED_PROVIDER_JS = ` }); } }, - + removeListener: function(eventName, callback) { if (this._events.has(eventName)) { this._events.get(eventName).delete(callback); } }, - + enable: function() { return this.request({ method: 'eth_requestAccounts' }); }, @@ -174,61 +170,54 @@ export const INJECTED_PROVIDER_JS = ` window.ethereum = provider; + // Initialize EIP-6963 event listeners + window.addEventListener('eip6963:requestProvider', function() { + provider.announceProvider(); + }); + if (!isInitialized) { isInitialized = true; - - // Announce provider immediately provider.announceProvider(); - - // Listen for provider requests - window.addEventListener('eip6963:requestProvider', function() { - provider.announceProvider(); - }); - - // Dispatch ethereum#initialized for backward compatibility window.dispatchEvent(new Event('ethereum#initialized')); } + + return true; })(); - `; -export const REVOKE_PERMISSIONS_JS = ` +`; + +export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` (function() { try { - if (window.ethereum) { - // Update state - window.ethereum.selectedAddress = null; - - // Emit single accountsChanged event - window.ethereum.emit('accountsChanged', []); + if (window.ethereum && window.ethereum.selectedAddress !== '${address}') { + window.ethereum.selectedAddress = '${address}'; + window.ethereum.chainId = '${chainId}'; - console.log('Permissions revoked'); + // Only emit if state actually changed + window.ethereum.emit('connect', { chainId: '${chainId}' }); + window.ethereum.emit('chainChanged', '${chainId}'); + window.ethereum.emit('accountsChanged', ['${address}']); } } catch(e) { - console.error('Revoke permissions error:', e); + console.error('State update error:', e); } return true; })(); `; -export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` +export const REVOKE_PERMISSIONS_JS = ` (function() { try { if (window.ethereum) { - // Update provider state - window.ethereum.selectedAddress = '${address}'; - window.ethereum.chainId = '${chainId}'; + const previousAddress = window.ethereum.selectedAddress; + window.ethereum.selectedAddress = null; - // Emit events only if state has changed - window.ethereum.emit('connect', { chainId: '${chainId}' }); - window.ethereum.emit('chainChanged', '${chainId}'); - window.ethereum.emit('accountsChanged', ['${address}']); - - console.log('Ethereum state updated:', { - address: '${address}', - chainId: '${chainId}' - }); + // Only emit if there was a previous address + if (previousAddress) { + window.ethereum.emit('accountsChanged', []); + } } } catch(e) { - console.error('State update error:', e); + console.error('Revoke permissions error:', e); } return true; })(); diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 00befc8f5..cc33502e0 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -87,6 +87,7 @@ export const BrowserScreen = () => { const request: JsonRpcRequest = JSON.parse(event.nativeEvent.data); const { id, method, params } = request; + // Skip duplicate requests if (requestsInProgress.current.has(id)) { return; } @@ -112,13 +113,23 @@ export const BrowserScreen = () => { if (!address) { throw new Error('No account available'); } - response.result = [address]; - setConnectedAddress(address); - webViewRef.current?.injectJavaScript( - UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) - ); + // Only update if not already connected to this address + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + response.result = [address]; + setConnectedAddress(address); + // Delay the state update to prevent loops + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + } + }, 100); + } else { + response.result = [address]; + } break; } @@ -134,6 +145,19 @@ export const BrowserScreen = () => { if (!address) { throw new Error('No account available'); } + + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + setConnectedAddress(address); + + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + } + }, 100); + } + response.result = [ { parentCapability: 'eth_accounts', @@ -156,14 +180,6 @@ export const BrowserScreen = () => { ] } ]; - - if (webViewRef.current) { - webViewRef.current.injectJavaScript( - UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) - ); - } - - setConnectedAddress(address); } else { response.error = { code: 4200, @@ -178,36 +194,26 @@ export const BrowserScreen = () => { if (permissions?.eth_accounts) { if (connectedAddress) { setConnectedAddress(null); - response.result = [ - { - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: [AMB_CHAIN_ID_HEX] - } - ] - } - ]; - if (webViewRef.current) { - webViewRef.current.injectJavaScript(REVOKE_PERMISSIONS_JS); - } - } else { - response.result = [ - { - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: [AMB_CHAIN_ID_HEX] - } - ] + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript(REVOKE_PERMISSIONS_JS); } - ]; + }, 100); } + + response.result = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; } else if (permissions?.['endowment:permitted-chains']) { response.error = { code: 4200, @@ -260,13 +266,79 @@ export const BrowserScreen = () => { response.result = await handleChainIdRequest(); break; - case 'eth_sendTransaction': - response.result = await handleSendTransaction(params[0]); + case 'eth_sendTransaction': { + try { + const txParams = params[0]; + + // Show confirmation alert + await new Promise((resolve, reject) => { + Alert.alert( + 'Confirm Transaction', + `Do you want to send this transaction?\n\n` + + `From: ${txParams.from}\n` + + `To: ${txParams.to}\n` + + `Value: ${txParams.value || '0'} Wei\n` + + `Data: ${txParams.data || 'None'}`, + [ + { + text: 'Cancel', + onPress: () => + reject(new Error('User rejected transaction')), + style: 'cancel' + }, + { + text: 'Send', + onPress: () => resolve(true), + style: 'default' + } + ], + { cancelable: false } + ); + }); + + response.result = await handleSendTransaction(txParams); + } catch (error) { + console.error('Transaction error:', error); + throw error; + } break; + } + + case 'eth_signTransaction': { + try { + const txParams = params[0]; - case 'eth_signTransaction': - response.result = await handleSignTransaction(params[0]); + await new Promise((resolve, reject) => { + Alert.alert( + 'Sign Transaction', + `Do you want to sign this transaction?\n\n` + + `From: ${txParams.from}\n` + + `To: ${txParams.to}\n` + + `Value: ${txParams.value || '0'} Wei\n` + + `Data: ${txParams.data || 'None'}`, + [ + { + text: 'Cancel', + onPress: () => reject(new Error('User rejected signing')), + style: 'cancel' + }, + { + text: 'Sign', + onPress: () => resolve(true), + style: 'default' + } + ], + { cancelable: false } + ); + }); + + response.result = await handleSignTransaction(txParams); + } catch (error) { + console.error('Sign transaction error:', error); + throw error; + } break; + } case 'personal_sign': { try { From 406bef66d0c2642ad97225f554d1d85910f15608 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:20:56 +0200 Subject: [PATCH 11/17] feat: enhance injectable provider with dynamic UUID and EIP-6963 integration - Updated the injectable provider to generate a dynamic UUID for the provider info, improving uniqueness and security. - Integrated EIP-6963 provider information, allowing for better compatibility with Ethereum standards. - Refactored the revoke permissions and update Ethereum state functions to streamline state management and event emissions. - Improved error handling and logging for better debugging and user experience during Ethereum interactions. --- src/features/browser/lib/eip6963.ts | 9 +++ .../browser/lib/injectable.provider.ts | 61 ++++++++++--------- 2 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 src/features/browser/lib/eip6963.ts diff --git a/src/features/browser/lib/eip6963.ts b/src/features/browser/lib/eip6963.ts new file mode 100644 index 000000000..e7c6d4739 --- /dev/null +++ b/src/features/browser/lib/eip6963.ts @@ -0,0 +1,9 @@ +import Constants from 'expo-constants'; + +const appSlug = Constants.expoConfig?.name; + +export const EIP6963_PROVIDER_INFO = { + name: `${appSlug} Wallet`, + icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', + rdns: Constants.expoConfig?.ios?.bundleIdentifier +}; diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index cfffeb1ff..db9ee1382 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -1,4 +1,8 @@ +import { randomUUID } from 'expo-crypto'; import { AMB_CHAIN_ID_DEC, AMB_CHAIN_ID_HEX } from '../constants'; +import { EIP6963_PROVIDER_INFO } from './eip6963'; + +const uuid = randomUUID; export const INJECTED_PROVIDER_JS = ` (function() { @@ -14,13 +18,14 @@ export const INJECTED_PROVIDER_JS = ` }; // EIP-6963 - const providerInfo = { - uuid: 'c7df86fd-339b-4d51-8ad6-6a600535d86a', - name: 'AirDAO Wallet', - icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', - rdns: 'io.airdao.app' + const eip6963ProviderInfo = { + uuid: '${uuid()}', + name: '${EIP6963_PROVIDER_INFO.name}', + icon: '${EIP6963_PROVIDER_INFO.icon}', + rdns: '${EIP6963_PROVIDER_INFO.rdns}' }; + // Create the provider object const provider = { isMetaMask: true, selectedAddress: null, @@ -127,7 +132,7 @@ export const INJECTED_PROVIDER_JS = ` return this.request({ method: 'eth_requestAccounts' }); }, - info: providerInfo, + info: eip6963ProviderInfo, announceProvider: function() { window.dispatchEvent( @@ -170,14 +175,12 @@ export const INJECTED_PROVIDER_JS = ` window.ethereum = provider; - // Initialize EIP-6963 event listeners - window.addEventListener('eip6963:requestProvider', function() { - provider.announceProvider(); - }); - if (!isInitialized) { isInitialized = true; provider.announceProvider(); + window.addEventListener('eip6963:requestProvider', function() { + provider.announceProvider(); + }); window.dispatchEvent(new Event('ethereum#initialized')); } @@ -185,39 +188,39 @@ export const INJECTED_PROVIDER_JS = ` })(); `; -export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` +export const REVOKE_PERMISSIONS_JS = ` (function() { try { - if (window.ethereum && window.ethereum.selectedAddress !== '${address}') { - window.ethereum.selectedAddress = '${address}'; - window.ethereum.chainId = '${chainId}'; - - // Only emit if state actually changed - window.ethereum.emit('connect', { chainId: '${chainId}' }); - window.ethereum.emit('chainChanged', '${chainId}'); - window.ethereum.emit('accountsChanged', ['${address}']); + if (window.ethereum) { + window.ethereum.selectedAddress = null; + window.ethereum.emit('accountsChanged', []); + console.log('Permissions revoked'); } } catch(e) { - console.error('State update error:', e); + console.error('Revoke permissions error:', e); } return true; })(); `; -export const REVOKE_PERMISSIONS_JS = ` +export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` (function() { try { if (window.ethereum) { - const previousAddress = window.ethereum.selectedAddress; - window.ethereum.selectedAddress = null; + window.ethereum.selectedAddress = '${address}'; + window.ethereum.chainId = '${chainId}'; - // Only emit if there was a previous address - if (previousAddress) { - window.ethereum.emit('accountsChanged', []); - } + window.ethereum.emit('connect', { chainId: '${chainId}' }); + window.ethereum.emit('chainChanged', '${chainId}'); + window.ethereum.emit('accountsChanged', ['${address}']); + + console.log('Ethereum state updated:', { + address: '${address}', + chainId: '${chainId}' + }); } } catch(e) { - console.error('Revoke permissions error:', e); + console.error('State update error:', e); } return true; })(); From fef3387d804e1255852fb8d497829d6bd8c2a42b Mon Sep 17 00:00:00 2001 From: EvgeniyJB <81742350+EvgeniyJB@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:14:51 +0100 Subject: [PATCH 12/17] refactor: refactor browser logic --- src/entities/browser/model/browser-store.ts | 9 + src/entities/browser/model/index.ts | 1 + src/entities/harbor/model/types.ts | 5 + src/features/browser/lib/index.ts | 1 + .../browser/lib/injectable.provider.ts | 20 +- src/features/browser/lib/middlewareHelpers.ts | 316 ++++++++++ src/features/browser/lib/rpc-methods.ts | 80 +++ .../browser/lib/rpc-middleware-methods.ts | 93 --- src/features/browser/lib/rpc-middleware.ts | 188 ++++++ src/features/browser/utils/index.ts | 2 + .../browser/utils/rpc-error-handler.ts | 7 + .../browser/utils/showConfirmation.ts | 35 ++ src/features/products/entities/_products.tsx | 2 +- src/screens/Browser/index.tsx | 564 +----------------- 14 files changed, 668 insertions(+), 655 deletions(-) create mode 100644 src/entities/browser/model/browser-store.ts create mode 100644 src/entities/browser/model/index.ts create mode 100644 src/features/browser/lib/middlewareHelpers.ts create mode 100644 src/features/browser/lib/rpc-methods.ts delete mode 100644 src/features/browser/lib/rpc-middleware-methods.ts create mode 100644 src/features/browser/lib/rpc-middleware.ts create mode 100644 src/features/browser/utils/index.ts create mode 100644 src/features/browser/utils/rpc-error-handler.ts create mode 100644 src/features/browser/utils/showConfirmation.ts diff --git a/src/entities/browser/model/browser-store.ts b/src/entities/browser/model/browser-store.ts new file mode 100644 index 000000000..ef5ac0dba --- /dev/null +++ b/src/entities/browser/model/browser-store.ts @@ -0,0 +1,9 @@ +import { create } from 'zustand'; +import { BrowserStoreModel } from '@entities/harbor/model/types'; + +export const useBrowserStore = create((set) => ({ + connectedAddress: '', + setConnectedAddress: async (address: string) => { + set({ connectedAddress: address }); + } +})); diff --git a/src/entities/browser/model/index.ts b/src/entities/browser/model/index.ts new file mode 100644 index 000000000..8f3cc25e2 --- /dev/null +++ b/src/entities/browser/model/index.ts @@ -0,0 +1 @@ +export * from './browser-store'; diff --git a/src/entities/harbor/model/types.ts b/src/entities/harbor/model/types.ts index a24f59fd6..50e530ee4 100644 --- a/src/entities/harbor/model/types.ts +++ b/src/entities/harbor/model/types.ts @@ -55,3 +55,8 @@ export interface HarborStoreModel { loading: boolean; updateAll: (payload: string) => void; } + +export interface BrowserStoreModel { + connectedAddress: string; + setConnectedAddress: (address: string) => void; +} diff --git a/src/features/browser/lib/index.ts b/src/features/browser/lib/index.ts index 93535b572..9d4f521c1 100644 --- a/src/features/browser/lib/index.ts +++ b/src/features/browser/lib/index.ts @@ -1 +1,2 @@ export * from './injectable.provider'; +export * from './rpc-middleware'; diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index db9ee1382..8b5348f42 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -7,7 +7,7 @@ const uuid = randomUUID; export const INJECTED_PROVIDER_JS = ` (function() { if (window.ethereum) return true; - + let isInitialized = false; let requestCounter = 0; const pendingRequests = new Map(); @@ -21,7 +21,7 @@ export const INJECTED_PROVIDER_JS = ` const eip6963ProviderInfo = { uuid: '${uuid()}', name: '${EIP6963_PROVIDER_INFO.name}', - icon: '${EIP6963_PROVIDER_INFO.icon}', + icon: '${EIP6963_PROVIDER_INFO.icon}', rdns: '${EIP6963_PROVIDER_INFO.rdns}' }; @@ -42,14 +42,14 @@ export const INJECTED_PROVIDER_JS = ` return new Promise((resolve, reject) => { const { method, params } = args; const id = requestCounter++; - + // Special handling for eth_requestAccounts on page refresh if (method === 'eth_requestAccounts' && this.selectedAddress) { return resolve([this.selectedAddress]); } - + pendingRequests.set(id, { resolve, reject }); - + window.ReactNativeWebView.postMessage(JSON.stringify({ id, jsonrpc: '2.0', @@ -88,7 +88,7 @@ export const INJECTED_PROVIDER_JS = ` break; } } - + return () => this._events.get(eventName).delete(callback); }, @@ -133,7 +133,7 @@ export const INJECTED_PROVIDER_JS = ` }, info: eip6963ProviderInfo, - + announceProvider: function() { window.dispatchEvent( new CustomEvent('eip6963:announceProvider', { @@ -150,11 +150,11 @@ export const INJECTED_PROVIDER_JS = ` try { const response = JSON.parse(event.data); const { id, result, error } = response; - + const pendingRequest = pendingRequests.get(id); if (pendingRequest) { pendingRequests.delete(id); - + if (error) { pendingRequest.reject(new Error(error.message)); } else { @@ -209,7 +209,7 @@ export const UPDATE_ETHEREUM_STATE_JS = (address: string, chainId: string) => ` if (window.ethereum) { window.ethereum.selectedAddress = '${address}'; window.ethereum.chainId = '${chainId}'; - + window.ethereum.emit('connect', { chainId: '${chainId}' }); window.ethereum.emit('chainChanged', '${chainId}'); window.ethereum.emit('accountsChanged', ['${address}']); diff --git a/src/features/browser/lib/middlewareHelpers.ts b/src/features/browser/lib/middlewareHelpers.ts new file mode 100644 index 000000000..d778b189c --- /dev/null +++ b/src/features/browser/lib/middlewareHelpers.ts @@ -0,0 +1,316 @@ +/* eslint-disable no-console */ +// tslint:disable:no-console +import { useBrowserStore } from '@entities/browser/model'; +import { AMB_CHAIN_ID_HEX } from '@features/browser/constants'; +import { + REVOKE_PERMISSIONS_JS, + UPDATE_ETHEREUM_STATE_JS +} from '@features/browser/lib'; +import { rpcErrorHandler, showConfirmation } from '@features/browser/utils'; +import { rpcMethods } from './rpc-methods'; + +const { connectedAddress, setConnectedAddress } = useBrowserStore.getState(); + +const { + getCurrentAddress, + handleSendTransaction, + handleSignTransaction, + signMessage, + _signTypedData +} = rpcMethods; + +export const ethRequestAccounts = async ({ + response, + privateKey, + webViewRef +}: any) => { + console.log('eth_requestAccounts called'); + const address = await getCurrentAddress(privateKey); + if (!address) { + throw new Error('No account available'); + } + try { + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + response.result = [address]; + setConnectedAddress(address); + + // Delay the state update to prevent loops + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + } + }, 100); + } else { + response.result = [address]; + } + } catch (e: unknown) { + rpcErrorHandler('eth_requestAccounts', e); + } +}; + +export const walletRequestPermissions = async ({ + permissions, + response, + privateKey, + webViewRef +}: any) => { + try { + if (permissions?.eth_accounts) { + const address = await getCurrentAddress(privateKey); + if (!address) { + throw new Error('No account available'); + } + + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + setConnectedAddress(address); + + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript( + UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) + ); + } + }, 100); + } + + response.result = [ + { + parentCapability: 'eth_accounts', + date: Date.now(), + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [address] + } + ] + }, + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; + } else { + response.error = { + code: 4200, + message: 'Requested permission is not available' + }; + } + } catch (e) { + rpcErrorHandler('walletRequestPermissions', e); + } +}; + +export const walletRevokePermissions = async ({ + permissions, + response, + webViewRef +}: any) => { + try { + if (permissions?.eth_accounts) { + if (connectedAddress) { + setConnectedAddress(''); + + setTimeout(() => { + if (webViewRef.current) { + webViewRef.current.injectJavaScript(REVOKE_PERMISSIONS_JS); + } + }, 100); + } + + response.result = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; + } else if (permissions?.['endowment:permitted-chains']) { + response.error = { + code: 4200, + message: 'Chain permissions cannot be revoked' + }; + } else { + response.error = { + code: 4200, + message: 'Permission to revoke is not recognized' + }; + } + } catch (e) { + rpcErrorHandler('walletRevokePermissions', e); + } +}; + +export const walletGetPermissions = async ({ response }: any) => { + try { + const basePermissions = [ + { + parentCapability: 'endowment:permitted-chains', + date: Date.now(), + caveats: [ + { + type: 'restrictChains', + value: [AMB_CHAIN_ID_HEX] + } + ] + } + ]; + + if (connectedAddress) { + response.result = [ + { + parentCapability: 'eth_accounts', + date: Date.now(), + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [connectedAddress] + } + ] + }, + ...basePermissions + ]; + } else { + response.result = basePermissions; + } + } catch (e) { + rpcErrorHandler('walletGetPermissions', e); + } +}; + +export const ethSendTransaction = async ({ + params, + response, + privateKey +}: any) => { + try { + const txParams = params[0]; + await new Promise((resolve, reject) => { + showConfirmation({ + header: 'Confirm Transaction', + message: + `Do you want to send this transaction? From: ${txParams.from}\n` + + `To: ${txParams.to}\n` + + `Value: ${txParams.value || '0'} Wei\n` + + `Data: ${txParams.data || 'None'}`, + resolve: () => resolve(true), + reject: () => reject(new Error('User rejected transaction')) + }); + }); + + response.result = await handleSendTransaction(txParams, privateKey); + } catch (error) { + rpcErrorHandler('eth_sendTransaction', error); + throw error; + } +}; + +export const ethSingTransaction = async ({ + params, + response, + privateKey +}: any) => { + try { + const txParams = params[0]; + + await new Promise((resolve, reject) => { + showConfirmation({ + header: 'Sign Transaction', + message: + `Do you want to send this transaction? From: ${txParams.from}\n` + + `To: ${txParams.to}\n` + + `Value: ${txParams.value || '0'} Wei\n` + + `Data: ${txParams.data || 'None'}`, + resolve: () => resolve(true), + reject: () => reject(new Error('User rejected signing')) + }); + }); + + response.result = await handleSignTransaction(txParams, privateKey); + } catch (error) { + rpcErrorHandler('eth_sendTransaction', error); + throw error; + } +}; + +export const personalSing = async ({ params, response, privateKey }: any) => { + try { + console.log('Personal sign request:', params); + const [message, address] = params; + + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + throw new Error('Address mismatch'); + } + + const userConfirmation = new Promise((resolve, reject) => { + showConfirmation({ + header: 'Sign Message', + message: 'Do you want to sign this message?', + resolve: () => resolve(true), + reject: () => reject(new Error('User rejected signing')) + }); + }); + + await userConfirmation; + + const signature = await signMessage(message, privateKey); + + console.log('Signature generated:', signature); + response.result = signature; + } catch (error) { + console.error('Personal sign error:', error); + throw error; + } +}; + +export const ethSingTypesData = async ({ + params, + response, + privateKey +}: any) => { + try { + console.log('Sign typed data request:', params); + const [address, typedData] = params; + + if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { + throw new Error('Address mismatch'); + } + + const data = + typeof typedData === 'string' ? JSON.parse(typedData) : typedData; + + const userConfirmation = new Promise((resolve, reject) => { + showConfirmation({ + header: 'Sign Typed Data', + message: `Do you want to sign this data?\n\nFrom: ${address}\n\nData: ${JSON.stringify( + data, + null, + 2 + )}`, + resolve: () => resolve(true), + reject: () => reject(new Error('User rejected signing')) + }); + }); + await userConfirmation; + + const signature = await _signTypedData(data, privateKey); + + console.log('Signature generated:', signature); + response.result = signature; + } catch (error) { + console.error('Failed to sign typed data:', error); + throw error; + } +}; diff --git a/src/features/browser/lib/rpc-methods.ts b/src/features/browser/lib/rpc-methods.ts new file mode 100644 index 000000000..960699ef6 --- /dev/null +++ b/src/features/browser/lib/rpc-methods.ts @@ -0,0 +1,80 @@ +/* eslint-disable no-console */ +// tslint:disable:no-console +import { RefObject } from 'react'; +import { WebView } from '@metamask/react-native-webview'; +import { JsonRpcResponse } from '@walletconnect/jsonrpc-types'; +import { ethers } from 'ethers'; +import Config from '@constants/config'; +import { AMB_CHAIN_ID_DEC } from '@features/browser/constants'; +import { rpcErrorHandler } from '@features/browser/utils'; +import { createAMBProvider } from '@features/swap/utils/contracts/instances'; + +const extractWallet = async (privateKey: string) => { + return new ethers.Wallet(privateKey, createAMBProvider()); +}; + +const getCurrentAddress = async (privateKey: string) => { + try { + const wallet = await extractWallet(privateKey); + return await wallet.getAddress(); + } catch (error) { + rpcErrorHandler('getCurrentAddress', error); + return null; + } +}; + +const handleChainIdRequest = async () => { + return `0x${Number(AMB_CHAIN_ID_DEC).toString(16)}`; +}; + +const handleSendTransaction = async (txParams: any, privateKey: string) => { + const wallet = await extractWallet(privateKey); + const tx = await wallet.sendTransaction(txParams); + return tx.hash; +}; + +const handleSignTransaction = async (txParams: any, privateKey: string) => { + const wallet = await extractWallet(privateKey); + return await wallet.signTransaction(txParams); +}; + +const handleGetBalance = async (address: string, blockTag = 'latest') => { + const provider = new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); + const balance = await provider.getBalance(address, blockTag); + return balance.toHexString(); +}; + +const sendResponse = ( + response: JsonRpcResponse, + webViewRef: RefObject +) => { + if (webViewRef?.current) { + const messageString = JSON.stringify(response); + console.log('Sending response:', response); + webViewRef.current.postMessage(messageString); + } +}; + +const signMessage = async (message: any, privateKey: string) => { + const wallet = await extractWallet(privateKey); + return await wallet.signMessage( + ethers.utils.isHexString(message) ? ethers.utils.arrayify(message) : message + ); +}; + +const _signTypedData = async (data: any, privateKey: string) => { + const wallet = await extractWallet(privateKey); + + return await wallet._signTypedData(data.domain, data.types, data.message); +}; + +export const rpcMethods = { + getCurrentAddress, + handleChainIdRequest, + handleSendTransaction, + handleSignTransaction, + handleGetBalance, + signMessage, + _signTypedData, + sendResponse +}; diff --git a/src/features/browser/lib/rpc-middleware-methods.ts b/src/features/browser/lib/rpc-middleware-methods.ts deleted file mode 100644 index 6b832598f..000000000 --- a/src/features/browser/lib/rpc-middleware-methods.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { ethers } from 'ethers'; -import { createAMBProvider } from '@features/swap/utils/contracts/instances'; - -export class RpcMiddlewareMethods { - private wallet: ethers.Wallet | null = null; - private initPromise: Promise | null = null; - - constructor(private privateKey: string) { - const provider = createAMBProvider(); - this.wallet = new ethers.Wallet(privateKey, provider); - } - - /** - * Ensures the wallet is initialized before executing any method - */ - private async ensureWallet(): Promise { - if (this.initPromise) { - await this.initPromise; - this.initPromise = null; - } - - if (!this.wallet) { - throw new Error('Wallet not initialized'); - } - - return this.wallet; - } - - /** - * Gets the current wallet address - */ - async getAddress(): Promise { - const wallet = await this.ensureWallet(); - return wallet.address; - } - - /** - * Signs a transaction - */ - async signTransaction( - transaction: ethers.providers.TransactionRequest - ): Promise { - const wallet = await this.ensureWallet(); - return wallet.signTransaction(transaction); - } - - /** - * Signs a message - */ - async signMessage(message: string): Promise { - const wallet = await this.ensureWallet(); - return wallet.signMessage(message); - } - - /** - * Sends a transaction - */ - async sendTransaction( - transaction: ethers.providers.TransactionRequest - ): Promise { - const wallet = await this.ensureWallet(); - return wallet.sendTransaction(transaction); - } - - /** - * Gets the balance for an address - */ - async getBalance(address: string, blockTag = 'latest'): Promise { - const wallet = await this.ensureWallet(); - const balance = await wallet.provider.getBalance(address, blockTag); - return balance.toHexString(); - } - - /** - * Gets the chain ID - */ - async getChainId(): Promise { - const wallet = await this.ensureWallet(); - const network = await wallet.provider.getNetwork(); - return `0x${network.chainId.toString(16)}`; - } -} - -// Example usage: -/* -const rpcMethods = new RpcMiddlewareMethods(async () => { - // Your logic to get private key - return privateKey; -}); - -// Methods will automatically ensure wallet is initialized -const address = await rpcMethods.getAddress(); -*/ diff --git a/src/features/browser/lib/rpc-middleware.ts b/src/features/browser/lib/rpc-middleware.ts new file mode 100644 index 000000000..4f63fe39d --- /dev/null +++ b/src/features/browser/lib/rpc-middleware.ts @@ -0,0 +1,188 @@ +/* eslint-disable no-console */ +// tslint:disable:no-console +import { RefObject } from 'react'; +import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; +import { useBrowserStore } from '@entities/browser/model'; +import { AMB_CHAIN_ID_DEC } from '@features/browser/constants'; +import { rpcMethods } from '@features/browser/lib/rpc-methods'; +import { rpcErrorHandler } from '@features/browser/utils'; +import { + ethRequestAccounts, + ethSendTransaction, + ethSingTransaction, + ethSingTypesData, + personalSing, + walletGetPermissions, + walletRequestPermissions, + walletRevokePermissions +} from './middlewareHelpers'; + +interface JsonRpcRequest { + id: number; + jsonrpc: string; + method: string; + params: any[]; +} + +interface JsonRpcResponse { + id: number; + jsonrpc: string; + result?: any; + error?: { + code: number; + message: string; + }; +} + +interface HandleWebViewMessageModel { + event: WebViewMessageEvent; + webViewRef: RefObject; + privateKey: string; +} + +export async function handleWebViewMessage({ + event, + webViewRef, + privateKey +}: HandleWebViewMessageModel) { + const requestsInProgress = new Set(); + const { connectedAddress } = useBrowserStore.getState(); + + const { handleChainIdRequest, handleGetBalance, sendResponse } = rpcMethods; + + const logger = (event: WebViewMessageEvent) => { + try { + const data = JSON.parse(event.nativeEvent.data); + console.log('WebView message:', { + method: data.method, + params: data.params, + id: data.id + }); + } catch (e) { + console.log('Raw WebView message:', event.nativeEvent.data); + } + }; + + try { + logger(event); + const request: JsonRpcRequest = JSON.parse(event.nativeEvent.data); + const { id, method, params } = request; + console.log('method->>', method); + + // Skip duplicate requests + if (requestsInProgress.has(id)) { + return; + } + requestsInProgress.add(id); + + // console.log('Incoming request:', { id, method, params }); + + const response: JsonRpcResponse = { + id, + jsonrpc: '2.0', + // @ts-ignore + method + }; + + try { + switch (method) { + case 'net_version': + response.result = AMB_CHAIN_ID_DEC; + break; + + case 'eth_requestAccounts': + await ethRequestAccounts({ response, privateKey, webViewRef }); + break; + + case 'eth_accounts': { + response.result = connectedAddress ? [connectedAddress] : []; + break; + } + + case 'wallet_requestPermissions': { + const permissions = params[0]; + await walletRequestPermissions({ + permissions, + response, + privateKey, + webViewRef + }); + break; + } + + case 'wallet_revokePermissions': { + const permissions = params[0]; + await walletRevokePermissions({ permissions, response, webViewRef }); + break; + } + + case 'wallet_getPermissions': { + await walletGetPermissions({ response }); + break; + } + + case 'eth_chainId': + response.result = await handleChainIdRequest(); + break; + + case 'eth_sendTransaction': { + await ethSendTransaction({ + params, + response, + privateKey + }); + break; + } + + case 'eth_signTransaction': { + await ethSingTransaction({ + params, + response, + privateKey + }); + break; + } + + case 'personal_sign': + case 'eth_sign': + await personalSing({ params, response, privateKey }); + break; + + case 'eth_getBalance': + response.result = await handleGetBalance(params[0], params[1]); + break; + + case 'eth_signTypedData_v4': + case 'eth_signTypedData': + await ethSingTypesData({ + params, + response, + privateKey + }); + break; + default: + response.error = { + code: 4200, + message: `Method ${method} not supported` + }; + } + // @ts-ignore + sendResponse(response, webViewRef); + } finally { + requestsInProgress.delete(id); + } + } catch (error) { + rpcErrorHandler('handleWebViewMessage', error); + sendResponse( + { + id: -1, + jsonrpc: '2.0', + error: { + code: 4001, + message: (error as Error).message || 'Unknown error occurred' + } + }, + webViewRef + ); + } +} diff --git a/src/features/browser/utils/index.ts b/src/features/browser/utils/index.ts new file mode 100644 index 000000000..fa23d1aa4 --- /dev/null +++ b/src/features/browser/utils/index.ts @@ -0,0 +1,2 @@ +export * from './showConfirmation'; +export * from './rpc-error-handler'; diff --git a/src/features/browser/utils/rpc-error-handler.ts b/src/features/browser/utils/rpc-error-handler.ts new file mode 100644 index 000000000..739a32e58 --- /dev/null +++ b/src/features/browser/utils/rpc-error-handler.ts @@ -0,0 +1,7 @@ +/* eslint-disable no-console */ +// tslint:disable:no-console + +export const rpcErrorHandler = (point: unknown, err: unknown) => { + const error = typeof err === 'string' ? err : JSON.stringify(err); + console.log(`${point} ERROR`, error); +}; diff --git a/src/features/browser/utils/showConfirmation.ts b/src/features/browser/utils/showConfirmation.ts new file mode 100644 index 000000000..9bb2e76c6 --- /dev/null +++ b/src/features/browser/utils/showConfirmation.ts @@ -0,0 +1,35 @@ +import { Alert } from 'react-native'; + +interface ShowConfirmationModel { + header?: string; + message?: string; + reject: () => void; + resolve: () => void; + cancelable?: boolean; +} + +export const showConfirmation = async ({ + header = 'Confirmation', + message = '', + reject, + resolve, + cancelable = false +}: ShowConfirmationModel) => { + return Alert.alert( + header, + message, + [ + { + text: 'Cancel', + onPress: reject, + style: 'cancel' + }, + { + text: 'Sign', + onPress: resolve, + style: 'default' + } + ], + { cancelable } + ); +}; diff --git a/src/features/products/entities/_products.tsx b/src/features/products/entities/_products.tsx index 43431eee0..c325c2069 100644 --- a/src/features/products/entities/_products.tsx +++ b/src/features/products/entities/_products.tsx @@ -14,7 +14,7 @@ export const PRODUCTS = (t: TFunction): SectionizedProducts[] => { title: t('products.title.trade'), data: [ { - id: 0, + id: 5, name: 'Browser', description: t('products.swap.description'), icon: ( diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index cc33502e0..91493d171 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -1,69 +1,29 @@ -// @ts-nocheck -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-console */ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react'; -import { StyleProp, ViewStyle, Alert } from 'react-native'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; -import { ethers } from 'ethers'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Text } from '@components/base'; -import Config from '@constants/config'; +import { useBrowserStore } from '@entities/browser/model'; import { useWalletPrivateKey } from '@entities/wallet'; +import { AMB_CHAIN_ID_HEX } from '@features/browser/constants'; import { - AMB_CHAIN_ID_DEC, - AMB_CHAIN_ID_HEX -} from '@features/browser/constants'; -import { - INJECTED_PROVIDER_JS, - REVOKE_PERMISSIONS_JS, - UPDATE_ETHEREUM_STATE_JS + handleWebViewMessage, + INJECTED_PROVIDER_JS } from '@features/browser/lib'; -import { createAMBProvider } from '@features/swap/utils/contracts/instances'; const SOURCE = { // uri: 'https://x3na.com/' uri: 'https://metamask.github.io/test-dapp/' }; -interface JsonRpcRequest { - id: number; - jsonrpc: string; - method: string; - params: any[]; -} - -interface JsonRpcResponse { - id: number; - jsonrpc: string; - result?: any; - error?: { - code: number; - message: string; - }; -} - export const BrowserScreen = () => { - const { _extractPrivateKey } = useWalletPrivateKey(); const webViewRef = useRef(null); - const [connectedAddress, setConnectedAddress] = useState(null); - const requestsInProgress = useRef(new Set()); - - const extractWallet = useCallback(async () => { - const privateKey = await _extractPrivateKey(); - const wallet = new ethers.Wallet(privateKey, createAMBProvider()); - return wallet; - }, [_extractPrivateKey]); + const { connectedAddress } = useBrowserStore(); + const { _extractPrivateKey } = useWalletPrivateKey(); const reload = () => webViewRef.current?.reload(); const back = () => webViewRef.current?.goBack(); - const containerStyle = useMemo>( () => ({ flex: 1 @@ -71,497 +31,6 @@ export const BrowserScreen = () => { [] ); - const getCurrentAddress = useCallback(async () => { - try { - const wallet = await extractWallet(); - return await wallet.getAddress(); - } catch (error) { - console.error('Error getting address:', error); - return null; - } - }, [extractWallet]); - - const handleWebViewMessage = async (event: WebViewMessageEvent) => { - try { - logger(event); - const request: JsonRpcRequest = JSON.parse(event.nativeEvent.data); - const { id, method, params } = request; - - // Skip duplicate requests - if (requestsInProgress.current.has(id)) { - return; - } - requestsInProgress.current.add(id); - - console.log('Incoming request:', { id, method, params }); - - const response: JsonRpcResponse = { - id, - jsonrpc: '2.0', - method - }; - - try { - switch (method) { - case 'net_version': - response.result = AMB_CHAIN_ID_DEC; - break; - - case 'eth_requestAccounts': { - console.log('eth_requestAccounts called'); - const address = await getCurrentAddress(); - if (!address) { - throw new Error('No account available'); - } - - // Only update if not already connected to this address - if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { - response.result = [address]; - setConnectedAddress(address); - - // Delay the state update to prevent loops - setTimeout(() => { - if (webViewRef.current) { - webViewRef.current.injectJavaScript( - UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) - ); - } - }, 100); - } else { - response.result = [address]; - } - break; - } - - case 'eth_accounts': { - response.result = connectedAddress ? [connectedAddress] : []; - break; - } - - case 'wallet_requestPermissions': { - const permissions = params[0]; - if (permissions?.eth_accounts) { - const address = await getCurrentAddress(); - if (!address) { - throw new Error('No account available'); - } - - if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { - setConnectedAddress(address); - - setTimeout(() => { - if (webViewRef.current) { - webViewRef.current.injectJavaScript( - UPDATE_ETHEREUM_STATE_JS(address, AMB_CHAIN_ID_HEX) - ); - } - }, 100); - } - - response.result = [ - { - parentCapability: 'eth_accounts', - date: Date.now(), - caveats: [ - { - type: 'restrictReturnedAccounts', - value: [address] - } - ] - }, - { - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: [AMB_CHAIN_ID_HEX] - } - ] - } - ]; - } else { - response.error = { - code: 4200, - message: 'Requested permission is not available' - }; - } - break; - } - - case 'wallet_revokePermissions': { - const permissions = params[0]; - if (permissions?.eth_accounts) { - if (connectedAddress) { - setConnectedAddress(null); - - setTimeout(() => { - if (webViewRef.current) { - webViewRef.current.injectJavaScript(REVOKE_PERMISSIONS_JS); - } - }, 100); - } - - response.result = [ - { - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: [AMB_CHAIN_ID_HEX] - } - ] - } - ]; - } else if (permissions?.['endowment:permitted-chains']) { - response.error = { - code: 4200, - message: 'Chain permissions cannot be revoked' - }; - } else { - response.error = { - code: 4200, - message: 'Permission to revoke is not recognized' - }; - } - break; - } - - case 'wallet_getPermissions': { - const basePermissions = [ - { - parentCapability: 'endowment:permitted-chains', - date: Date.now(), - caveats: [ - { - type: 'restrictChains', - value: [AMB_CHAIN_ID_HEX] - } - ] - } - ]; - - if (connectedAddress) { - response.result = [ - { - parentCapability: 'eth_accounts', - date: Date.now(), - caveats: [ - { - type: 'restrictReturnedAccounts', - value: [connectedAddress] - } - ] - }, - ...basePermissions - ]; - } else { - response.result = basePermissions; - } - break; - } - - case 'eth_chainId': - response.result = await handleChainIdRequest(); - break; - - case 'eth_sendTransaction': { - try { - const txParams = params[0]; - - // Show confirmation alert - await new Promise((resolve, reject) => { - Alert.alert( - 'Confirm Transaction', - `Do you want to send this transaction?\n\n` + - `From: ${txParams.from}\n` + - `To: ${txParams.to}\n` + - `Value: ${txParams.value || '0'} Wei\n` + - `Data: ${txParams.data || 'None'}`, - [ - { - text: 'Cancel', - onPress: () => - reject(new Error('User rejected transaction')), - style: 'cancel' - }, - { - text: 'Send', - onPress: () => resolve(true), - style: 'default' - } - ], - { cancelable: false } - ); - }); - - response.result = await handleSendTransaction(txParams); - } catch (error) { - console.error('Transaction error:', error); - throw error; - } - break; - } - - case 'eth_signTransaction': { - try { - const txParams = params[0]; - - await new Promise((resolve, reject) => { - Alert.alert( - 'Sign Transaction', - `Do you want to sign this transaction?\n\n` + - `From: ${txParams.from}\n` + - `To: ${txParams.to}\n` + - `Value: ${txParams.value || '0'} Wei\n` + - `Data: ${txParams.data || 'None'}`, - [ - { - text: 'Cancel', - onPress: () => reject(new Error('User rejected signing')), - style: 'cancel' - }, - { - text: 'Sign', - onPress: () => resolve(true), - style: 'default' - } - ], - { cancelable: false } - ); - }); - - response.result = await handleSignTransaction(txParams); - } catch (error) { - console.error('Sign transaction error:', error); - throw error; - } - break; - } - - case 'personal_sign': { - try { - console.log('Personal sign request:', params); - const [message, address] = params; - - if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { - throw new Error('Address mismatch'); - } - - const userConfirmation = new Promise((resolve, reject) => { - Alert.alert( - 'Sign Message', - `Do you want to sign this message?\n\nMessage: ${message}\n\nAddress: ${address}`, - [ - { - text: 'Cancel', - onPress: () => reject(new Error('User rejected signing')), - style: 'cancel' - }, - { - text: 'Sign', - onPress: () => resolve(true), - style: 'default' - } - ], - { cancelable: false } - ); - }); - - await userConfirmation; - - const wallet = await extractWallet(); - const signature = await wallet.signMessage( - ethers.utils.isHexString(message) - ? ethers.utils.arrayify(message) - : message - ); - - console.log('Signature generated:', signature); - response.result = signature; - } catch (error) { - console.error('Personal sign error:', error); - throw error; - } - break; - } - - case 'eth_sign': { - try { - const [address, message] = params; - - if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { - throw new Error('Address mismatch'); - } - - const userConfirmation = new Promise((resolve, reject) => { - Alert.alert( - 'Sign Message', - `Do you want to sign this message?\n\nMessage: ${message}\n\nAddress: ${address}`, - [ - { - text: 'Cancel', - onPress: () => reject(new Error('User rejected signing')), - style: 'cancel' - }, - { - text: 'Sign', - onPress: () => resolve(true), - style: 'default' - } - ], - { cancelable: false } - ); - }); - - await userConfirmation; - - const wallet = await extractWallet(); - const signature = await wallet.signMessage(message); - - console.log('Signature generated:', signature); - response.result = signature; - } catch (error) { - console.error('Eth sign error:', error); - throw error; - } - break; - } - - case 'wallet_switchEthereumChain': - response.result = await handleSwitchChain(params[0]); - break; - - case 'wallet_addEthereumChain': - response.result = await handleAddChain(params[0]); - break; - - case 'eth_getBalance': - response.result = await handleGetBalance(params[0], params[1]); - break; - - case 'eth_signTypedData_v4': - case 'eth_signTypedData': { - try { - console.log('Sign typed data request:', params); - const [address, typedData] = params; - - if (address.toLowerCase() !== connectedAddress?.toLowerCase()) { - throw new Error('Address mismatch'); - } - - const data = - typeof typedData === 'string' - ? JSON.parse(typedData) - : typedData; - - const userConfirmation = new Promise((resolve, reject) => { - Alert.alert( - 'Sign Typed Data', - `Do you want to sign this data?\n\nFrom: ${address}\n\nData: ${JSON.stringify( - data, - null, - 2 - )}`, - [ - { - text: 'Cancel', - onPress: () => reject(new Error('User rejected signing')), - style: 'cancel' - }, - { - text: 'Sign', - onPress: () => resolve(true), - style: 'default' - } - ], - { cancelable: false } - ); - }); - - await userConfirmation; - - const wallet = await extractWallet(); - const signature = await wallet._signTypedData( - data.domain, - data.types, - data.message - ); - - console.log('Signature generated:', signature); - response.result = signature; - } catch (error) { - console.error('Failed to sign typed data:', error); - throw error; - } - break; - } - - default: - response.error = { - code: 4200, - message: `Method ${method} not supported` - }; - } - - sendResponse(response); - } finally { - requestsInProgress.current.delete(id); - } - } catch (error) { - sendResponse({ - id: -1, - jsonrpc: '2.0', - error: { - code: 4001, - message: (error as Error).message || 'Unknown error occurred' - } - }); - } - }; - - const handleChainIdRequest = async () => { - return `0x${Number(AMB_CHAIN_ID_DEC).toString(16)}`; - }; - - const handleSendTransaction = async (txParams: any) => { - const wallet = await extractWallet(); - const tx = await wallet.sendTransaction(txParams); - return tx.hash; - }; - - const handleSignTransaction = async (txParams: any) => { - const wallet = await extractWallet(); - return await wallet.signTransaction(txParams); - }; - - const handleSwitchChain = async (params: { chainId: string }) => { - const wallet = await extractWallet(); - await wallet.switchEthereumChain(parseInt(params.chainId, 16)); - return null; - }; - - const handleAddChain = async (chainParams: any) => { - const wallet = await extractWallet(); - await wallet.addChain(chainParams); - return null; - }; - - const handleGetBalance = async (address: string, blockTag = 'latest') => { - const provider = new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); - const balance = await provider.getBalance(address, blockTag); - return balance.toHexString(); - }; - - const sendResponse = (response: JsonRpcResponse) => { - if (webViewRef.current) { - const messageString = JSON.stringify(response); - console.log('Sending response:', response); - webViewRef.current.postMessage(messageString); - } - }; - useEffect(() => { if (webViewRef.current && connectedAddress) { const updateScript = ` @@ -582,17 +51,10 @@ export const BrowserScreen = () => { } }, [connectedAddress]); - const logger = (event: WebViewMessageEvent) => { - try { - const data = JSON.parse(event.nativeEvent.data); - console.log('WebView message:', { - method: data.method, - params: data.params, - id: data.id - }); - } catch (e) { - console.log('Raw WebView message:', event.nativeEvent.data); - } + const onMessage = async (event: WebViewMessageEvent) => { + event.persist(); + const privateKey = await _extractPrivateKey(); + await handleWebViewMessage({ event, webViewRef, privateKey }); }; return ( @@ -616,7 +78,7 @@ export const BrowserScreen = () => { }} javaScriptEnabled={true} injectedJavaScriptBeforeContentLoaded={INJECTED_PROVIDER_JS} - onMessage={handleWebViewMessage} + onMessage={onMessage} style={containerStyle} webviewDebuggingEnabled={__DEV__} /> From 4d4f33fa5103db504b55652339f136e8353b41b7 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:37:35 +0200 Subject: [PATCH 13/17] feat: enhance browser feature structure and refactor middleware - Refactored the middleware to streamline event handling and state management, enhancing performance and reliability. - Updated the injectable provider to use dynamic network version handling. - Removed the deprecated middlewareHelpers file and improved error handling in the rpc-error-handler. - Enhanced the BrowserScreen to utilize the new event handling structure, improving user interactions and state updates. --- src/features/browser/lib/eip6963.ts | 2 +- src/features/browser/lib/index.ts | 3 +++ .../browser/lib/injectable.provider.ts | 2 +- ...lewareHelpers.ts => middleware.helpers.ts} | 22 +++++++++++-------- src/features/browser/lib/rpc-methods.ts | 10 +++++---- src/features/browser/lib/rpc-middleware.ts | 16 +++++++------- src/features/browser/utils/index.ts | 2 +- ...tion.ts => request-user-approval-alert.ts} | 2 +- .../browser/utils/rpc-error-handler.ts | 5 +---- src/screens/Browser/index.tsx | 21 ++++++++++++------ 10 files changed, 49 insertions(+), 36 deletions(-) rename src/features/browser/lib/{middlewareHelpers.ts => middleware.helpers.ts} (91%) rename src/features/browser/utils/{showConfirmation.ts => request-user-approval-alert.ts} (92%) diff --git a/src/features/browser/lib/eip6963.ts b/src/features/browser/lib/eip6963.ts index e7c6d4739..ec7fc7746 100644 --- a/src/features/browser/lib/eip6963.ts +++ b/src/features/browser/lib/eip6963.ts @@ -6,4 +6,4 @@ export const EIP6963_PROVIDER_INFO = { name: `${appSlug} Wallet`, icon: 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAChCAMAAAB3TUS6AAAAAXNSR0IArs4c6QAAAwBQTFRFR3BMapTzVYPqSnjmZI/wV4XrZ5LxY47wWofsUoHpVoTrTnvnZZDwVILqYY3vaJPyXoruXYrtbJbzT3zoRXTkSHflUH3oa5Xzbpj0UYDpQ3LjXIntdpz3dp/4THrnWYbscZv1P3DiR3blOm3gX4joc5z2cJr1Zo/xXovudJ33QXHjeJ/2PG7gQ3PkaJPyaZPySHbleqH7apPyU4LoaJLyWYbreKD4eqL6QHDfdZ73Z5HwZI7vapTycJf1eJ/2cJn0bZb0N2rfSHjlSHfkZI7wOmzgOWzgXIjsaJHxbpb0WYbsc5r2cZr1WYbsR3flW4fscJj0cJj0b5n0d575aJHxaZTybpj1N2recpr1SHfleJ/2P3DhbJX0Om3fOGvfSHjldp33eJ/3e6L5TXznbZbzeaH4U4HqPGzfT37oOGreeqD4aZLxdZz2WobtUH7oeKD4O27gcJj1RHXje6L5d533VoPpUYDnY43uRnblYYzuQ3PjXInsVYPrUYDoYIzuUH/oYY3vPnDhXYntN2rfX4zvP2/heaH4dJv2bJXzPW7gOmzdc5v2e6L4fqT6faL5SXnlYYzuVYPqSnnlVoLpSXbkUoDoPW/gT33oZY7vWYTrZZDwZY/waJLxSXnmO23fYYzuZpDwWobsXIjsYIvuTHvmZI7vVIHpW4fsWYXrZY/vUH7oS3rmX4ruUX/oaJLxZ5HwYo3uVYLpUoDoVoPqXYnta5TyRXXjZY/wQ3TjRnbkV4TqY47vcJj0U4DpXontPW/haJHxSXjlWITqSHjkaZLxc5v1QXLibJXyTXzmR3fkcZn1X4vuSnnlbpfzQHHhZpHxaZPyYIzvY43vTnznXYjsYo3vT33nOmzfbZbzYIvtTXvnP3Dhb5f0WIXrXortapPxYozuapPyTn3neJ/4PG7ge6L5QnPiOGreV4Pqcpr1Z5Dwdp33Q3PiNmneeqH4W4frd573WYbrXIfsdZz2VoLqOWvfdJz2eaD4bJXzPW7hZZDwUn/oVILpZo/wXonsYIrtxp3BSwAAAQB0Uk5TAP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/iD+/v7+/v7+EP7+IP7+/jD+IG+//hBgIDD+/v4Qf39AkEDv/n/+f0Cvf19f31B/37+fX9+f7/5foN9w36+fYN+fn2/v34/v3++fb0C/v4Dv7+Tfz/nfv1D6QJBw35Dev+9Q57fv7+8w33i/59/PkLdvf9+Qv7/P33Dfr+7fz+/Pn6/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xQbD9oAABMCSURBVHja7VzpV9vnlRYghARCQuwGb9gGC9vxVoOTNGnWttk62ZNmT5pmkkx60pPkdJ85p5329LQ9032WdjbJCAECg9iFWc1mwGaxAWM4NsLgBYMJNth4wyHzPPcn5/TLfEPS74Pfv+Cee+/z3Ofe+76vRnP73D63z+1z+/w/Z/Nmddv3zu6r76xUsX3/cubM7NVv3aVa+1Z+a3Z29uqpVW+qNc7fPSMGnlp1UZ1xfvmMGHg1++KqzDdfVqGBv929e/dsNj14PvPSpTdXqjAF6cKrSMJVmZmX2tvfUp2JK383m00DT108f/5Sevvax59Vm4U7vpl9dTZ71arz589npl9pXzv1hOqc+A+z2dmnaOElGDiVcfDg/aqzECG+yBy80t4+NXWwPuOjnWojG+bgxczMK+3pa+HB+vrkp1QW581fuSg8097efjAD9p3oGb5fdRYiB2EfYlyf0ZPcMzz8q0dVZmGmeBA5CBee6DkR21H1qqrivPk7QHF6ezty8GBP8onh2NiqkOe3qstCocGDUwfvq69HiJM6qqrGn9+oIgvvTZcQZxDFycMdHR1VIWPmtv9WkYkv+3Iwo74HHuxIqho3H27TPaIiUrzrytp0RjgZKAZIqsbG20pK+oyPqMeJb6VnACTP1PcAJPBgSMhYiVs3bffcs0E1AjuDOdjTkzw8HJuUVDU2VlKis9tvGP+iljivfHwtUpAWEiMhY23mErfbfsPjjHhYJXHeNSW1uGe44xocaG4rMevEwLp+093qsPAF5GAyUBwrKGkr2Y8c/Mzl9JhMpl98XRUWPgH/fZGDZnNJn73P49LW1Z002RzfUEOcVz4NEMfGxg5fCxkbR4zdOpfLo40oLTXZRkb++TEVWPiohLhjGMV4HESo67PTgxGlNpvN4dj7i13Bt/ApKSQACVDctr9k2u4yaiP6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', rdns: Constants.expoConfig?.ios?.bundleIdentifier -}; +} as const; diff --git a/src/features/browser/lib/index.ts b/src/features/browser/lib/index.ts index 9d4f521c1..91ac3c2c7 100644 --- a/src/features/browser/lib/index.ts +++ b/src/features/browser/lib/index.ts @@ -1,2 +1,5 @@ export * from './injectable.provider'; export * from './rpc-middleware'; +export * from './rpc-methods'; +export * from './eip6963'; +export * from './middleware.helpers'; diff --git a/src/features/browser/lib/injectable.provider.ts b/src/features/browser/lib/injectable.provider.ts index 8b5348f42..47a1c3a03 100644 --- a/src/features/browser/lib/injectable.provider.ts +++ b/src/features/browser/lib/injectable.provider.ts @@ -30,7 +30,7 @@ export const INJECTED_PROVIDER_JS = ` isMetaMask: true, selectedAddress: null, chainId: '${AMB_CHAIN_ID_HEX}', - networkVersion: '${AMB_CHAIN_ID_DEC}', + networkVersion: ${AMB_CHAIN_ID_DEC}, _events: new Map(), isConnected: () => true, diff --git a/src/features/browser/lib/middlewareHelpers.ts b/src/features/browser/lib/middleware.helpers.ts similarity index 91% rename from src/features/browser/lib/middlewareHelpers.ts rename to src/features/browser/lib/middleware.helpers.ts index d778b189c..728b54e98 100644 --- a/src/features/browser/lib/middlewareHelpers.ts +++ b/src/features/browser/lib/middleware.helpers.ts @@ -6,11 +6,9 @@ import { REVOKE_PERMISSIONS_JS, UPDATE_ETHEREUM_STATE_JS } from '@features/browser/lib'; -import { rpcErrorHandler, showConfirmation } from '@features/browser/utils'; +import { rpcErrorHandler, requestUserApproval } from '@features/browser/utils'; import { rpcMethods } from './rpc-methods'; -const { connectedAddress, setConnectedAddress } = useBrowserStore.getState(); - const { getCurrentAddress, handleSendTransaction, @@ -24,6 +22,7 @@ export const ethRequestAccounts = async ({ privateKey, webViewRef }: any) => { + const { connectedAddress, setConnectedAddress } = useBrowserStore.getState(); console.log('eth_requestAccounts called'); const address = await getCurrentAddress(privateKey); if (!address) { @@ -56,6 +55,7 @@ export const walletRequestPermissions = async ({ privateKey, webViewRef }: any) => { + const { connectedAddress, setConnectedAddress } = useBrowserStore.getState(); try { if (permissions?.eth_accounts) { const address = await getCurrentAddress(privateKey); @@ -113,6 +113,7 @@ export const walletRevokePermissions = async ({ response, webViewRef }: any) => { + const { connectedAddress, setConnectedAddress } = useBrowserStore.getState(); try { if (permissions?.eth_accounts) { if (connectedAddress) { @@ -154,6 +155,7 @@ export const walletRevokePermissions = async ({ }; export const walletGetPermissions = async ({ response }: any) => { + const { connectedAddress } = useBrowserStore.getState(); try { const basePermissions = [ { @@ -198,7 +200,7 @@ export const ethSendTransaction = async ({ try { const txParams = params[0]; await new Promise((resolve, reject) => { - showConfirmation({ + requestUserApproval({ header: 'Confirm Transaction', message: `Do you want to send this transaction? From: ${txParams.from}\n` + @@ -217,7 +219,7 @@ export const ethSendTransaction = async ({ } }; -export const ethSingTransaction = async ({ +export const ethSignTransaction = async ({ params, response, privateKey @@ -226,7 +228,7 @@ export const ethSingTransaction = async ({ const txParams = params[0]; await new Promise((resolve, reject) => { - showConfirmation({ + requestUserApproval({ header: 'Sign Transaction', message: `Do you want to send this transaction? From: ${txParams.from}\n` + @@ -246,6 +248,7 @@ export const ethSingTransaction = async ({ }; export const personalSing = async ({ params, response, privateKey }: any) => { + const { connectedAddress } = useBrowserStore.getState(); try { console.log('Personal sign request:', params); const [message, address] = params; @@ -255,7 +258,7 @@ export const personalSing = async ({ params, response, privateKey }: any) => { } const userConfirmation = new Promise((resolve, reject) => { - showConfirmation({ + requestUserApproval({ header: 'Sign Message', message: 'Do you want to sign this message?', resolve: () => resolve(true), @@ -275,11 +278,12 @@ export const personalSing = async ({ params, response, privateKey }: any) => { } }; -export const ethSingTypesData = async ({ +export const ethSignTypesData = async ({ params, response, privateKey }: any) => { + const { connectedAddress } = useBrowserStore.getState(); try { console.log('Sign typed data request:', params); const [address, typedData] = params; @@ -292,7 +296,7 @@ export const ethSingTypesData = async ({ typeof typedData === 'string' ? JSON.parse(typedData) : typedData; const userConfirmation = new Promise((resolve, reject) => { - showConfirmation({ + requestUserApproval({ header: 'Sign Typed Data', message: `Do you want to sign this data?\n\nFrom: ${address}\n\nData: ${JSON.stringify( data, diff --git a/src/features/browser/lib/rpc-methods.ts b/src/features/browser/lib/rpc-methods.ts index 960699ef6..7595193d8 100644 --- a/src/features/browser/lib/rpc-methods.ts +++ b/src/features/browser/lib/rpc-methods.ts @@ -5,12 +5,14 @@ import { WebView } from '@metamask/react-native-webview'; import { JsonRpcResponse } from '@walletconnect/jsonrpc-types'; import { ethers } from 'ethers'; import Config from '@constants/config'; -import { AMB_CHAIN_ID_DEC } from '@features/browser/constants'; -import { rpcErrorHandler } from '@features/browser/utils'; -import { createAMBProvider } from '@features/swap/utils/contracts/instances'; +import { AMB_CHAIN_ID_DEC } from '../constants'; +import { rpcErrorHandler } from '../utils'; const extractWallet = async (privateKey: string) => { - return new ethers.Wallet(privateKey, createAMBProvider()); + return new ethers.Wallet( + privateKey, + new ethers.providers.JsonRpcProvider(Config.NETWORK_URL, Config.CHAIN_ID) + ); }; const getCurrentAddress = async (privateKey: string) => { diff --git a/src/features/browser/lib/rpc-middleware.ts b/src/features/browser/lib/rpc-middleware.ts index 4f63fe39d..f86c79080 100644 --- a/src/features/browser/lib/rpc-middleware.ts +++ b/src/features/browser/lib/rpc-middleware.ts @@ -4,18 +4,18 @@ import { RefObject } from 'react'; import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; import { useBrowserStore } from '@entities/browser/model'; import { AMB_CHAIN_ID_DEC } from '@features/browser/constants'; -import { rpcMethods } from '@features/browser/lib/rpc-methods'; -import { rpcErrorHandler } from '@features/browser/utils'; +import { rpcErrorHandler } from '@features/browser/utils/rpc-error-handler'; import { ethRequestAccounts, ethSendTransaction, - ethSingTransaction, - ethSingTypesData, + ethSignTransaction, + ethSignTypesData, personalSing, walletGetPermissions, walletRequestPermissions, walletRevokePermissions -} from './middlewareHelpers'; +} from './middleware.helpers'; +import { rpcMethods } from './rpc-methods'; interface JsonRpcRequest { id: number; @@ -45,8 +45,8 @@ export async function handleWebViewMessage({ webViewRef, privateKey }: HandleWebViewMessageModel) { - const requestsInProgress = new Set(); const { connectedAddress } = useBrowserStore.getState(); + const requestsInProgress = new Set(); const { handleChainIdRequest, handleGetBalance, sendResponse } = rpcMethods; @@ -135,7 +135,7 @@ export async function handleWebViewMessage({ } case 'eth_signTransaction': { - await ethSingTransaction({ + await ethSignTransaction({ params, response, privateKey @@ -154,7 +154,7 @@ export async function handleWebViewMessage({ case 'eth_signTypedData_v4': case 'eth_signTypedData': - await ethSingTypesData({ + await ethSignTypesData({ params, response, privateKey diff --git a/src/features/browser/utils/index.ts b/src/features/browser/utils/index.ts index fa23d1aa4..e14c42139 100644 --- a/src/features/browser/utils/index.ts +++ b/src/features/browser/utils/index.ts @@ -1,2 +1,2 @@ -export * from './showConfirmation'; +export * from './request-user-approval-alert'; export * from './rpc-error-handler'; diff --git a/src/features/browser/utils/showConfirmation.ts b/src/features/browser/utils/request-user-approval-alert.ts similarity index 92% rename from src/features/browser/utils/showConfirmation.ts rename to src/features/browser/utils/request-user-approval-alert.ts index 9bb2e76c6..e04f9675e 100644 --- a/src/features/browser/utils/showConfirmation.ts +++ b/src/features/browser/utils/request-user-approval-alert.ts @@ -8,7 +8,7 @@ interface ShowConfirmationModel { cancelable?: boolean; } -export const showConfirmation = async ({ +export const requestUserApproval = async ({ header = 'Confirmation', message = '', reject, diff --git a/src/features/browser/utils/rpc-error-handler.ts b/src/features/browser/utils/rpc-error-handler.ts index 739a32e58..32193cd32 100644 --- a/src/features/browser/utils/rpc-error-handler.ts +++ b/src/features/browser/utils/rpc-error-handler.ts @@ -1,7 +1,4 @@ -/* eslint-disable no-console */ -// tslint:disable:no-console - export const rpcErrorHandler = (point: unknown, err: unknown) => { const error = typeof err === 'string' ? err : JSON.stringify(err); - console.log(`${point} ERROR`, error); + console.error(`${point} ERROR`, error); }; diff --git a/src/screens/Browser/index.tsx b/src/screens/Browser/index.tsx index 91493d171..17cd444a5 100644 --- a/src/screens/Browser/index.tsx +++ b/src/screens/Browser/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { StyleProp, ViewStyle } from 'react-native'; import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -51,11 +51,18 @@ export const BrowserScreen = () => { } }, [connectedAddress]); - const onMessage = async (event: WebViewMessageEvent) => { - event.persist(); - const privateKey = await _extractPrivateKey(); - await handleWebViewMessage({ event, webViewRef, privateKey }); - }; + const onMessageEventHandler = useCallback( + async (event: WebViewMessageEvent) => { + event.persist(); + try { + const privateKey = await _extractPrivateKey(); + await handleWebViewMessage({ event, webViewRef, privateKey }); + } catch (error) { + throw error; + } + }, + [_extractPrivateKey] + ); return ( @@ -78,7 +85,7 @@ export const BrowserScreen = () => { }} javaScriptEnabled={true} injectedJavaScriptBeforeContentLoaded={INJECTED_PROVIDER_JS} - onMessage={onMessage} + onMessage={onMessageEventHandler} style={containerStyle} webviewDebuggingEnabled={__DEV__} /> From 4133c51cce53f2212fc4a87a85b567e3173dd291 Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:38:36 +0200 Subject: [PATCH 14/17] fix: update import paths - Updated the import path for middleware helpers to use the correct module location. --- src/features/browser/lib/rpc-middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/browser/lib/rpc-middleware.ts b/src/features/browser/lib/rpc-middleware.ts index f86c79080..6513bccd4 100644 --- a/src/features/browser/lib/rpc-middleware.ts +++ b/src/features/browser/lib/rpc-middleware.ts @@ -4,7 +4,6 @@ import { RefObject } from 'react'; import { WebView, WebViewMessageEvent } from '@metamask/react-native-webview'; import { useBrowserStore } from '@entities/browser/model'; import { AMB_CHAIN_ID_DEC } from '@features/browser/constants'; -import { rpcErrorHandler } from '@features/browser/utils/rpc-error-handler'; import { ethRequestAccounts, ethSendTransaction, @@ -14,7 +13,8 @@ import { walletGetPermissions, walletRequestPermissions, walletRevokePermissions -} from './middleware.helpers'; +} from '@features/browser/lib/middleware.helpers'; +import { rpcErrorHandler } from '@features/browser/utils/rpc-error-handler'; import { rpcMethods } from './rpc-methods'; interface JsonRpcRequest { From d710675be0df7b7d92dbddb3c1c085929aaa6f9d Mon Sep 17 00:00:00 2001 From: ArturHoncharuk <73081258+ArturHoncharuk@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:03:28 +0200 Subject: [PATCH 15/17] fix: correct import paths in middleware.helpers.ts - Updated the import statement for the injectable provider to ensure proper module resolution. - Removed redundant import of rpcErrorHandler and requestUserApproval, streamlining the code structure. --- src/features/browser/lib/middleware.helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/browser/lib/middleware.helpers.ts b/src/features/browser/lib/middleware.helpers.ts index 728b54e98..92b36f77d 100644 --- a/src/features/browser/lib/middleware.helpers.ts +++ b/src/features/browser/lib/middleware.helpers.ts @@ -2,11 +2,11 @@ // tslint:disable:no-console import { useBrowserStore } from '@entities/browser/model'; import { AMB_CHAIN_ID_HEX } from '@features/browser/constants'; +import { rpcErrorHandler, requestUserApproval } from '@features/browser/utils'; import { REVOKE_PERMISSIONS_JS, UPDATE_ETHEREUM_STATE_JS -} from '@features/browser/lib'; -import { rpcErrorHandler, requestUserApproval } from '@features/browser/utils'; +} from './injectable.provider'; import { rpcMethods } from './rpc-methods'; const { From 58cacf7347c98af4660ec72d6a8aa94e747c8942 Mon Sep 17 00:00:00 2001 From: EvgeniyJB <81742350+EvgeniyJB@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:58:15 +0100 Subject: [PATCH 16/17] hotfix: change browser types file --- src/entities/browser/model/types.ts | 11 +++++++++++ src/entities/harbor/model/types.ts | 5 ----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/entities/browser/model/types.ts diff --git a/src/entities/browser/model/types.ts b/src/entities/browser/model/types.ts new file mode 100644 index 000000000..26db60b26 --- /dev/null +++ b/src/entities/browser/model/types.ts @@ -0,0 +1,11 @@ +export interface BrowserStoreModel { + connectedAddress: string; + setConnectedAddress: (address: string) => void; +} + +export interface BrowserConfig { + id: string; + name: string; + url: string; + isAirDaoApp: string; +} diff --git a/src/entities/harbor/model/types.ts b/src/entities/harbor/model/types.ts index 50e530ee4..a24f59fd6 100644 --- a/src/entities/harbor/model/types.ts +++ b/src/entities/harbor/model/types.ts @@ -55,8 +55,3 @@ export interface HarborStoreModel { loading: boolean; updateAll: (payload: string) => void; } - -export interface BrowserStoreModel { - connectedAddress: string; - setConnectedAddress: (address: string) => void; -} From ca9b9abfd4de770069bbc1e7ab535b6be4f54c7c Mon Sep 17 00:00:00 2001 From: EvgeniyJB <81742350+EvgeniyJB@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:12:26 +0100 Subject: [PATCH 17/17] hotfix: hotfix app crush --- src/features/products/entities/_products.tsx | 1 + src/features/swap/utils/contracts/instances.ts | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/features/products/entities/_products.tsx b/src/features/products/entities/_products.tsx index f844ef43b..b05e3343c 100644 --- a/src/features/products/entities/_products.tsx +++ b/src/features/products/entities/_products.tsx @@ -6,6 +6,7 @@ import { ProductStake, ProductSwap } from '@components/svg/icons/v2'; +import { SwapAccountActionIcon } from '@components/svg/icons/v2/actions'; import { CustomAppEvents } from '@lib/firebaseEventAnalytics'; import { SectionizedProducts } from '../utils'; diff --git a/src/features/swap/utils/contracts/instances.ts b/src/features/swap/utils/contracts/instances.ts index e1df354da..d4ee26bdd 100644 --- a/src/features/swap/utils/contracts/instances.ts +++ b/src/features/swap/utils/contracts/instances.ts @@ -1,7 +1,6 @@ import { ethers } from 'ethers'; import Config from '@constants/config'; import { FACTORY_ABI } from '@features/swap/lib/abi'; -import { AmbErrorProvider } from '@lib'; type ProviderOrSigner = ethers.providers.JsonRpcProvider | ethers.Signer; @@ -10,12 +9,6 @@ export function createAMBProvider() { Config.NETWORK_URL, Config.CHAIN_ID ); - -export function createAMBProvider() { - if (__DEV__) - return new AmbErrorProvider(Config.NETWORK_URL, Config.CHAIN_ID); - - return new ethers.providers.JsonRpcProvider(Config.NETWORK_URL); } export function createSigner(