Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 131 additions & 3 deletions EIPS/eip-7039.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,31 @@ A request without a transferred reply port SHALL NOT be considered an error, eve

### Icon Images

<!-- TODO -->
Wallets and pages MAY display an icon for a provider using the `icon` URI from `ProviderInfo`. To ensure interoperability and security, the following rules apply:

- The `icon` value MUST be a URI string.
- The URI scheme MUST be one of: `https:` or `data:`. User agents MAY additionally support implementation-specific extension schemes (e.g., browser extension origins) but SHOULD NOT rely on them for cross-implementation compatibility.
- When using `https:`:
- The resource MUST be served over TLS with a valid certificate.
- The response MUST include a correct `Content-Type` (e.g., `image/png`, `image/webp`, or `image/svg+xml`).
- Implementations SHOULD set cache headers to allow safe caching and reduce network load.
- When using `data:`:
- The media type MUST be `image/png`, `image/webp`, or `image/svg+xml`.
- Implementations SHOULD prefer `data:` URIs only for small icons (e.g., ≤ 10 KiB) to avoid bloating messages.
- Format and dimensions:
- The icon SHOULD be square.
- The icon SHOULD be at least 64×64 CSS pixels, and it is RECOMMENDED to provide 128×128 or higher for HiDPI displays.
- Implementations SHOULD render the icon within a square viewport and MAY downscale larger images.
- Transparent backgrounds are RECOMMENDED.
- SVG-specific requirements:
- SVG icons MUST NOT contain scripts, external resource references, or event handlers, and MUST be sanitized before rendering.
- User agents SHOULD reject SVGs that reference remote resources (fonts, images) or contain potentially executable content.
- Privacy and tracking:
- Icon URLs SHOULD be stable and MUST NOT contain per-user or per-request identifiers.
- Implementations SHOULD avoid initiating third-party network requests solely to render an icon; `data:` URIs MAY be used to mitigate cross-origin fetches when appropriate.
- Security:
- Consumers MUST validate that the reported media type matches the actual content and SHOULD cap the maximum icon payload size.
- Mixed content MUST be avoided; `http:` icons MUST NOT be used in secure contexts.

## Rationale

Expand All @@ -88,11 +112,115 @@ While not backwards compatible with EIP-1193, this proposal uses extremely simil

It is possible to implement an EIP-1193 compatible provider using this proposal like so:

<!-- TODO: Show example of implementing EIP-1193 provider on top of this proposal. -->
```typescript
// Minimal EIP-1193 provider built on SHADOW (web page side)
// See: https://eips.ethereum.org/EIPS/eip-1193

type RequestArguments = {
method: string;
params?: unknown[] | object;
};

type ProviderRpcError = {
code: number;
message: string;
data?: unknown;
};

class ShadowProvider {
private primaryPort: MessagePort | null = null;
private iframe: HTMLIFrameElement | null = null;

async connect(): Promise<void> {
if (this.primaryPort) return;

const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'web+evm://';

const primaryPort = await new Promise<MessagePort>((resolve, reject) => {
const onMessage = (event: MessageEvent) => {
if (event.source !== iframe.contentWindow) return;
// Expect the first message to transfer a port for primary communication
const [port] = event.ports || [];
if (!port) return;
window.removeEventListener('message', onMessage);
resolve(port);
};
window.addEventListener('message', onMessage);
// Safety timeout in case no wallet responds
setTimeout(() => reject(new Error('Wallet did not respond')), 10_000);
});

document.body.appendChild(iframe);
this.iframe = iframe;
this.primaryPort = primaryPort;
this.primaryPort.start();
}

async request<T = unknown>(args: RequestArguments): Promise<T> {
if (!this.primaryPort) throw new Error('Not connected');

const channel = new MessageChannel();
const { port1, port2 } = channel;

const response = await new Promise<{ result?: T; error?: ProviderRpcError }>((resolve) => {
const onMessage = (event: MessageEvent) => {
port1.removeEventListener('message', onMessage as EventListener);
port1.close();
resolve(event.data);
};
port1.addEventListener('message', onMessage as EventListener);
port1.start();
this.primaryPort!.postMessage(args, [port2]);
});

if (response.error) {
const { code, message } = response.error;
const err = new Error(message) as Error & { code?: number };
err.code = code;
throw err;
}
return response.result as T;
}
}

// Usage example
(async () => {
const provider = new ShadowProvider();
await provider.connect();
// Example JSON-RPC call via EIP-1193 semantics
const chainId = await provider.request<string>({ method: 'eth_chainId' });
console.log('chainId', chainId);
})();
```

## Security Considerations

<!-- TODO: Needs more discussion. -->
The following non-exhaustive list outlines key security requirements and recommendations:

- Message origin and source validation:
- Web pages MUST verify that the initial `message` event's `source` equals the created `iframe`'s `contentWindow` before trusting any transferred port.
- Where an origin is available, implementations SHOULD verify that it matches an expected wallet origin; wallets served from `https:` origins are RECOMMENDED.
- Parties MUST ignore messages from unexpected sources and MUST treat unknown messages as untrusted data.
- Use of `MessagePort`:
- Only the transferred primary port SHOULD be used for subsequent requests; additional unsolicited ports MUST be ignored.
- Implementations SHOULD bound the lifetime of reply ports and MUST post at most one response message per request.
- Permissioning and user consent:
- Wallets MUST gate sensitive methods (e.g., transaction signing, key export) behind explicit user consent and per-origin permissions, consistent with EIP-1193 expectations.
- Wallets SHOULD present confirmation UX outside the embedded `iframe` context to reduce clickjacking risk.
- Input validation and rate limiting:
- Responders MUST validate JSON-RPC method names and parameters and SHOULD apply size limits and schema checks.
- Implementations SHOULD enforce per-origin rate limits and timeouts to mitigate denial-of-service.
- Icon handling security:
- Icons MUST be loaded over secure transports; `http:` MUST NOT be used in secure contexts.
- SVG icons MUST be sanitized and MUST NOT execute scripts or reference external resources.
- Implementations SHOULD cap icon file size and dimensions.
- Privacy:
- Implementations SHOULD avoid leaking browsing context information via icon URLs or request headers and SHOULD favor stable, cacheable URLs or `data:` URIs.
- Wallets SHOULD avoid embedding third-party content within the transport `iframe`.
- Content Security Policy (CSP):
- Pages and wallets SHOULD configure CSP such that only intended image and messaging endpoints are permitted (e.g., `img-src` and `connect-src`).

Both providers and web pages MUST verify the origin of messages before trusting them.

Expand Down