Skip to content

Commit 22aa667

Browse files
committed
fix wallet provider
1 parent ae0f5fa commit 22aa667

File tree

2 files changed

+43
-127
lines changed

2 files changed

+43
-127
lines changed

apps/remix-ide/src/app/plugins/prompt-blocks.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ export const invariants = {
140140
3. **ALWAYS import React from 'react'** in any file using JSX (especially \`src/main.jsx\` and \`src/App.jsx\`).
141141
- Example: \`import React from 'react';\` must be at the top, even if you use \`createRoot\`.
142142
4. **ETHERS.JS PROVIDER RULES (CRITICAL):**
143-
- **MUST USE:** Always use \`new ethers.BrowserProvider(window.ethereum)\` for both reading and writing.
143+
- **MUST USE:** Always use \`ethers.BrowserProvider\` with a wallet provider for both reading and writing.
144+
- **PROVIDER ACQUISITION:** Use \`window.__qdapp_getProvider ? await window.__qdapp_getProvider() : window.ethereum\` to get the provider. Store this raw provider in a ref/variable for reuse (e.g. network switching).
144145
- **FORBIDDEN:** NEVER use \`new ethers.JsonRpcProvider\`, \`InfuraProvider\`, or \`AlchemyProvider\`.
145146
- **FORBIDDEN:** NEVER generate code containing placeholders like 'YOUR_INFURA_KEY' or ask for API keys.
146147
4. Use React with JSX syntax (not "text/babel" scripts).
@@ -233,11 +234,24 @@ const switchNetwork = async (targetChainHex) => {
233234
1. **Connect Wallet** button must be visible in the header/navbar when disconnected.
234235
2. **Disconnect Wallet** button must be visible in the header/navbar when connected (next to the account address).
235236
3. **Switch Network** button must appear **only when** the connected wallet's chain ID differs from the DApp's target chain ID. Hide it when on the correct network.
236-
4. Check \`window.ethereum\` existence before any wallet operations.
237-
5. Show "Wrong Network" warning with a **Switch Network** button if chain ID mismatches.
238-
6. Use loading spinners for async actions.
239-
7. Handle "User rejected request" errors gracefully.
240-
8. Show truncated wallet address (e.g. \`0x1234...5678\`) when connected.
237+
4. Use loading spinners for async actions.
238+
5. Handle "User rejected request" errors gracefully.
239+
6. Show truncated wallet address (e.g. \`0x1234...5678\`) when connected.
240+
241+
**WALLET PROVIDER ACQUISITION (CRITICAL):**
242+
The deployed DApp uses \`window.__qdapp_getProvider()\` to discover and select wallets via EIP-6963.
243+
Always get the raw provider like this:
244+
\`\`\`javascript
245+
const rawProvider = window.__qdapp_getProvider
246+
? await window.__qdapp_getProvider()
247+
: window.ethereum;
248+
if (!rawProvider) {
249+
alert('Please install a Web3 wallet (e.g. MetaMask).');
250+
return;
251+
}
252+
const provider = new ethers.BrowserProvider(rawProvider);
253+
\`\`\`
254+
**Store \`rawProvider\` in a React ref** (e.g. \`rawProviderRef.current = rawProvider\`) so you can reuse it for network switching without calling \`__qdapp_getProvider\` again.
241255
242256
**🚨 CHAIN ID COMPARISON (CRITICAL — prevents wrong-network false positive):**
243257
- ethers.js v6 returns \`network.chainId\` as a **BigInt** (e.g. \`11155111n\`).
@@ -260,6 +274,7 @@ const disconnectWallet = () => {
260274
setAccount(null);
261275
setProvider(null);
262276
setSigner(null);
277+
rawProviderRef.current = null;
263278
// Clear saved wallet preference for wallet selection
264279
try { localStorage.removeItem('__qdapp_wallet_rdns'); } catch(e) {}
265280
};
@@ -268,17 +283,19 @@ The Disconnect button should be placed in the navbar/header, visible when connec
268283
When disconnected, the DApp should return to the initial "Connect Wallet" state.
269284
270285
**Network Switch Pattern (mandatory — MUST be a visible button):**
286+
Use the stored \`rawProviderRef.current\` for network operations:
271287
\`\`\`javascript
272288
const switchNetwork = async (targetChainHex) => {
289+
const rp = rawProviderRef.current;
290+
if (!rp) return;
273291
try {
274-
await window.ethereum.request({
292+
await rp.request({
275293
method: 'wallet_switchEthereumChain',
276294
params: [{ chainId: targetChainHex }],
277295
});
278296
} catch (switchError) {
279297
if (switchError.code === 4902) {
280-
// Chain not added — prompt to add it
281-
await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [...] });
298+
await rp.request({ method: 'wallet_addEthereumChain', params: [...] });
282299
} else {
283300
throw switchError;
284301
}
@@ -295,7 +312,10 @@ Show a **"Switch to [Network Name]"** button when the user is on the wrong chain
295312
- **Read-Only:** For 'view'/'pure' functions, use \`new ethers.Contract(addr, abi, provider)\`.
296313
- **Write (Transaction):** For 'nonpayable'/'payable' functions, YOU MUST USE A SIGNER:
297314
\`\`\`javascript
298-
const provider = new ethers.BrowserProvider(window.ethereum);
315+
const rawProvider = window.__qdapp_getProvider
316+
? await window.__qdapp_getProvider()
317+
: window.ethereum;
318+
const provider = new ethers.BrowserProvider(rawProvider);
299319
const signer = await provider.getSigner();
300320
const contractWithSigner = new ethers.Contract(address, abi, signer);
301321
const tx = await contractWithSigner.functionName(args);

libs/remix-ui/quick-dapp-v2/src/lib/utils/wallet-selection-script.ts

Lines changed: 13 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
*
44
* This script is injected into the <head> of the deployed HTML file and provides:
55
* 1. EIP-6963 provider discovery
6-
* 2. A Proxy on window.ethereum that intercepts eth_requestAccounts
7-
* 3. A minimal wallet selection modal (inline HTML/CSS)
6+
* 2. A wallet selection modal (inline HTML/CSS)
7+
* 3. A global `window.__qdapp_getProvider()` function for DApp code to call
88
* 4. localStorage-based auto-reconnect on page refresh
99
*
10+
* It does NOT override `window.ethereum` — avoids conflicts with MetaMask/Coinbase.
1011
* It is NOT injected in the IDE preview (VM Bridge or parent.ethereum are used there).
1112
*/
1213
export function generateWalletSelectionScript(): string {
@@ -83,11 +84,11 @@ export function generateWalletSelectionScript(): string {
8384
return resolve(_providers[0].provider);
8485
}
8586
86-
// No providers? Fall back to legacy window.ethereum if captured
87+
// No providers? Fall back to legacy window.ethereum
8788
if (_providers.length === 0) {
88-
if (window.__qdapp_legacy_ethereum) {
89+
if (window.ethereum) {
8990
_modalPromise = null;
90-
return resolve(window.__qdapp_legacy_ethereum);
91+
return resolve(window.ethereum);
9192
}
9293
_modalPromise = null;
9394
return reject(new Error('No Ethereum wallet detected. Please install a wallet extension.'));
@@ -178,119 +179,14 @@ export function generateWalletSelectionScript(): string {
178179
return div.innerHTML;
179180
}
180181
181-
// ── 4. Proxy window.ethereum ────────────────────────────────
182-
// Capture any existing window.ethereum (e.g. from legacy injection)
183-
if (window.ethereum) {
184-
window.__qdapp_legacy_ethereum = window.ethereum;
185-
}
186-
187-
var _resolved = false;
188-
var _selectedProvider = null;
189-
var _eventListeners = {};
190-
191-
function getProviderOrProxy() {
192-
if (_resolved && _selectedProvider) return _selectedProvider;
193-
194-
return new Proxy({
195-
isMetaMask: true, // Some DApps check this
196-
_isQuickDappProxy: true,
197-
198-
request: function(args) {
199-
if (_resolved && _selectedProvider) {
200-
return _selectedProvider.request(args);
201-
}
202-
return showWalletModal().then(function(provider) {
203-
_selectedProvider = provider;
204-
_resolved = true;
205-
// Object.defineProperty getter will now return _selectedProvider
206-
// Replay any buffered event listeners
207-
Object.keys(_eventListeners).forEach(function(evt) {
208-
_eventListeners[evt].forEach(function(cb) {
209-
try { provider.on(evt, cb); } catch(e) {}
210-
});
211-
});
212-
return provider.request(args);
213-
});
214-
},
215-
216-
on: function(event, cb) {
217-
if (_resolved && _selectedProvider) {
218-
return _selectedProvider.on(event, cb);
219-
}
220-
// Buffer listeners for later replay
221-
if (!_eventListeners[event]) _eventListeners[event] = [];
222-
_eventListeners[event].push(cb);
223-
return this;
224-
},
225-
226-
removeListener: function(event, cb) {
227-
if (_resolved && _selectedProvider) {
228-
return _selectedProvider.removeListener(event, cb);
229-
}
230-
if (_eventListeners[event]) {
231-
_eventListeners[event] = _eventListeners[event].filter(function(l) { return l !== cb; });
232-
}
233-
return this;
234-
},
235-
236-
removeAllListeners: function() {
237-
if (_resolved && _selectedProvider) {
238-
return _selectedProvider.removeAllListeners();
239-
}
240-
_eventListeners = {};
241-
return this;
242-
}
243-
}, {
244-
get: function(target, prop) {
245-
// If we already resolved, delegate everything
246-
if (_resolved && _selectedProvider) {
247-
var val = _selectedProvider[prop];
248-
if (typeof val === 'function') return val.bind(_selectedProvider);
249-
return val;
250-
}
251-
// Return target's own methods
252-
if (prop in target) {
253-
var v = target[prop];
254-
if (typeof v === 'function') return v.bind(target);
255-
return v;
256-
}
257-
return undefined;
258-
}
259-
});
260-
}
261-
262-
// Use Object.defineProperty to prevent wallet extensions from overwriting our Proxy.
263-
// When MetaMask/Coinbase content scripts try to set window.ethereum, we capture
264-
// their provider but keep our Proxy in control until the user selects a wallet.
265-
var _proxyProvider = getProviderOrProxy();
266-
267-
try {
268-
Object.defineProperty(window, 'ethereum', {
269-
get: function() {
270-
if (_resolved && _selectedProvider) return _selectedProvider;
271-
return _proxyProvider;
272-
},
273-
set: function(val) {
274-
if (val && !val._isQuickDappProxy) {
275-
window.__qdapp_legacy_ethereum = val;
276-
}
277-
if (_resolved) {
278-
_selectedProvider = val;
279-
}
280-
},
281-
configurable: true,
282-
enumerable: true
283-
});
284-
} catch (e) {
285-
// MetaMask (Chrome) may define window.ethereum as non-configurable before us.
286-
// Fall back to using the existing provider directly.
287-
console.warn('[QuickDapp] Cannot override window.ethereum:', e.message);
288-
if (window.ethereum) {
289-
window.__qdapp_legacy_ethereum = window.ethereum;
290-
}
291-
}
182+
// ── 4. Expose global provider getter ────────────────────────
183+
// DApp code calls: window.__qdapp_getProvider()
184+
// Returns a Promise<EIP1193Provider> — either auto-selected or user-picked.
185+
window.__qdapp_getProvider = function() {
186+
return showWalletModal();
187+
};
292188
293-
console.log('[QuickDapp] Wallet selection script loaded. Detected', _providers.length, 'EIP-6963 providers.');
189+
console.log('[QuickDapp] Wallet selection script loaded.');
294190
})();
295191
</script>`;
296192
}

0 commit comments

Comments
 (0)