Skip to content
Closed
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
67 changes: 59 additions & 8 deletions SIPS/sip-20.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sip: 20
title: External data entry point
title: Advanced external communication and events
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like the name is not reflecting what this SIP really is anymore? Looking for advice to change it if possible. Updated with one of the ideas I had.

status: Draft
author: David Drazic (@david0xd)
created: 2023-12-15
Expand All @@ -12,11 +12,11 @@ This SIP proposes a new permission that enables Snaps to receive data from exter

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.
The Snap can then do whatever it wants with the data, and can send data back to the external source if supported (e.g., a WebSocket connection).

## 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.
Snaps are currently limited in their ability to communicate with 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 for bidirectional communication, and are limited to HTTP requests.

## Specification

Expand Down Expand Up @@ -111,6 +111,33 @@ 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.

### RPC Methods

This SIP introduces a new RPC method for sending data to the external connections (if supported).

#### snap_sendData

Snap can use `snap_sendData` RPC method to send data specified within request params.
The proposed RPC method `snap_sendData` SHOULD be a restricted RPC method. The RPC method SHOULD only be available to Snaps.
Snap MUST specify destination which is URL identifier of an external connection (e.g. websocket). Destination MUST be exact match of some of the URLs defined in manifest's permission caveats within `endowment:external-data`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory the same URL can be used twice, in which case it would be ambiguous which one to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we disallow that in our validation steps or you think we need to design it in a different way? 🤔


The `snap_sendData` JSON-RPC method takes an object as parameters, which has `data` and `destination` properties.

Example:
```json
{
"method": "snap_sendData",
"params": {
"data": {
"foo": "bar"
},
"destination": "wss://example.com/text/endpoint"
}
}
```

The method returns a `boolean`, `true` in case of successful delivery of a message to the WebSocket. If delivery was unsuccessful, error is thrown.

### 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.
Expand All @@ -123,7 +150,7 @@ The specification of types of `onExternalData` and its parameters:

```typescript
/**
* A text message received from a WebSocket.
* WebSocket text message.
*
* @property type - The type of the message.
* @property message - The message as a string.
Expand All @@ -134,7 +161,7 @@ type WebSocketTextMessage = {
};

/**
* A binary message received from a WebSocket.
* WebSocket binary message.
*
* @property type - The type of the message.
* @property message - The message as an ArrayBuffer.
Expand All @@ -144,6 +171,21 @@ type WebSocketBinaryMessage = {
message: ArrayBuffer;
};

/**
* Incoming Websocket message.
*
*/
type WebSocketIncomingMessage = WebSocketTextMessage | WebSocketBinaryMessage;

/**
* Outgoing Websocket message.
*
* @property destination - The destination web socket URL as a string.
*/
type WebSocketOutgoingMessage = WebSocketIncomingMessage & {
destination: string;
};

/**
* A message received from a WebSocket.
*
Expand All @@ -153,7 +195,7 @@ type WebSocketBinaryMessage = {
*/
type WebSocketData = {
type: 'websocket';
data: WebSocketTextMessage | WebSocketBinaryMessage;
message: WebSocketIncomingMessage;
};

/**
Expand All @@ -177,12 +219,21 @@ type OnExternalDataHandler = (args: OnExternalDataHandlerArgs) => Promise<void>;
import { OnExternalDataHandler } from '@metamask/snaps-types';
import { assert } from '@metamask/utils';

export const onExternalData: OnExternalDataHandler = ({ type, data, source }) => {
export const onExternalData: OnExternalDataHandler = async ({ 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);

// Send message back
await snap.request({
method: 'snap_sendData',
params: {
type: 'text',
message: 'Message received successfully',
destination: 'wss://example.com/text/endpoint',
},
});
};
```

Expand Down
Loading