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?: {