Skip to content

Commit

Permalink
Add SIP-20 (External Data and Events) (#125)
Browse files Browse the repository at this point in the history
* Add SIP-20

* Rework according to review requests

* Update related to review request

* Add minor reformatting and refactoring

* Add minor refactoring and modifications

* Fix ExternalData type

* Fix ExternalData type (2)
david0xd authored Feb 19, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent d730ab2 commit a5ef71d
Showing 1 changed file with 191 additions and 0 deletions.
191 changes: 191 additions & 0 deletions SIPS/sip-20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
sip: 20
title: External data entry point
status: Draft
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;
};

/**
* 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;
};

/**
* 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;
};

/**
* 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;
};

type OnExternalDataHandler = (args: OnExternalDataHandlerArgs) => Promise<void>;
```

#### 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);
};
```

## Copyright

Copyright and related rights waived via [CC0](../LICENSE).

0 comments on commit a5ef71d

Please sign in to comment.