CP-13448 - Injected Provider Demo#3645
Merged
alexnicolae-ava merged 7 commits intomainfrom Mar 19, 2026
Merged
Conversation
78b150c to
dc95a73
Compare
032f8eb to
08ef414
Compare
B0Y3R-AVA
reviewed
Mar 18, 2026
B0Y3R-AVA
reviewed
Mar 18, 2026
B0Y3R-AVA
reviewed
Mar 18, 2026
packages/core-mobile/app/hooks/browser/useEvmInjectedProvider.ts
Outdated
Show resolved
Hide resolved
B0Y3R-AVA
reviewed
Mar 18, 2026
Contributor
B0Y3R-AVA
left a comment
There was a problem hiding this comment.
looking good, few small comments
onghwan
approved these changes
Mar 19, 2026
B0Y3R-AVA
approved these changes
Mar 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Ticket: CP-13448
iOS: 7769
Android: 7770
EVM Injected Provider — Demo Implementation
1. Overview
The in-app browser now injects an EIP-1193 compliant
window.ethereumprovider into every web page, making dApps recognize Core Mobile as an installed wallet — exactly like the Core browser extension does on desktop.Before
sequenceDiagram participant User participant dApp participant Browser as In-App Browser User->>Browser: Opens dApp dApp->>Browser: Checks window.ethereum Browser-->>dApp: undefined dApp->>User: "No wallet detected, install extension or use WalletConnect"After
sequenceDiagram participant User participant dApp participant Shim as Provider Shim (JS) participant Native as Native Bridge (RN) participant Chain as Blockchain User->>dApp: Opens dApp dApp->>Shim: window.ethereum.request({ method: 'eth_accounts' }) Shim-->>dApp: ['0x1234...'] (pre-connected) dApp->>User: "Connected as 0x1234..."2. Architecture
High-Level Flow
graph TD subgraph WebView ["WebView (dApp Page)"] A[dApp JavaScript] -->|"window.ethereum.request()"| B[Provider Shim] B -->|"Local methods"| B B -->|"postMessage"| C[ReactNativeWebView Bridge] end subgraph Native ["React Native (Native App)"] C -->|"onMessage"| D[useEvmInjectedProvider Hook] D -->|"Read-only RPC"| E[proxyToRpc] D -->|"Signing methods"| F[dispatchSigningRequest] F --> G[createInAppRequest / Redux] G --> H[Approval Screen] H -->|"User approves"| I[VM Module] end E -->|"fetch"| J[RPC Node] I -->|"sign + send"| J J -->|"result"| D D -->|"injectJavaScript"| B B -->|"resolve Promise"| AThree-Tier Method Routing
flowchart LR Request["provider.request()"] --> Check{Method type?} Check -->|"eth_chainId, eth_accounts<br/>eth_requestAccounts<br/>wallet_requestPermissions"| Local["Resolve locally<br/>(no bridge)"] Check -->|"eth_getBalance, eth_call<br/>eth_getLogs + 15 more"| Proxy["Proxy to RPC node<br/>(via native fetch)"] Check -->|"personal_sign<br/>eth_sendTransaction<br/>eth_signTypedData_v4 + 4 more"| Sign["Dispatch to Redux<br/>→ Approval Screen<br/>→ VM Module"] Local -->|"~0ms"| Done["Promise resolves"] Proxy -->|"~100-500ms"| Done Sign -->|"User interaction"| Done3. Files Changed
New Files
app/hooks/browser/evmProviderShim.tswindow.ethereum, EIP-6963, and all local RPC handlingapp/hooks/browser/useEvmInjectedProvider.tsModified Files
app/hooks/browser/useInjectedJavascript.tsprovider_requestanddomain_metadatatoInjectedJsMessageWrappertypeapp/new/features/browser/components/BrowserTab.tsxuseEvmInjectedProvider, split JS injection intobeforeContentLoadedandafterContentLoaded, added message routingapp/new/features/browser/components/Webview.tsxinjectedJavaScriptBeforeContentLoadedprop support4. How It Works
Injection Timing
sequenceDiagram participant WebView as WebView (native) participant Shim as Provider Shim participant Page as Page Scripts participant wagmi as wagmi / ConnectKit Note over WebView: injectedJavaScriptBeforeContentLoaded WebView->>Shim: Execute provider shim Shim->>Shim: Set window.ethereum, window.core, window.avalanche Shim->>Shim: Dispatch eip6963:announceProvider Shim->>Shim: Listen for eip6963:requestProvider Note over Page: HTML parsing begins Page->>Page: Load bundled JS Note over wagmi: App initialization wagmi->>Shim: eip6963:requestProvider Shim->>wagmi: eip6963:announceProvider (Core) wagmi->>Shim: provider.on('connect', handler) Note over Shim: Auto-fires handler (already connected) Shim->>wagmi: handler({ chainId: '0xa86a' }) wagmi->>wagmi: Auto-connect completeThe provider is injected via
injectedJavaScriptBeforeContentLoaded, which runs before any page scripts. This is critical — dApps check forwindow.ethereumduring their initialization. If we inject after page load (viainjectedJavaScript), many dApps will have already decided "no wallet detected" and show install prompts.Pre-Connected State
Unlike a browser extension which requires a connection approval step, the mobile in-app browser pre-connects the provider with the active account. This means:
eth_accountsreturns['0x...']immediately (noeth_requestAccountsneeded)isConnected()returnstruefrom the startconnect/accountsChangedevents, the callbacks fire immediatelyThis design makes sense because the in-app browser is the wallet — there's no separate extension the user needs to approve. dApps auto-connect on page load.
Signing Flow (e.g., OpenSea SIWE)
sequenceDiagram participant dApp as dApp (OpenSea) participant Shim as Provider Shim participant Bridge as Native Bridge participant Redux as Redux RPC System participant UI as Approval Screen participant VM as VM Module participant Chain as Blockchain dApp->>Shim: request({ method: 'personal_sign', params: [message, address] }) Shim->>Bridge: postMessage({ method: 'provider_request', payload: ... }) Bridge->>Redux: createInAppRequest(dispatch) Redux->>UI: Show approval modal UI->>User: "OpenSea wants you to sign this message" alt User approves User->>UI: Tap "Approve" UI->>VM: Sign message VM-->>Bridge: Signature (0x...) Bridge->>Shim: __coreProviderRespond(id, null, '0x...') Shim-->>dApp: Promise resolves with signature dApp->>User: "Authenticated!" else User rejects User->>UI: Tap "Reject" UI-->>Bridge: Error { code: 4001 } Bridge->>Shim: __coreProviderRespond(id, { code: 4001 }, null) Shim-->>dApp: Promise rejects with EIP-1193 error endRead-Only RPC Proxy
sequenceDiagram participant dApp participant Shim as Provider Shim participant Bridge as Native Bridge participant RPC as RPC Node dApp->>Shim: request({ method: 'eth_getBalance', params: ['0x...', 'latest'] }) Shim->>Bridge: postMessage(provider_request) Bridge->>RPC: POST { jsonrpc: '2.0', method: 'eth_getBalance', ... } RPC-->>Bridge: { result: '0x1234...' } Bridge->>Shim: __coreProviderRespond(id, null, '0x1234...') Shim-->>dApp: Promise resolves5. Provider Shim Deep Dive
The provider shim (
evmProviderShim.ts) is a self-contained JavaScript IIFE that createswindow.ethereuminside the WebView. Key design decisions:Global Provider Installation
Three globals are set to maximize compatibility with different wallet connection libraries:
window.ethereumwindow.corewindow.core?.ethereumwindow.avalanchewindow.avalanche.isAvalancheAll are protected with
Object.definePropertyto prevent dApp scripts from overwriting them.Provider Flags
Methods Handled Locally (No Bridge Round-Trip)
eth_chainIdeth_accountseth_requestAccountseth_accounts(auto-approve)net_versioneth_coinbasewallet_requestPermissionswallet_getPermissionsAuto-Connect Event Mechanism
When a dApp library (wagmi) subscribes to events:
This triggers wagmi's internal
onConnectflow, causing the dApp to recognize the wallet as already connected — no manual "Connect Wallet" click needed.EIP-6963 Announcement
The
rdns: 'app.core.extension'matches the Core browser extension's identifier in wallet connection libraries, so dApps show the correct Core branding.6. Native Bridge Deep Dive
The
useEvmInjectedProviderhook (useEvmInjectedProvider.ts) handles all messages that arrive from the WebView.Method Routing
flowchart TD MSG["handleProviderMessage(payload)"] --> PARSE["Parse JSON"] PARSE --> SWITCH{"switch(method)"} SWITCH -->|"wallet_switchEthereumChain"| STUB["Return null (stub)"] SWITCH -->|"personal_sign<br/>eth_sendTransaction<br/>eth_signTypedData_*<br/>eth_sign"| SIGN["dispatchSigningRequest"] SWITCH -->|"eth_getBalance, eth_call<br/>eth_getLogs + 15 more"| PROXY["proxyToRpc"] SWITCH -->|"unknown"| REJECT["Error: -32601"] SIGN --> REDUX["createInAppRequest(dispatch)"] REDUX --> APPROVAL["Approval Screen"] APPROVAL --> VM["VM Module"] PROXY --> FETCH["fetch(rpcUrl, { method, params })"] VM --> RESPOND["sendResponse(id, error, result)"] FETCH --> RESPOND STUB --> RESPOND REJECT --> RESPOND RESPOND --> INJECT["webViewRef.injectJavaScript()"]Signing Methods Mapping
eth_sendTransactionRpcMethod.ETH_SEND_TRANSACTIONpersonal_signRpcMethod.PERSONAL_SIGNeth_signRpcMethod.ETH_SIGNeth_signTypedDataRpcMethod.SIGN_TYPED_DATAeth_signTypedData_v1RpcMethod.SIGN_TYPED_DATA_V1eth_signTypedData_v3RpcMethod.SIGN_TYPED_DATA_V3eth_signTypedData_v4RpcMethod.SIGN_TYPED_DATA_V4Read-Only Methods (18 total)
eth_blockNumber,eth_call,eth_estimateGas,eth_gasPrice,eth_getBalance,eth_getBlockByHash,eth_getBlockByNumber,eth_getCode,eth_getLogs,eth_getStorageAt,eth_getTransactionByHash,eth_getTransactionCount,eth_getTransactionReceipt,eth_maxPriorityFeePerGas,eth_feeHistory,web3_clientVersion,web3_sha3,eth_getBlockTransactionCountByHash,eth_getBlockTransactionCountByNumber7. dApp Compatibility
Wallet Connection Libraries
During development, we discovered that different dApps use different wallet connection libraries, each with its own provider detection mechanism:
graph LR subgraph Libraries ["Wallet Connection Libraries"] A[wagmi / EIP-6963] B[ConnectKit] C[RainbowKit] D["Direct window.ethereum"] end subgraph Detection ["How They Find Core"] A -->|"eip6963:announceProvider event"| E["Provider from event detail"] B -->|"wagmi EIP-6963 + window.avalanche"| F["window.avalanche.isAvalanche"] C -->|"window.core?.ethereum"| G["window.core.ethereum"] D -->|"window.ethereum"| H["window.ethereum"] end subgraph Globals ["Our Provider Globals"] E --> I[provider object] F --> I G --> I H --> I endeip6963:announceProvidereventwindow.avalanche.isAvalancheflagwindow.core?.ethereumnamespacewindow.ethereumwindow.ethereum.request()Tested dApps
personal_sign)8. What the Demo Covers
Working Features
window.ethereuminjected before page content loadseth_requestAccounts/eth_accounts— returns active account instantlyeth_chainId/net_version— returns current chainwallet_requestPermissions/wallet_getPermissions— mock permissionspersonal_sign(SIWE authentication)eth_sendTransactioneth_signTypedData(v1, v3, v4)eth_signwindow.coreandwindow.avalancheglobals for library compatibilityenable(),send(),sendAsync())INJECTED_PROVIDER)9. Known Limitations
No Connection Approval
Currently, every dApp is auto-approved. There is no "Connect to this site?" prompt. The provider returns the active account to any page that asks. For production, we need per-origin connection tracking.
Chain Switching is Stubbed
wallet_switchEthereumChainreturns success without actually switching. The dApp believes the chain changed, but the wallet stays on the same network. Need to wire into the actual network switching logic and emitchainChangedevents.No
chainChanged/accountsChangedEvents from AppIf the user switches their account or network inside the app while a dApp is open, the dApp is not notified. Need to observe Redux state changes and call
emitEvent().Single Account Only
Only the active EVM account (
addressC) is exposed. No multi-account selection.No
wallet_addEthereumChaindApps cannot add custom networks. Requests will fail with "Method not supported."
No Disconnect Support
wallet_revokePermissionsis not handled. dApps that try to disconnect will silently fail (wagmi has a timeout for this so it won't hang).Screenshots/Videos
Testing
Dev Testing (if applicable)
INJECTED_PROVIDER_DEMO_ENABLEDwithinBrowserTab.tsxtotrueChecklist
Please check all that apply (if applicable)