Skip to content

Commit bb7e667

Browse files
committed
EIP-6963 provider discovery
1 parent 7b16afa commit bb7e667

File tree

5 files changed

+370
-6
lines changed

5 files changed

+370
-6
lines changed

apps/remix-ide/src/app/plugins/ai-dapp-generator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ export class AIDappGenerator extends Plugin {
194194

195195
if (Object.keys(pages).length === 0) {
196196
console.error('[DEBUG-AI] ❌ CRITICAL: parsePages returned empty object!');
197+
console.error('[DEBUG-AI] Raw response length:', htmlContent?.length);
198+
console.error('[DEBUG-AI] First 500 chars:', htmlContent?.substring(0, 500));
199+
console.error('[DEBUG-AI] Contains START_TITLE?', htmlContent?.includes('START_TITLE'));
200+
console.error('[DEBUG-AI] Contains <<<:', htmlContent?.includes('<<<'));
201+
const debugMatches = htmlContent?.match(/<{3,}\s*START_TITLE\s+(.*?)\s+>{3,}/g);
202+
console.error('[DEBUG-AI] Marker matches found:', debugMatches?.length || 0, debugMatches);
197203
throw new Error("AI generated empty content. Please try again.");
198204
}
199205

@@ -504,7 +510,7 @@ const ensureCompleteHtml = (html: string): string => {
504510

505511
const parsePages = (content: string) => {
506512
const pages: Record<string, string> = {}
507-
const markerRegex = /<{3,}\s*START_TITLE\s+(.*?)\s+>{3,}\s*END_TITLE/g
513+
const markerRegex = /<{3,}\s*START_TITLE\s+(.*?)\s+>{3,}(?:\s*END_TITLE)?/g
508514

509515
const parts = content.split(markerRegex)
510516

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

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ ${functionNames}
196196
},
197197

198198
/** Wallet connection and network switching patterns */
199-
wallet: (): string => `
199+
wallet: (isLocalVM: boolean = false): string => {
200+
if (isLocalVM) {
201+
return `
200202
**WALLET CONNECTION RULES:**
201203
1. **Connect Wallet** button must be visible when disconnected.
202204
2. Check \`window.ethereum\` existence before any wallet operations.
@@ -222,7 +224,70 @@ const switchNetwork = async (targetChainHex) => {
222224
}
223225
};
224226
\`\`\`
225-
`,
227+
`
228+
}
229+
230+
// Real network: full wallet selection rules with disconnect/switch/localStorage
231+
return `
232+
**WALLET CONNECTION RULES:**
233+
1. **Connect Wallet** button must be visible in the header/navbar when disconnected.
234+
2. **Disconnect Wallet** button must be visible in the header/navbar when connected (next to the account address).
235+
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.
241+
242+
**🚨 CHAIN ID COMPARISON (CRITICAL — prevents wrong-network false positive):**
243+
- ethers.js v6 returns \`network.chainId\` as a **BigInt** (e.g. \`11155111n\`).
244+
- **NEVER compare hex strings directly** (e.g. \`"0xaa36a7" !== "aa36a7"\` — prefix mismatch!).
245+
- **ALWAYS compare as decimal numbers:**
246+
\`\`\`javascript
247+
const TARGET_CHAIN_ID = 11155111; // Sepolia — use DECIMAL number
248+
// After connecting:
249+
const network = await provider.getNetwork();
250+
const currentChainId = Number(network.chainId);
251+
setChainId(currentChainId);
252+
// Wrong network check:
253+
const isWrongNetwork = account && chainId !== null && chainId !== TARGET_CHAIN_ID;
254+
\`\`\`
255+
- For \`wallet_switchEthereumChain\`, convert to hex: \`'0x' + TARGET_CHAIN_ID.toString(16)\`
256+
257+
**Disconnect Pattern (mandatory — MUST implement this):**
258+
\`\`\`javascript
259+
const disconnectWallet = () => {
260+
setAccount(null);
261+
setProvider(null);
262+
setSigner(null);
263+
// Clear saved wallet preference for wallet selection
264+
try { localStorage.removeItem('__qdapp_wallet_rdns'); } catch(e) {}
265+
};
266+
\`\`\`
267+
The Disconnect button should be placed in the navbar/header, visible when connected.
268+
When disconnected, the DApp should return to the initial "Connect Wallet" state.
269+
270+
**Network Switch Pattern (mandatory — MUST be a visible button):**
271+
\`\`\`javascript
272+
const switchNetwork = async (targetChainHex) => {
273+
try {
274+
await window.ethereum.request({
275+
method: 'wallet_switchEthereumChain',
276+
params: [{ chainId: targetChainHex }],
277+
});
278+
} catch (switchError) {
279+
if (switchError.code === 4902) {
280+
// Chain not added — prompt to add it
281+
await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [...] });
282+
} else {
283+
throw switchError;
284+
}
285+
}
286+
};
287+
\`\`\`
288+
Show a **"Switch to [Network Name]"** button when the user is on the wrong chain.
289+
`
290+
},
226291

227292
/** Ethers.js v6 specific rules */
228293
ethersRules: (): string => `
@@ -533,7 +598,7 @@ export const buildSystemPrompt = (ctx: PromptContext): string => {
533598
invariants.truncationPrevention(),
534599
// Layer 1
535600
blockchain.ethersRules(),
536-
blockchain.wallet(),
601+
blockchain.wallet(!!ctx.isLocalVM),
537602
blockchain.networkContext(ctx.contract.chainId, !!ctx.isLocalVM),
538603
// Layer 2
539604
ctx.isBaseMiniApp ? platform.baseMiniApp() : '',

libs/remix-ui/quick-dapp-v2/src/lib/components/DeployPanel/BaseAppWizard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ethers } from 'ethers';
44
import { AppContext } from '../../contexts';
55
import { readDappFiles } from '../EditHtmlTemplate';
66
import { InBrowserVite } from '../../InBrowserVite';
7+
import { generateWalletSelectionScript } from '../../utils/wallet-selection-script';
78
// remixClient removed - using plugin from context instead
89
import { trackMatomoEvent } from '@remix-api';
910

@@ -185,9 +186,10 @@ const BaseAppWizard: React.FC = () => {
185186
logoDataUrl = logo;
186187
}
187188
const injectionScript = `<script>window.__QUICK_DAPP_CONFIG__={logo:"${logoDataUrl}",title:${JSON.stringify(title || '')},details:${JSON.stringify(details || '')}};</script>`;
189+
const walletScript = generateWalletSelectionScript();
188190

189191
let modifiedHtml = indexHtmlContent;
190-
if (modifiedHtml.includes('</head>')) modifiedHtml = modifiedHtml.replace('</head>', `${injectionScript}\n</head>`);
192+
if (modifiedHtml.includes('</head>')) modifiedHtml = modifiedHtml.replace('</head>', `${walletScript}\n${injectionScript}\n</head>`);
191193
else modifiedHtml = `<html><head>${injectionScript}</head>${modifiedHtml}</html>`;
192194

193195
const inlineScript = `<script type="module">\n${jsResult.js}\n</script>`;

libs/remix-ui/quick-dapp-v2/src/lib/components/DeployPanel/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
55
import { AppContext } from '../../contexts';
66
import { readDappFiles } from '../EditHtmlTemplate';
77
import { InBrowserVite } from '../../InBrowserVite';
8+
import { generateWalletSelectionScript } from '../../utils/wallet-selection-script';
89
// remixClient removed - using plugin from context instead
910
import { trackMatomoEvent } from '@remix-api';
1011

@@ -142,9 +143,10 @@ function DeployPanel(): JSX.Element {
142143
}
143144

144145
const injectionScript = `<script>window.__QUICK_DAPP_CONFIG__={logo:"${logoDataUrl}",title:${JSON.stringify(title || '')},details:${JSON.stringify(details || '')}};</script>`;
146+
const walletScript = generateWalletSelectionScript();
145147

146148
let modifiedHtml = indexHtmlContent;
147-
if (modifiedHtml.includes('</head>')) modifiedHtml = modifiedHtml.replace('</head>', `${injectionScript}\n</head>`);
149+
if (modifiedHtml.includes('</head>')) modifiedHtml = modifiedHtml.replace('</head>', `${walletScript}\n${injectionScript}\n</head>`);
148150
else modifiedHtml = `<html><head>${injectionScript}</head>${modifiedHtml}</html>`;
149151

150152
const inlineScript = `<script type="module">\n${jsResult.js}\n</script>`;

0 commit comments

Comments
 (0)