From 3dad0899c2f9e0d2f1873f7b37fb65e24c306b8f Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 3 Jun 2025 12:13:24 +0200 Subject: [PATCH 01/22] wip: SIP-20 rewrite --- SIPS/sip-20.md | 198 ++++++++----------------------------------------- 1 file changed, 30 insertions(+), 168 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 3d76d5b8..09e8529b 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -6,184 +6,46 @@ author: David Drazic (@david0xd) created: 2023-12-15 --- -## Abstract - -This SIP proposes a new permission that enables Snaps to receive data from external sources. - -In the initial version, this SIP proposes support for WebSockets, but it can be extended to support other data sources like blockchain events in the future. -The Snap can specify the external data source in the Snap manifest. The client then connects to the external data source and sends the data to the Snap. -The Snap can then do whatever it wants with the data. This initial version only supports one-way communication from the external source to the Snap. The Snap can't send any data back to the external source. - -## Motivation - -Snaps are currently limited in their ability to receive data from external sources; they have to rely on user actions or cron jobs to fetch data, so they can't react to events in real time. Snaps also cannot use WebSocket connections to receive data from external sources, and are limited to HTTP requests. - -## Specification - -> Formal specifications are written in Typescript. - -### Language - -The key words "MUST", "MUST NOT", "SHOULD", "RECOMMENDED" and "MAY" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). - -### Snap manifest - -This SIP introduces a new permission named `endowment:external-data`. - -```typescript -/** - * A WebSocket connection as specified in the Snap manifest. - * - * @property type - The type of connection. Currently, only WebSockets are - * supported. - * @property url - The URL to connect to. This must be a valid WebSocket URL, - * starting with `wss://`. URLs starting with `ws://` are not allowed. - * @property dataType - The type of data expected from the WebSocket. If this - * field is omitted, text is assumed. - */ -type WebSocketConnection = { - type: 'websocket'; - url: string; - dataType?: 'text' | 'binary'; -}; - -/** - * An external data connection as specified in the Snap manifest. - * - * Currently, only {@link WebSocketConnection} is supported. - */ -type ExternalDataConnection = WebSocketConnection; - -/** - * External data connections as specified in the Snap manifest. - * - * This is the value of the `endowment:external-data` field in the Snap - * manifest. - * - * @property maxRequestTime - The maximum request time for the `onExternalData` - * entry point, as described in SIP-21. - * @property connections - The external data connections. - */ -type ExternalData = { - maxRequestTime?: number; - connections: ExternalDataConnection[] -}; -``` - -#### Example - -The new field can be specified as follows in a `snap.manifest.json` file: - -```json -{ - "initialPermissions": { - "endowment:external-data": { - "maxRequestTime": 50000, - "connections": [ - { - "type": "websocket", - "url": "wss://example.com/binary/endpoint", - "dataType": "binary" - }, - { - "type": "websocket", - "url": "wss://example.com/text/endpoint", - "dataType": "text" - } - ] - } - } -} -``` - -### Permission caveats - -Each caveat object MUST have `type` property that SHOULD be `websocket`. -The caveat MUST have `url` and `dataType` properties. -The `url` MUST be string. -The `dataType` MUST be string that MUST be `"binary"` or `"text"`. - -- `type` property represents the type of data source. -- `url` is the WebSocket URL. -- `dataType` is type of data that WebSocket returns. - -The caveats MUST NOT have duplicate objects with the same `url` properties. - -The `url` MUST start with `wss://` which is a protocol indicator for secure WebSocket connection. - -### Snap implementation - -This SIP introduces a new `onExternalData` function export that MAY be implemented by the Snap. The function is called every time the WebSocket client receives some data. The function SHOULD accept an object parameter that MUST have `data`, `type` and `source` properties. -The parameters represent the following: -- `data`: An object that represents the data that was received. Depending on the `data.type`, this SHOULD contain a `message` property, which SHOULD be either a `string` or an `ArrayBuffer`. -- `type`: The type of the external data source. For this SIP, this SHOULD always be `websocket`. -- `source`: The URL that the message was sent from. Snaps can use this to differentiate between different endpoints. This SHOULD be the exact same URL as specified in manifest. - -The specification of types of `onExternalData` and its parameters: - -```typescript -/** - * A text message received from a WebSocket. - * - * @property type - The type of the message. - * @property message - The message as a string. - */ -type WebSocketTextMessage = { - type: 'text'; - message: string; +- `snap_openConnection` +- `snap_closeConnection` +- `snap_sendMessage` + +```ts +export type WebSocketDataEvent = { + type: "data"; + id: string; + origin: string; + dataType: "text" | "binary"; + data: string | Uint8Array; }; -/** - * A binary message received from a WebSocket. - * - * @property type - The type of the message. - * @property message - The message as an ArrayBuffer. - */ -type WebSocketBinaryMessage = { - type: 'binary'; - message: ArrayBuffer; +export type WebSocketOpenEvent = { + type: "open"; + id: string; + origin: string; }; -/** - * A message received from a WebSocket. - * - * @property type - The type of the message. - * @property message - The message as a string or an ArrayBuffer, depending on - * the type. - */ -type WebSocketData = { - type: 'websocket'; - data: WebSocketTextMessage | WebSocketBinaryMessage; +export type WebSocketCloseEvent = { + type: "close"; + id: string; + origin: string; }; -/** - * The data received from an external source. - * - * @property type - The type of the external data source. - * @property data - The data received from the external data source. - */ -type ExternalData = WebSocketData; - -type OnExternalDataHandlerArgs = ExternalData & { - source: string; +export type WebSocketErrorEvent = { + type: "error"; + id: string; + origin: string; }; -type OnExternalDataHandler = (args: OnExternalDataHandlerArgs) => Promise; +export type WebSocketEvent = + | WebSocketDataEvent + | WebSocketOpenEvent + | WebSocketCloseEvent + | WebSocketErrorEvent; ``` -#### Example - -```typescript -import { OnExternalDataHandler } from '@metamask/snaps-types'; -import { assert } from '@metamask/utils'; - -export const onExternalData: OnExternalDataHandler = ({ type, data, source }) => { - assert(type === 'websocket'); // `data` is inferred as `WebSocketData`. - assert(data.type === 'text'); // `message` is inferred as `string`. - - // Now the Snap can do whatever is needed with the message. - const json = JSON.parse(data.message); -}; +```ts +export const onWebSocketEvent = ({ event }) => {}; ``` ## Copyright From 8b655a6f67408d5564378eb00bbc15cf776963e1 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 5 Jun 2025 14:57:32 +0200 Subject: [PATCH 02/22] Expand SIP --- SIPS/sip-20.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 9 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 09e8529b..6bacf3a1 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -2,17 +2,159 @@ sip: 20 title: External data entry point status: Draft -author: David Drazic (@david0xd) +author: David Drazic (@david0xd), Frederik Bolding (@frederikbolding), Guillaume Roux (@guillaumerx) created: 2023-12-15 +updated: 2024-06-05 --- -- `snap_openConnection` -- `snap_closeConnection` -- `snap_sendMessage` +## Abstract +This SIP proposes to expose a new communication protocol to `endowment:network-access`, that enables Snaps to communicate with external services via WebSockets. This will allow Snaps to receive real-time data updates from external sources, such as price feeds or event notifications. -```ts -export type WebSocketDataEvent = { - type: "data"; +## Motivation +Currently, Snaps can only communicate with external services via HTTP requests. This limits their ability to receive real-time data updates, which is essential for many use cases, such as price feeds or event notifications. By exposing a WebSocket protocol, Snaps can establish persistent connections with external services and receive real-time updates. + +## Specification + +> Formal specifications are written in TypeScript. + +### Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and +"OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) + +### RPC Methods + +#### `snap_openWebSocket` + +This method allows a Snap to open a WebSocket connection to an external service. The method takes a URL as a parameter and returns a unique identifier for the connection. + +```typescript + +type OpenWebSocketParams = { + url: string; +}; +``` +The RPC method takes one parameter: + +- `url` - The URL of the WebSocket service to connect to. + - The URL MUST be a valid WebSocket URL, starting with `wss://`. URLs starting with `ws://` are not allowed. + +An example of usage is given below. + +```typescript +snap.request({ + method: "snap_openWebSocket", + params: { + url: "wss://example.com/websocket", + }, +}); + +``` + +#### `snap_closeWebSocket` +This method allows a Snap to close an existing WebSocket connection. The method takes the unique identifier of the connection as a parameter. + +```typescript +type CloseWebSocketParams = { + id: string; +}; +``` +The RPC method takes one parameter: +- `id` - The unique identifier of the WebSocket connection to close. This identifier is returned by the `snap_openWebSocket` method. + +An example of usage is given below. + +```typescript +snap.request({ + method: "snap_closeWebSocket", + params: { + id: "unique-connection-id", + }, +}); +``` +#### `snap_sendMessage` +This method allows a Snap to send a message over an existing WebSocket connection. The method takes the unique identifier of the connection and the message to send as parameters. + +```typescript +type SendWebSocketMessageParams = { + id: string; + message: string | Uint8Array; +}; +``` + +The RPC method takes two parameters: +- `id` - The unique identifier of the WebSocket connection to send the message over. This identifier is returned by the `snap_openWebSocket` method. +- `message` - The message to send over the WebSocket connection. It can be either a string or a `Uint8Array`. + +An example of usage is given below. + +```typescript +snap.request({ + method: "snap_sendMessage", + params: { + id: "unique-connection-id", + message: "Hello, WebSocket!", + }, +}); +``` +#### `snap_getWebSockets` +This method allows a Snap to retrieve a list of all currently open WebSocket connections. It returns an array of objects, each containing the unique identifier and URL of the connection. + +- `id` - The unique identifier of the WebSocket connection. +- `url` - The URL of the WebSocket connection. + +```typescript +type WebSocketConnection = { + id: string; + url: string; +}; + +type GetWebSocketsResult = WebSocketConnection[]; +``` + +An example of usage is given below. + +```typescript +snap.request({ + method: "snap_getWebSockets", +}) +``` + + +### Handling WebSocket Events + +Snaps can handle WebSocket events by implementing the `onWebSocketEvent` handler. This handler will be called whenever a WebSocket event occurs, such as receiving a message, opening a connection, closing a connection, or encountering an error. + +```typescript +import { OnWebSocketEventHandler } from "@metamask/snap-sdk"; + +export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => { + switch (event.type) { + case "message": + // Handle incoming message + console.log(`Message received from ${event.origin}:`, event.data); + break; + case "open": + // Handle connection opened + console.log(`WebSocket connection opened with ID ${event.id} from ${event.origin}`); + break; + case "close": + // Handle connection closed + console.log(`WebSocket connection closed with ID ${event.id} from ${event.origin}`); + break; + case "error": + // Handle error + console.error(`WebSocket error occurred with ID ${event.id} from ${event.origin}`); + break; + } +}; +``` +the type for an `onWebSocketEvent` handler function's arguments is: + +```typescript +export type WebSocketMessageEvent = { + type: "message"; id: string; origin: string; dataType: "text" | "binary"; @@ -44,8 +186,29 @@ export type WebSocketEvent = | WebSocketErrorEvent; ``` -```ts -export const onWebSocketEvent = ({ event }) => {}; +```typescript +type OnWebSocketEventArgs = { + event: WebSocketEvent; +}; +``` +`type` - The type of the WebSocket event, which can be one of the following: + - `message` - Indicates that a message has been received. + - `open` - Indicates that a WebSocket connection has been opened. + - `close` - Indicates that a WebSocket connection has been closed. + - `error` - Indicates that an error has occurred with the WebSocket connection. + +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the Snap that is handling the WebSocket event. + +`dataType` - The type of data received in the event, which can be either `text` or `binary`. This property is only present for `message` events. + +`data` - The data received in the event. For `message` events, this can be either a string (for text messages) or a `Uint8Array` (for binary messages). For other event types, this property is not present. + +This handler does not return any value. It is used to handle WebSocket events asynchronously: + +```typescript +type OnWebSocketEventResponse = void; ``` ## Copyright From f0ca41bf5ca024dcffd0874618efc63286804ed1 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 6 Jun 2025 12:08:09 +0200 Subject: [PATCH 03/22] Apply requested changes --- SIPS/sip-20.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 6bacf3a1..02b59ad7 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -1,6 +1,6 @@ --- sip: 20 -title: External data entry point +title: WebSockets status: Draft author: David Drazic (@david0xd), Frederik Bolding (@frederikbolding), Guillaume Roux (@guillaumerx) created: 2023-12-15 @@ -23,6 +23,28 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) +### Top-level architecture + +This SIP proposes a way to defer WebSocket management to the client, allowing Snaps to open, close, and send messages over WebSocket. The client will handle the underlying WebSocket connections and notify the Snap of any events that occur on those connections. + +The client has multiple responsibilities in this architecture: +- All WebSocket connections opened by Snaps MUST be monitored by the client. Each connection is identified by a unique identifier, which is returned to the Snap when the connection is opened. + +- Any incoming message from WebSocket connections MUST be handled by the client and forwarded to the appropriate Snap through `onWebSocketEvent`. + +- When the Snap requests to open a WebSocket connection via `snap_openWebSocket`, the client MUST ensure that the connection is established and return a unique identifier for that connection. + +- Any open connection MUST be kept alive by the client until the Snap explicitly closes it or the connection is lost, even when the Snap is terminated. + +- When the Snap requests to close a WebSocket connection via `snap_closeWebSocket`, the client MUST close the connection and remove it from its list of open connections. + +- Any errors that occur during the WebSocket connection lifecycle (e.g., connection failure, message send failure) MUST be handled by the client and reported to the Snap via `onWebSocketEvent`. + + +### Snap Manifest + +This SIP doesn't introduce any new permissions, but rather extends `endowment:network-access` with new capabilities. + ### RPC Methods #### `snap_openWebSocket` @@ -39,6 +61,7 @@ The RPC method takes one parameter: - `url` - The URL of the WebSocket service to connect to. - The URL MUST be a valid WebSocket URL, starting with `wss://`. URLs starting with `ws://` are not allowed. + - Only one WebSocket connection can be opened per URL at a time. If a Snap tries to open a new connection to the same URL while an existing connection is still open, it will result in an error. An example of usage is given below. @@ -79,7 +102,7 @@ This method allows a Snap to send a message over an existing WebSocket connectio ```typescript type SendWebSocketMessageParams = { id: string; - message: string | Uint8Array; + message: string | number[]; }; ``` @@ -102,12 +125,12 @@ snap.request({ This method allows a Snap to retrieve a list of all currently open WebSocket connections. It returns an array of objects, each containing the unique identifier and URL of the connection. - `id` - The unique identifier of the WebSocket connection. -- `url` - The URL of the WebSocket connection. +- `origin` - The origin of the WebSocket connection. ```typescript type WebSocketConnection = { id: string; - url: string; + origin: string; }; type GetWebSocketsResult = WebSocketConnection[]; @@ -199,7 +222,7 @@ type OnWebSocketEventArgs = { `id` - The unique identifier of the WebSocket connection associated with the event. -`origin` - The origin of the Snap that is handling the WebSocket event. +`origin` - The origin of the WebSocket event. `dataType` - The type of data received in the event, which can be either `text` or `binary`. This property is only present for `message` events. From a4e059c85bd274a66ce4ab46324edbee7d8abedc Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 6 Jun 2025 12:15:48 +0200 Subject: [PATCH 04/22] Update SIPS/sip-20.md Co-authored-by: Frederik Bolding --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 02b59ad7..f4922f7b 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -61,7 +61,7 @@ The RPC method takes one parameter: - `url` - The URL of the WebSocket service to connect to. - The URL MUST be a valid WebSocket URL, starting with `wss://`. URLs starting with `ws://` are not allowed. - - Only one WebSocket connection can be opened per URL at a time. If a Snap tries to open a new connection to the same URL while an existing connection is still open, it will result in an error. + - Only one WebSocket connection can be opened per URL at a time. If a Snap tries to open a new connection to the same URL while an existing connection is still open, the client MUST throw an error. An example of usage is given below. From f8ea5c1b5af22c871126ca8064a4950fb54f7310 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 6 Jun 2025 12:17:21 +0200 Subject: [PATCH 05/22] update error handling sentence --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index f4922f7b..cffb227f 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -38,7 +38,7 @@ The client has multiple responsibilities in this architecture: - When the Snap requests to close a WebSocket connection via `snap_closeWebSocket`, the client MUST close the connection and remove it from its list of open connections. -- Any errors that occur during the WebSocket connection lifecycle (e.g., connection failure, message send failure) MUST be handled by the client and reported to the Snap via `onWebSocketEvent`. +- Any errors that occur when the WebSocket connection is open (e.g., connection failure, message send failure) MUST be handled by the client and reported to the Snap via `onWebSocketEvent`. ### Snap Manifest From 130aaf7e3285e9cfc78fb41ee8ee7a34e6d7fb92 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 6 Jun 2025 12:20:40 +0200 Subject: [PATCH 06/22] avoid repetition of `This SIP proposes....` --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index cffb227f..eae46533 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -25,7 +25,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", ### Top-level architecture -This SIP proposes a way to defer WebSocket management to the client, allowing Snaps to open, close, and send messages over WebSocket. The client will handle the underlying WebSocket connections and notify the Snap of any events that occur on those connections. +This architecture defers WebSocket management to the client, allowing Snaps to open, close, and send messages over WebSocket. The client will handle the underlying WebSocket connections and notify the Snap of any events that occur on those connections. The client has multiple responsibilities in this architecture: - All WebSocket connections opened by Snaps MUST be monitored by the client. Each connection is identified by a unique identifier, which is returned to the Snap when the connection is opened. From 84a402ca517f968435ca9e74d04becb5557d4f95 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:04:03 +0200 Subject: [PATCH 07/22] address requested changes --- SIPS/sip-20.md | 162 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 36 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index eae46533..6a6c827d 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -15,7 +15,7 @@ Currently, Snaps can only communicate with external services via HTTP requests. ## Specification -> Formal specifications are written in TypeScript. +> Formal specifications are written in TypeScript. ### Language @@ -25,9 +25,10 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", ### Top-level architecture -This architecture defers WebSocket management to the client, allowing Snaps to open, close, and send messages over WebSocket. The client will handle the underlying WebSocket connections and notify the Snap of any events that occur on those connections. +This architecture defers WebSocket management to the client, allowing Snaps to open, close, and send messages over WebSockets. The client will handle the underlying WebSocket connections and notify the Snap of any events that occur on those connections. The client has multiple responsibilities in this architecture: + - All WebSocket connections opened by Snaps MUST be monitored by the client. Each connection is identified by a unique identifier, which is returned to the Snap when the connection is opened. - Any incoming message from WebSocket connections MUST be handled by the client and forwarded to the appropriate Snap through `onWebSocketEvent`. @@ -55,6 +56,7 @@ This method allows a Snap to open a WebSocket connection to an external service. type OpenWebSocketParams = { url: string; + protocols?: string[]; }; ``` The RPC method takes one parameter: @@ -62,6 +64,7 @@ The RPC method takes one parameter: - `url` - The URL of the WebSocket service to connect to. - The URL MUST be a valid WebSocket URL, starting with `wss://`. URLs starting with `ws://` are not allowed. - Only one WebSocket connection can be opened per URL at a time. If a Snap tries to open a new connection to the same URL while an existing connection is still open, the client MUST throw an error. +- `protocols` - An optional array of subprotocols to use for the WebSocket connection. An example of usage is given below. @@ -70,6 +73,7 @@ snap.request({ method: "snap_openWebSocket", params: { url: "wss://example.com/websocket", + protocols: ["soap", "wamp"], }, }); @@ -96,7 +100,7 @@ snap.request({ }, }); ``` -#### `snap_sendMessage` +#### `snap_sendWebSocketMessage` This method allows a Snap to send a message over an existing WebSocket connection. The method takes the unique identifier of the connection and the message to send as parameters. ```typescript @@ -108,13 +112,13 @@ type SendWebSocketMessageParams = { The RPC method takes two parameters: - `id` - The unique identifier of the WebSocket connection to send the message over. This identifier is returned by the `snap_openWebSocket` method. -- `message` - The message to send over the WebSocket connection. It can be either a string or a `Uint8Array`. +- `message` - The message to send over the WebSocket connection. It can be either a string or a number array. An example of usage is given below. ```typescript snap.request({ - method: "snap_sendMessage", + method: "snap_sendWebSocketMessage", params: { id: "unique-connection-id", message: "Hello, WebSocket!", @@ -173,67 +177,153 @@ export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => { } }; ``` -the type for an `onWebSocketEvent` handler function's arguments is: ```typescript -export type WebSocketMessageEvent = { - type: "message"; - id: string; - origin: string; - dataType: "text" | "binary"; - data: string | Uint8Array; +type OnWebSocketEventArgs = { + event: WebSocketEvent; }; +``` +`type` - The type of the WebSocket event, which can be one of the following: + - `message` - Indicates that a message has been received. + - `open` - Indicates that a WebSocket connection has been opened. + - `close` - Indicates that a WebSocket connection has been closed. + - `error` - Indicates that an error has occurred with the WebSocket connection. -export type WebSocketOpenEvent = { - type: "open"; +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the WebSocket event. + +`dataType` - The type of data received in the event, which can be either `text` or `binary`. This property is only present for `message` events. + +`data` - The data received in the event. For `message` events, this can be either a string (for text messages) or a `Uint8Array` (for binary messages). For other event types, this property is not present. + +This handler does not return any value. + +```typescript +type OnWebSocketEventResponse = void; +``` + +#### Event Types + +the type for an `onWebSocketEvent` handler function's arguments. + +```typescript +export type WebSocketEvent = + | WebSocketMessage + | WebSocketOpenEvent + | WebSocketCloseEvent + | WebSocketErrorEvent; +``` + +##### Message Event + +This event is triggered when a message is received over a WebSocket connection. The message can be either text or binary data. + +```typescript +export type WebSocketTextMessage = { + type: "message"; id: string; origin: string; + dataType: "text"; + data: string; }; +``` -export type WebSocketCloseEvent = { - type: "close"; +`type` - The type of the WebSocket event, which is `"message"` for this event type. + +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the WebSocket event. + +`dataType` - The type of data received in the event, which is `"text"` for this event type. + +`data` - The data received in the event, which is a string for text messages. + +```typescript +export type WebSocketBinaryMessage = { + type: "message"; id: string; origin: string; + dataType: "binary"; + data: number[]; }; +``` -export type WebSocketErrorEvent = { - type: "error"; +`type` - The type of the WebSocket event, which is `"message"` for this event type. + +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the WebSocket event. + +`dataType` - The type of data received in the event, which is `"binary"` for this event type. + +`data` - The data received in the event, which is a number array for binary messages. + +```typescript +export type WebSocketMessageEvent = + | WebSocketTextMessage + | WebSocketBinaryMessage; +``` + +##### Connection opened event + +This event is triggered when a WebSocket connection is successfully opened. It provides the unique identifier of the connection and the origin of the event. + +```typescript +export type WebSocketOpenEvent = { + type: "open"; id: string; origin: string; }; - -export type WebSocketEvent = - | WebSocketDataEvent - | WebSocketOpenEvent - | WebSocketCloseEvent - | WebSocketErrorEvent; ``` +`type` - The type of the WebSocket event, which is `"open"` for this event type. + +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the WebSocket connection. + +##### Connection closed event + +This event is triggered when a WebSocket connection is closed. It provides the unique identifier of the connection and the origin of the event. ```typescript -type OnWebSocketEventArgs = { - event: WebSocketEvent; +export type WebSocketCloseEvent = { + type: "close"; + id: string; + origin: string; + code: number; + reason: string; }; ``` -`type` - The type of the WebSocket event, which can be one of the following: - - `message` - Indicates that a message has been received. - - `open` - Indicates that a WebSocket connection has been opened. - - `close` - Indicates that a WebSocket connection has been closed. - - `error` - Indicates that an error has occurred with the WebSocket connection. + +`type` - The type of the WebSocket event, which is `"close"` for this event type. `id` - The unique identifier of the WebSocket connection associated with the event. -`origin` - The origin of the WebSocket event. +`origin` - The origin of the WebSocket connection. -`dataType` - The type of data received in the event, which can be either `text` or `binary`. This property is only present for `message` events. +`code` - The numeric code indicating the reason for the closure of the WebSocket connection. This is a standard WebSocket close code. -`data` - The data received in the event. For `message` events, this can be either a string (for text messages) or a `Uint8Array` (for binary messages). For other event types, this property is not present. +`reason` - A string providing a human-readable explanation of why the WebSocket connection was closed. -This handler does not return any value. It is used to handle WebSocket events asynchronously: +##### Error event + +This event is triggered when an error occurs with a WebSocket connection. It provides the unique identifier of the connection, and the origin of the event. ```typescript -type OnWebSocketEventResponse = void; +export type WebSocketErrorEvent = { + type: "error"; + id: string; + origin: string; +}; ``` +`type` - The type of the WebSocket event, which is `"error"` for this event type. + +`id` - The unique identifier of the WebSocket connection associated with the event. + +`origin` - The origin of the WebSocket connection where the error occurred. + ## Copyright Copyright and related rights waived via [CC0](../LICENSE). From bd73571af205c6b659e8ca4d20c94993c390a697 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:08:12 +0200 Subject: [PATCH 08/22] update data object --- SIPS/sip-20.md | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 6a6c827d..cab0ddef 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -220,32 +220,24 @@ export type WebSocketEvent = This event is triggered when a message is received over a WebSocket connection. The message can be either text or binary data. ```typescript -export type WebSocketTextMessage = { - type: "message"; - id: string; - origin: string; +export type WebSocketTextMessageData = { dataType: "text"; data: string; }; -``` - -`type` - The type of the WebSocket event, which is `"message"` for this event type. - -`id` - The unique identifier of the WebSocket connection associated with the event. - -`origin` - The origin of the WebSocket event. - -`dataType` - The type of data received in the event, which is `"text"` for this event type. +export type WebSocketBinaryMessageData = { + dataType: "binary"; + data: number[]; +}; -`data` - The data received in the event, which is a string for text messages. +export type WebSocketMessageData = + | WebSocketTextMessageData + | WebSocketBinaryMessageData; -```typescript -export type WebSocketBinaryMessage = { +export type WebSocketMessage = { type: "message"; id: string; origin: string; - dataType: "binary"; - data: number[]; + data: WebSocketMessageData; }; ``` @@ -255,15 +247,7 @@ export type WebSocketBinaryMessage = { `origin` - The origin of the WebSocket event. -`dataType` - The type of data received in the event, which is `"binary"` for this event type. - -`data` - The data received in the event, which is a number array for binary messages. - -```typescript -export type WebSocketMessageEvent = - | WebSocketTextMessage - | WebSocketBinaryMessage; -``` +`data` - The data received in the event. This can be either a text message (as a string) or binary data (as an array of numbers). The `type` property indicates whether the data is text or binary. ##### Connection opened event From 0d666bfc3d5626e51a5c9ee5fb7a357e6e7a6608 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:09:29 +0200 Subject: [PATCH 09/22] correct naming --- SIPS/sip-20.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index cab0ddef..76ab4dc7 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -220,18 +220,18 @@ export type WebSocketEvent = This event is triggered when a message is received over a WebSocket connection. The message can be either text or binary data. ```typescript -export type WebSocketTextMessageData = { - dataType: "text"; - data: string; +export type WebSocketTextMessage = { + type: "text"; + message: string; }; -export type WebSocketBinaryMessageData = { - dataType: "binary"; - data: number[]; +export type WebSocketBinaryMessage = { + type: "binary"; + message: number[]; }; export type WebSocketMessageData = - | WebSocketTextMessageData - | WebSocketBinaryMessageData; + | WebSocketTextMessage + | WebSocketBinaryMessage; export type WebSocketMessage = { type: "message"; From a5bf604644e3585e0b952a991e15615d29a0a179 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:12:51 +0200 Subject: [PATCH 10/22] remove event properties description --- SIPS/sip-20.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 76ab4dc7..522820b9 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -183,21 +183,6 @@ type OnWebSocketEventArgs = { event: WebSocketEvent; }; ``` -`type` - The type of the WebSocket event, which can be one of the following: - - `message` - Indicates that a message has been received. - - `open` - Indicates that a WebSocket connection has been opened. - - `close` - Indicates that a WebSocket connection has been closed. - - `error` - Indicates that an error has occurred with the WebSocket connection. - -`id` - The unique identifier of the WebSocket connection associated with the event. - -`origin` - The origin of the WebSocket event. - -`dataType` - The type of data received in the event, which can be either `text` or `binary`. This property is only present for `message` events. - -`data` - The data received in the event. For `message` events, this can be either a string (for text messages) or a `Uint8Array` (for binary messages). For other event types, this property is not present. - -This handler does not return any value. ```typescript type OnWebSocketEventResponse = void; From 585f10a1ec424979ba5ce58cbd906551779c344c Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:16:21 +0200 Subject: [PATCH 11/22] Update SIPS/sip-20.md Co-authored-by: Frederik Bolding --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 522820b9..f173509f 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -112,7 +112,7 @@ type SendWebSocketMessageParams = { The RPC method takes two parameters: - `id` - The unique identifier of the WebSocket connection to send the message over. This identifier is returned by the `snap_openWebSocket` method. -- `message` - The message to send over the WebSocket connection. It can be either a string or a number array. +- `message` - The message to send over the WebSocket connection. It can be either a string or an array of bytes. An example of usage is given below. From 726fbb5b541c6de38e8cf34a71e089ac13999363 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:16:38 +0200 Subject: [PATCH 12/22] Update SIPS/sip-20.md Co-authored-by: Frederik Bolding --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index f173509f..cecc8dcc 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -11,7 +11,7 @@ updated: 2024-06-05 This SIP proposes to expose a new communication protocol to `endowment:network-access`, that enables Snaps to communicate with external services via WebSockets. This will allow Snaps to receive real-time data updates from external sources, such as price feeds or event notifications. ## Motivation -Currently, Snaps can only communicate with external services via HTTP requests. This limits their ability to receive real-time data updates, which is essential for many use cases, such as price feeds or event notifications. By exposing a WebSocket protocol, Snaps can establish persistent connections with external services and receive real-time updates. +Currently, Snaps can only communicate with external services via HTTP requests. This limits their ability to receive real-time data updates, which is essential for many use cases, such as price feeds or event notifications. By exposing the WebSocket protocol, Snaps can establish persistent connections with external services and receive real-time updates. ## Specification From 88ed602a48efff5cdad16d7614dd20bbdf6af03b Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:18:31 +0200 Subject: [PATCH 13/22] use `url` instead of `origin` --- SIPS/sip-20.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index cecc8dcc..6cea999e 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -129,12 +129,12 @@ snap.request({ This method allows a Snap to retrieve a list of all currently open WebSocket connections. It returns an array of objects, each containing the unique identifier and URL of the connection. - `id` - The unique identifier of the WebSocket connection. -- `origin` - The origin of the WebSocket connection. +- `url` - The URL of the WebSocket connection. ```typescript type WebSocketConnection = { id: string; - origin: string; + url: string; }; type GetWebSocketsResult = WebSocketConnection[]; From 186e4c29048877f6ab91037d444e6fcd639965e8 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 10 Jun 2025 15:26:11 +0200 Subject: [PATCH 14/22] add `wasClean` to close event --- SIPS/sip-20.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 6cea999e..f34c1a0e 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -262,6 +262,7 @@ export type WebSocketCloseEvent = { origin: string; code: number; reason: string; + wasClean: boolean; }; ``` @@ -275,6 +276,8 @@ export type WebSocketCloseEvent = { `reason` - A string providing a human-readable explanation of why the WebSocket connection was closed. +`wasClean` - A boolean indicating whether the connection was closed cleanly (i.e., without any errors). + ##### Error event This event is triggered when an error occurs with a WebSocket connection. It provides the unique identifier of the connection, and the origin of the event. From 1525b9373b4c2361e9995c82bea1277db257dd42 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 11 Jun 2025 14:34:31 +0200 Subject: [PATCH 15/22] Update SIPS/sip-20.md Co-authored-by: Daniel Rocha <68558152+danroc@users.noreply.github.com> --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index f34c1a0e..afa686cf 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -4,7 +4,7 @@ title: WebSockets status: Draft author: David Drazic (@david0xd), Frederik Bolding (@frederikbolding), Guillaume Roux (@guillaumerx) created: 2023-12-15 -updated: 2024-06-05 +updated: 2025-06-05 --- ## Abstract From e56dd584c74fe2e3473bc2988572a9b9b4a5a53c Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 12 Jun 2025 13:07:41 +0200 Subject: [PATCH 16/22] Remove error event --- SIPS/sip-20.md | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index afa686cf..58103de9 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -151,7 +151,7 @@ snap.request({ ### Handling WebSocket Events -Snaps can handle WebSocket events by implementing the `onWebSocketEvent` handler. This handler will be called whenever a WebSocket event occurs, such as receiving a message, opening a connection, closing a connection, or encountering an error. +Snaps can handle WebSocket events by implementing the `onWebSocketEvent` handler. This handler will be called whenever a WebSocket event occurs, such as receiving a message, opening a connection or closing a connection. ```typescript import { OnWebSocketEventHandler } from "@metamask/snap-sdk"; @@ -170,10 +170,6 @@ export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => { // Handle connection closed console.log(`WebSocket connection closed with ID ${event.id} from ${event.origin}`); break; - case "error": - // Handle error - console.error(`WebSocket error occurred with ID ${event.id} from ${event.origin}`); - break; } }; ``` @@ -196,8 +192,7 @@ the type for an `onWebSocketEvent` handler function's arguments. export type WebSocketEvent = | WebSocketMessage | WebSocketOpenEvent - | WebSocketCloseEvent - | WebSocketErrorEvent; + | WebSocketCloseEvent; ``` ##### Message Event @@ -278,24 +273,6 @@ export type WebSocketCloseEvent = { `wasClean` - A boolean indicating whether the connection was closed cleanly (i.e., without any errors). -##### Error event - -This event is triggered when an error occurs with a WebSocket connection. It provides the unique identifier of the connection, and the origin of the event. - -```typescript -export type WebSocketErrorEvent = { - type: "error"; - id: string; - origin: string; -}; -``` - -`type` - The type of the WebSocket event, which is `"error"` for this event type. - -`id` - The unique identifier of the WebSocket connection associated with the event. - -`origin` - The origin of the WebSocket connection where the error occurred. - ## Copyright Copyright and related rights waived via [CC0](../LICENSE). From ef5cd6e279819282e9c02a61367f5df6b6767e46 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Thu, 12 Jun 2025 14:48:08 +0200 Subject: [PATCH 17/22] Update wording around open connections --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 58103de9..a1c20ef2 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -35,7 +35,7 @@ The client has multiple responsibilities in this architecture: - When the Snap requests to open a WebSocket connection via `snap_openWebSocket`, the client MUST ensure that the connection is established and return a unique identifier for that connection. -- Any open connection MUST be kept alive by the client until the Snap explicitly closes it or the connection is lost, even when the Snap is terminated. +- Any open connection MUST be kept alive by the client until the Snap explicitly closes it, the connection is lost or the client is shut down, even while the Snap isn't running. - When the Snap requests to close a WebSocket connection via `snap_closeWebSocket`, the client MUST close the connection and remove it from its list of open connections. From 10f827063740be7d8115af5ef64f154d8588f4b8 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 13 Jun 2025 12:37:21 +0200 Subject: [PATCH 18/22] Apply suggestions from code review Co-authored-by: Maarten Zuidhoorn --- SIPS/sip-20.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index a1c20ef2..7dce9a14 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -59,7 +59,7 @@ type OpenWebSocketParams = { protocols?: string[]; }; ``` -The RPC method takes one parameter: +The RPC method takes two parameters: - `url` - The URL of the WebSocket service to connect to. - The URL MUST be a valid WebSocket URL, starting with `wss://`. URLs starting with `ws://` are not allowed. @@ -186,7 +186,7 @@ type OnWebSocketEventResponse = void; #### Event Types -the type for an `onWebSocketEvent` handler function's arguments. +The type for the `onWebSocketEvent` handler function's arguments. ```typescript export type WebSocketEvent = @@ -204,6 +204,7 @@ export type WebSocketTextMessage = { type: "text"; message: string; }; + export type WebSocketBinaryMessage = { type: "binary"; message: number[]; From 8c20671c479669efff0dd43e1b9e7544521289ca Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 13 Jun 2025 12:39:10 +0200 Subject: [PATCH 19/22] Update sip-20.md --- SIPS/sip-20.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 7dce9a14..a21a3dd3 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -135,6 +135,7 @@ This method allows a Snap to retrieve a list of all currently open WebSocket con type WebSocketConnection = { id: string; url: string; + protocols?: string[]; }; type GetWebSocketsResult = WebSocketConnection[]; @@ -168,7 +169,7 @@ export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => { break; case "close": // Handle connection closed - console.log(`WebSocket connection closed with ID ${event.id} from ${event.origin}`); + console.log(`WebSocket connection closed with ID ${event.id} code ${event.code} and reason ${event.reason} from ${event.origin}`); break; } }; From 639dcf8962113dc211c749bbdb2b2e3a33ab2d5a Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 13 Jun 2025 12:48:13 +0200 Subject: [PATCH 20/22] Update SIPS/sip-20.md --- SIPS/sip-20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index a21a3dd3..5a332195 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -126,7 +126,7 @@ snap.request({ }); ``` #### `snap_getWebSockets` -This method allows a Snap to retrieve a list of all currently open WebSocket connections. It returns an array of objects, each containing the unique identifier and URL of the connection. +This method allows a Snap to retrieve a list of all currently open WebSocket connections. It returns an array of objects, each containing the unique identifier, the optional protocols and the URL of the connection. - `id` - The unique identifier of the WebSocket connection. - `url` - The URL of the WebSocket connection. From 35655e1d2dd5f08cbe86ba49d56c30a38ec4d284 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 13 Jun 2025 13:00:08 +0200 Subject: [PATCH 21/22] Update SIPS/sip-20.md Co-authored-by: Maarten Zuidhoorn --- SIPS/sip-20.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index 5a332195..b66a1d04 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -130,6 +130,7 @@ This method allows a Snap to retrieve a list of all currently open WebSocket con - `id` - The unique identifier of the WebSocket connection. - `url` - The URL of the WebSocket connection. +- `protocols` - The optional protocols of the WebSocket connection. ```typescript type WebSocketConnection = { From 3c8ab9ec62286596d4e571102d1f4f1f9efbb0c6 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 13 Jun 2025 14:10:22 +0200 Subject: [PATCH 22/22] Update sip-20.md --- SIPS/sip-20.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SIPS/sip-20.md b/SIPS/sip-20.md index b66a1d04..d9da2dda 100644 --- a/SIPS/sip-20.md +++ b/SIPS/sip-20.md @@ -259,8 +259,8 @@ export type WebSocketCloseEvent = { id: string; origin: string; code: number; - reason: string; - wasClean: boolean; + reason: string | null; + wasClean: boolean | null; }; ```