From 4c4d1f61f185a5c81fa0f3370b2d95f13fbea1af Mon Sep 17 00:00:00 2001 From: 0xpantera <0xpantera@proton.me> Date: Fri, 19 Jun 2026 15:48:54 +0200 Subject: [PATCH] Add EIP-6963 provider discovery --- docs/browser-bridge.md | 10 +++- extension/injected.js | 27 +++++++++ .../browser-bridge-extension.spec.ts | 55 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/browser-bridge.md b/docs/browser-bridge.md index b40f37a..a3a545b 100644 --- a/docs/browser-bridge.md +++ b/docs/browser-bridge.md @@ -74,6 +74,15 @@ Unsupported methods return an EIP-1193-style error object with code `4200`. The injected provider also exposes `isConnected()`, `on`, and `removeListener` with a minimal event surface for `accountsChanged`, `chainChanged`, `connect`, and `disconnect`. +## Provider discovery + +The injected provider supports EIP-6963 multi-wallet discovery in addition to the legacy +`window.ethereum` path: + +- announces `eip6963:announceProvider` on page load +- re-announces when the dapp dispatches `eip6963:requestProvider` +- uses `name: "Deckard"`, `rdns: "com.deckard.wallet"`, and a self-contained SVG data URI icon + `personal_sign`, `eth_sendTransaction`, broad signing, hardware wallets, native messaging, and store distribution are intentionally not implemented in this bridge slice. Kohaku remains useful for wallet-internal provider/backend integration, not as the browser-facing dapp provider. @@ -195,7 +204,6 @@ Open in the browser where the unpacked extension is loa - Decide native messaging vs hardened localhost using the PRD-04 spike evidence. - Add a real approval UI for `eth_requestAccounts` and per-origin revocation. -- Add EIP-6963 provider announcement. - Persist permissions safely. - Consider a small integration test for the loopback `/rpc` HTTP boundary. - Add clear-signing/message-signing only after Deckard has the reviewed intent model for it. diff --git a/extension/injected.js b/extension/injected.js index b95bfab..fc499e4 100644 --- a/extension/injected.js +++ b/extension/injected.js @@ -2,6 +2,19 @@ let nextId = 1; const pending = new Map(); const listeners = new Map(); + const deckardIconSvg = [ + '', + '', + '', + '', + '', + ].join(''); + const deckardProviderInfo = Object.freeze({ + uuid: '3f2e4f7c-5e49-4d7d-8e2c-0d9a7c4f1193', + name: 'Deckard', + icon: `data:image/svg+xml,${encodeURIComponent(deckardIconSvg)}`, + rdns: 'com.deckard.wallet', + }); class DeckardProvider { constructor() { @@ -81,6 +94,18 @@ } const provider = new DeckardProvider(); + const providerDetail = Object.freeze({ + info: deckardProviderInfo, + provider, + }); + + function announceProvider() { + window.dispatchEvent( + new CustomEvent('eip6963:announceProvider', { + detail: providerDetail, + }), + ); + } window.addEventListener('message', (event) => { if (event.source !== window) return; @@ -136,5 +161,7 @@ configurable: true, }); + window.addEventListener('eip6963:requestProvider', announceProvider); + announceProvider(); window.dispatchEvent(new Event('ethereum#initialized')); })(); diff --git a/tests/extension/browser-bridge-extension.spec.ts b/tests/extension/browser-bridge-extension.spec.ts index 2b041c2..a52826e 100644 --- a/tests/extension/browser-bridge-extension.spec.ts +++ b/tests/extension/browser-bridge-extension.spec.ts @@ -80,6 +80,61 @@ test('local dapp can connect through the injected provider', async ({ page }, te }); }); +test('local dapp can discover Deckard through EIP-6963', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('#output')).toContainText('window.ethereum detected'); + + const discoveryState = await page.evaluate(async () => { + type Eip6963ProviderDetail = { + info: { + uuid: string; + name: string; + icon: string; + rdns: string; + }; + provider: NonNullable; + }; + + const announcedProviders: Eip6963ProviderDetail[] = []; + window.addEventListener('eip6963:announceProvider', (event) => { + announcedProviders.push((event as CustomEvent).detail); + }); + window.dispatchEvent(new Event('eip6963:requestProvider')); + await new Promise((resolve) => setTimeout(resolve, 0)); + + const detail = announcedProviders.find((providerDetail) => ( + providerDetail.info.rdns === 'com.deckard.wallet' + )); + if (!detail) { + throw new Error('Deckard EIP-6963 provider announcement missing'); + } + + const chainId = await detail.provider.request({ method: 'eth_chainId' }); + return { + announcementCount: announcedProviders.length, + detailFrozen: Object.isFrozen(detail), + infoFrozen: Object.isFrozen(detail.info), + sameProvider: detail.provider === window.ethereum, + chainId, + info: detail.info, + }; + }); + + expect(discoveryState).toEqual({ + announcementCount: 1, + detailFrozen: true, + infoFrozen: true, + sameProvider: true, + chainId: '0xaa36a7', + info: { + uuid: '3f2e4f7c-5e49-4d7d-8e2c-0d9a7c4f1193', + name: 'Deckard', + icon: expect.stringMatching(/^data:image\/svg\+xml,/), + rdns: 'com.deckard.wallet', + }, + }); +}); + declare global { interface Window { ethereum?: {