73 | {% comment %}
74 | Article schema specification:
75 | https://schema.org/TechArticle
76 | {% endcomment %}
77 |
94 |
--------------------------------------------------------------------------------
/tools/validate/src/rules/preamble-data.ts:
--------------------------------------------------------------------------------
1 | import Debug from "debug";
2 | import { Root } from "mdast";
3 | import {
4 | enums,
5 | number,
6 | object,
7 | optional,
8 | refine,
9 | string,
10 | Struct,
11 | validate,
12 | } from "superstruct";
13 | import { lintRule } from "unified-lint-rule";
14 | import { select } from "unist-util-select";
15 | import { ParsedYaml } from "../plugins/parseYaml.js";
16 | import { isValidISO8601Date, isValidURL, lowercaseFirst } from "../utils.js";
17 |
18 | const debug = Debug("validate:rule:preamble");
19 |
20 | const url = (protocols?: string[]) =>
21 | refine(
22 | string(),
23 | "url",
24 | (value) =>
25 | isValidURL(value, protocols) || `not a valid ${protocols?.join("/")} url`
26 | );
27 | const date = () =>
28 | refine(
29 | string(),
30 | "date",
31 | (value) => isValidISO8601Date(value) || "not a valid iso-8601 date"
32 | );
33 | const positive = (struct: Struct) =>
34 | refine(struct, "positive", (value) => value > 0 || "not a positive number");
35 | const integer = () =>
36 | refine(
37 | number(),
38 | "integer",
39 | (value) => Number.isSafeInteger(value) || "not an integer"
40 | );
41 |
42 | const GITHUB_USERNAME = String.raw`[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}`;
43 | const EMAIL = String.raw`.+@.+`;
44 | const AUTHOR = new RegExp(
45 | String.raw`^\w[.\w\s]*(?: (?:\<${EMAIL}\>(?: \((\@${GITHUB_USERNAME})\))?)|\((\@${GITHUB_USERNAME})\))?$`
46 | );
47 | const author = () =>
48 | refine(string(), "author", (value) => {
49 | const authors: string[] = value.split(",");
50 | let hasGithub = false;
51 | for (const author of authors) {
52 | const match = author.trim().match(AUTHOR);
53 | if (match === null) {
54 | return "is malformed";
55 | }
56 | if (match[1] !== undefined || match[2] !== undefined) {
57 | hasGithub = true;
58 | }
59 | }
60 | return hasGithub || "doesn't have at least one GitHub account";
61 | });
62 |
63 | export const Preamble = refine(
64 | object({
65 | sip: positive(integer()),
66 | title: string(),
67 | status: enums([
68 | "Draft",
69 | "Review",
70 | "Implementation",
71 | "Final",
72 | "Withdrawn",
73 | "Living",
74 | ]),
75 | "discussions-to": optional(url(["http", "https"])),
76 | author: author(),
77 | created: date(),
78 | updated: optional(date()),
79 | }),
80 | "preamble",
81 | (value) => {
82 | const errors = [];
83 |
84 | if (
85 | value.updated !== undefined &&
86 | new Date(value.updated) < new Date(value.created)
87 | ) {
88 | errors.push('property "updated" is earlier than "created"');
89 | }
90 |
91 | if (value.status === "Living" && value.updated === undefined) {
92 | errors.push(
93 | 'has status of "Living" but doesn\'t have "updated" preamble header'
94 | );
95 | }
96 | return errors.length === 0 || errors;
97 | }
98 | );
99 |
100 | const rule = lintRule("sip:preamble-data", (tree, file) => {
101 | const node = select("parsedYaml", tree) as ParsedYaml | null;
102 | if (!node) {
103 | return;
104 | }
105 | const [error] = validate(node.data.parsed, Preamble);
106 |
107 | if (error !== undefined) {
108 | const failures = error.failures();
109 | debug(failures);
110 | failures.forEach((failure) => {
111 | const prop = failure.path.at(-1);
112 | let msg;
113 | if (prop === undefined) {
114 | msg = `Front-matter ${lowercaseFirst(failure.message)}`;
115 | } else {
116 | msg = `Front-matter property "${prop}" ${lowercaseFirst(
117 | failure.message
118 | )}`;
119 | }
120 | file.message(msg, node);
121 | });
122 | }
123 | });
124 |
125 | export default rule;
126 |
--------------------------------------------------------------------------------
/SIPS/sip-8.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 8
3 | title: Snap Locations
4 | status: Implementation
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/75
6 | author: Olaf Tomalka (@ritave)
7 | created: 2022-11-03
8 | ---
9 |
10 | ## Abstract
11 |
12 | A specification of URIs the DApp can use under which the wallet looks for snaps.
13 | It describes location URIs which can be used to locate snap as well as specifying how to access relative files.
14 |
15 | ## Motivation
16 |
17 | Currently Snap IDs are used both as the unique identifier as well as location to look for a snap. This introduces weird behavior where a snap installed from different places (such as npm and http) with the same code is different while different snaps from one location (such as multiple deployments from http://localhost:8080) are treated as single continuously updated snap and share persistent state.
18 |
19 | This SIP is part of separating Snap IDs into location and a unique identifier.
20 |
21 | ## Specification
22 |
23 | > Such sections are considered non-normative.
24 |
25 | ### Language
26 |
27 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
28 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
29 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
30 |
31 | ### Snap Location
32 |
33 | The location of a snap MUST be an URI as defined in [RFC-3986](https://datatracker.ietf.org/doc/html/rfc3986).
34 |
35 | > URI consists of a `scheme`, `authority` and a `path`, which we use throughout this SIP.
36 |
37 | ### Supported schemes
38 |
39 | - `npm` - The snap SHALL be searched for using [registry.npmjs.com](https://registry.npmjs.com) protocol. `https` SHALL be used as the underlying transport protocol.
40 | - The `authority` part of the URI SHALL indicate the npmjs-like registry to use. The `authority` MAY be omitted and the default [https://registry.npmjs.com](https://registry.npmjs.com) SHALL be used instead.
41 | - The `path` represents the package's id namespaced to the registry. The wallet SHALL search for `package.json` in the root directory of the package. All files referenced in `package.json` SHALL be searched for relative to the root directory of the package.
42 | - `http` / `https` - The `package.json` MUST be under that URL. All files referenced in `package.json` SHALL be searched for relative to the path of `package.json` using relative URL resolution as described by [RFC 1808](https://www.ietf.org/rfc/rfc1808.txt).
43 | - `ipfs` - The `authority` MUST be an IPFS CID that is also a directory. The wallet SHALL search for `package.json` in root of that directory. All files referenced in `package.json` SHALL be looked relative to the root directory.
44 |
45 | ## Test vectors
46 |
47 | ### NPM
48 |
49 | - `npm:my-snap`
50 | - `scheme` - `npm`
51 | - `authority` - `https://registry.npmjs.com`
52 | - `path` - `my-snap`
53 | - `npm://root@my-registry.com:8080/my-snap`
54 | - `scheme` - `npm`
55 | - `authority` - `https://root@my-registry.com:8080`
56 | - `path` - `my-snap`
57 |
58 | ### HTTP / HTTPS
59 |
60 | > Test vectors for HTTP are considered the same except the differing scheme
61 |
62 | - `https://localhost:8080`
63 | - `scheme` - `https`
64 | - `authority` - `localhost:8080`
65 | - `path` - _(zero-length)_
66 | - `package.json:main: "dist/index.js"` - `https://localhost:8080/dist/index.js`
67 | - `https://my-host.com/my-snap`
68 | - `scheme` - `https`
69 | - `authority` - `my-host.com`
70 | - `path` - `my-snap`
71 | - `package.json:main: "dist/index.js"` - `https://my-host.com/dist/index.js`
72 | - `https://my-host.com/my-snap/`
73 | - `scheme` - `https`
74 | - `authority` - `my-host.com`
75 | - `path` - `my-snap/`
76 | - `package.json:main: "dist/index.js"` - `https://my-host.com/my-snap/dist/index.js`
77 |
78 | ### IPFS
79 |
80 | - `ipfs://bafybeifpaez32hlrz5tmr7scndxtjgw3auuloyuyxblynqmjw5saapewmu`
81 | - `scheme` - `ipfs`
82 | - `authority` - `bafybeifpaez32hlrz5tmr7scndxtjgw3auuloyuyxblynqmjw5saapewmu`
83 | - `path` - _(zero-length)_
84 | - `package.json:main: "dist/index.js"` - `ipfs://bafybeifpaez32hlrz5tmr7scndxtjgw3auuloyuyxblynqmjw5saapewmu/dist/index.js`
85 |
86 | ## Copyright
87 |
88 | Copyright and related rights waived via [CC0](../LICENSE).
89 |
--------------------------------------------------------------------------------
/SIPS/sip-14.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 14
3 | title: Dynamic Permissions
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/114
6 | author: Frederik Bolding (@frederikbolding)
7 | created: 2023-10-19
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP proposes changes to the Snap manifest and new RPC methods that allows Snap developers to request additional permissions dynamically at runtime. This proposal outlines some of the details around this feature.
13 |
14 | ## Motivation
15 |
16 | Snaps currently have to request all permissions that they plan to use at install-time. This becomes a problem when a Snap wants to use many permissions as the installation experience suffers and the user has to either accept all permissions requested, or deny the installation. This proposal provides an improvement to the experience by letting Snaps request permissions at runtime as long as those permissions are statically defined in the manifest at build-time.
17 |
18 | ## Specification
19 |
20 | > Formal specifications are written in Typescript.
21 |
22 | ### Language
23 |
24 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
25 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
26 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
27 |
28 | ### Snap Manifest
29 |
30 | This SIP adds a new field to the Snap manifest called `dynamicPermissions`.
31 | This field can be used in tandem with the existing `initialPermissions`, but permissions in this field are not granted by installation: They MUST be requested when needed. The field follows the same format as `initialPermissions`.
32 |
33 | The new field can be specified as follows in a `snap.manifest.json` file:
34 |
35 | ```json
36 | {
37 | "initialPermissions": {
38 | "endowment:transaction-insight": {}
39 | },
40 | "dynamicPermissions": {
41 | "snap_dialog": {},
42 | "snap_getBip44Entropy": [
43 | {
44 | "coinType": 1
45 | },
46 | {
47 | "coinType": 3
48 | }
49 | ]
50 | }
51 | }
52 | ```
53 |
54 | ### Permission caveats
55 |
56 | Duplicated permissions in `initialPermissions` and `dynamicPermissions` MUST NOT be allowed. A permission MUST only be able to exist in one of the manifest fields.
57 |
58 | Furthermore, permissions specified in `dynamicPermissions` MUST contain the caveats that will be requested at runtime and the permission request MUST fully match the caveats specified in the manifest.
59 |
60 | This MAY change in a future SIP.
61 |
62 | ### RPC Methods
63 |
64 | This SIP proposes the following RPC methods to manage the dynamic permissions:
65 |
66 | #### snap_requestPermissions
67 |
68 | This RPC method SHOULD function as a subset of the existing `wallet_requestPermissions` RPC method (as defined in [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255)) and take the same parameters and have the same return value.
69 |
70 | This RPC method MUST prompt the user to get consent for any requested permissions and MUST validate that the requested permissions are specified in the manifest before continuing its execution (including matching caveats).
71 |
72 |
73 | #### snap_getPermissions
74 |
75 | This RPC method SHOULD be an alias for `wallet_getPermissions`, and MAY be used by the Snap for verifying whether it already has the permissions needed for operating. The return value and parameters SHOULD match the existing specification defined in [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255).
76 |
77 | #### snap_revokePermissions
78 |
79 | This RPC method SHOULD take a similar input to `wallet_requestPermissions`, an object keyed with permission names, where the values may contain caveats if applicable.
80 |
81 | For example:
82 |
83 | ```json
84 | {
85 | "method": "snap_revokePermissions",
86 | "params": {
87 | "snap_getBip32Entropy": {
88 | "caveats": [
89 | {
90 | "type": "permittedDerivationPaths",
91 | "value": [
92 | { "path": ["m", "44'", "60'"], "curve": "secp256k1" },
93 | { "path": ["m", "0'", "0'"], "curve": "ed25519" }
94 | ]
95 | }
96 | ]
97 | }
98 | }
99 | }
100 | ```
101 |
102 | The caveat information passed SHOULD be ignored in the initial implementation of this. Instead of processing the caveats, the implementation SHOULD revoke the entire permission key. We will revisit this at a later time to make it more granular.
103 |
104 | This RPC method SHOULD return `null` if the permissions are revoked successfully, or return an error otherwise.
105 |
106 | This RPC method MUST validate that the permissions to be revoked do not contain any permissions specified in `initialPermissions`. Only `dynamicPermissions` can be revoked.
107 |
108 | ## Copyright
109 |
110 | Copyright and related rights waived via [CC0](../LICENSE).
111 |
--------------------------------------------------------------------------------
/assets/sip-9/snap.manifest.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 |
4 | "title": "Manifest describing capabilities of a MetaMask snap",
5 |
6 | "type": "object",
7 | "required": [
8 | "version",
9 | "proposedName",
10 | "description",
11 | "source",
12 | "initialPermissions",
13 | "manifestVersion"
14 | ],
15 | "properties": {
16 | "version": {
17 | "title": "Snap's version in SemVer format",
18 | "description": "Must be the same as in package.json",
19 |
20 | "type": "string"
21 | },
22 | "proposedName": {
23 | "title": "Snap author's proposed name for the snap",
24 | "description": "The wallet may display this name in its user interface",
25 |
26 | "type": "string",
27 | "minLength": 1,
28 | "maxLength": 214
29 | },
30 | "description": {
31 | "title": "A short description of snap",
32 | "description": "The wallet may display this description in its user interface",
33 |
34 | "type": "string",
35 | "minLength": 1,
36 | "maxLength": 280
37 | },
38 | "repository": {
39 | "$comment": "Taken from https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/package.json",
40 |
41 | "title": "Git repository with snap's source code",
42 | "description": "Specify the place where your code lives. This is helpful for people who want to contribute.",
43 |
44 | "type": ["object", "string"],
45 | "properties": {
46 | "type": {
47 | "type": "string"
48 | },
49 | "url": {
50 | "type": "string"
51 | },
52 | "directory": {
53 | "type": "string"
54 | }
55 | }
56 | },
57 | "source": {
58 | "title": "Snap's runtime files",
59 | "description": "Information needed to load and execute the snaps' code",
60 |
61 | "type": "object",
62 | "required": ["shasum", "location"],
63 | "properties": {
64 | "shasum": {
65 | "title": "Checksum of runtime files",
66 | "description": "Checksum composed from all the files that are loaded during runtime",
67 |
68 | "type": "string",
69 | "minLength": 44,
70 | "maxLength": 44,
71 | "pattern": "^[A-Za-z0-9+\\/]{43}=$"
72 | },
73 | "location": {
74 | "title": "Location of the snap runtime code",
75 | "description": "Points to a single JavaScript file which will be run under Snaps sandbox",
76 |
77 | "type": "object",
78 | "additionalProperties": false,
79 | "required": ["npm"],
80 | "properties": {
81 | "npm": {
82 | "title": "Runtime file location in Npm",
83 | "description": "Information on how to locate the runtime file in an Npm package",
84 |
85 | "type": "object",
86 | "required": ["filePath", "packageName"],
87 | "properties": {
88 | "filePath": {
89 | "title": "Runtime code filepath",
90 | "description": "Unix-style location of the JavaScript source file relative to root of the Npm package",
91 | "type": "string"
92 | },
93 | "packageName": {
94 | "$comment": "Taken from https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/package.json",
95 |
96 | "title": "Npm package name",
97 | "description": "The name of the npm package. Currently required to be the same one as the one containing the manifest.",
98 | "type": "string",
99 | "maxLength": 214,
100 | "minLength": 1,
101 | "pattern": "^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"
102 | },
103 | "iconPath": {
104 | "title": "Snap icon filepath",
105 | "description": "Optional path of .svg icon that will be shown to the user in the UI",
106 |
107 | "type": "string"
108 | },
109 | "registry": {
110 | "title": "Npm registry",
111 | "description": "URL pointing to Npm registry used to load the package",
112 |
113 | "enum": [
114 | "https://registry.npmjs.org",
115 | "https://registry.npmjs.org/"
116 | ]
117 | }
118 | }
119 | }
120 | }
121 | }
122 | }
123 | },
124 | "initialPermissions": {
125 | "title": "Snap permissions",
126 | "description": "Permissions requested by the snap from the user",
127 |
128 | "type": "object"
129 | },
130 | "manifestVersion": {
131 | "title": "snap.manifest.json version",
132 | "description": "The version of this file used to detect compatibility",
133 | "enum": ["0.1"]
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/SIPS/sip-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 1
3 | title: SIP Process
4 | status: Living
5 | author: Olaf Tomalka (@ritave), Erik Marks (@rekmarks)
6 | created: 2022-06-08
7 | updated: 2022-09-29
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP specified the process of working with proposals inside this repository.
13 | The goals of the process is participate with the community, but in a structured, engineering-focused, way.
14 |
15 | ### Language
16 |
17 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
18 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
19 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
20 |
21 | ## Workflow
22 |
23 | > A good starting point for a new SIP is the [SIP template](../sip-template.md).
24 |
25 | ### Stages
26 |
27 | ```mermaid
28 | stateDiagram-v2
29 | direction LR
30 | [*] --> Draft
31 | Draft --> Review
32 | Review --> Living
33 | Review --> Implementation
34 | Implementation --> Final
35 | Final --> [*]
36 |
37 | Draft --> Withdrawn
38 | Review --> Withdrawn
39 | Implementation --> Withdrawn
40 | Withdrawn --> [*]
41 | ```
42 |
43 | - **Draft** - The initial SIP status, indicating that it is in development. The SIP will be merged into the repository after being properly formatted. Major changes to the contents of the SIP are expected.
44 | - **Review** - The SIP author(s) marked this SIP as ready for peer review. All members of community are encouraged to participate. Incremental changes to the contents of the SIP are expected.
45 | - **Implementation** - This SIP is being implemented. Only critical changes based on implementation experience are expected.
46 | - **Final** - The SIP is considered a final standard. No further updates except errata and clarifications will be considered. The SIP MUST be fully implemented before being considered for this status.
47 | - **Withdrawn** - The proposed SIP has been withdrawn by the SIP author(s) or will not be considered for inclusion. This status is final. If the idea is to be pursued again, a new proposal MUST be created.
48 | - **Living** - SIPs with this special status are considered continually updated and never final. Such SIPs MUST include the `updated` property in their front-matter. Specifically this includes [SIP-1](./sip-1.md).
49 |
50 | ## SIP Header Preamble
51 |
52 | Each SIP MUST begin with a header preamble in YAML format, preceded and followed by `---`. Such header is termed as [Front Matter by Jekyll](https://jekyllrb.com/docs/front-matter/). The headers MUST appear in following order:
53 |
54 | - `sip` - A unique number identifying the SIP, assigned by the SIP editors.
55 | - `title` - A descriptive name for the SIP.
56 | - `status` - The current status of the SIP. One of stages as described [above](#stages).
57 | - `discussions-to` - (Optional) A URL where discussions and review of the specified SIP can be found.
58 | - `author` - The list of authors in format described [below](#author-header).
59 | - `created` - The date the SIP was created on.
60 | - `updated` - (Optional) The date the SIP was updated on. SIPs with status `Living` MUST have this header.
61 |
62 | All dates in the preamble MUST be in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format.
63 |
64 | #### `author` header
65 |
66 | A comma-separated list of the SIP authors. Each author MUST be written in the following format: `Name Surname (@github-username)`. If the author wants to maintain anonymity, they MAY provide only the username instead of their name and surname. The e-mail and github username are optional and MAY be provided. At least one of the authors MUST have a Github username provided in order to be notified of the changes concerning their SIP.
67 |
68 | Example `author` header:
69 |
70 | `John F. Kowalsky (@kowalsky), Gregory House, Anon (@anon)`
71 |
72 | ## Additional files
73 |
74 | The SIP MAY include additional files such as images and diagrams. Such files MUST be placed in `assets` folder in the following location `assets/SIP/sip-N/` where `N` is the SIP's number. When linking to files from SIPs, relative links MUST be used, such as `[schema](../assets/sip/sip-1/schema.json)`.
75 |
76 | ## Validation
77 |
78 | Every SIP MUST pass automatic validation when added in a pull request. You can manually use the validation tool as follows:
79 |
80 | ```bash
81 | cd tools/validate
82 | yarn install
83 | yarn build
84 | yarn validate '../../SIPS/**'
85 | ```
86 |
87 | ## SIP Editors
88 |
89 | The role of SIP repository editors is to enforce the inclusion process. They do not judge the proposals on it's merits, it is up to the community to discuss.
90 |
91 | The current SIP editors, sorted alphabetically, are:
92 |
93 | - Christian Montoya ([@Montoya](https://github.com/Montoya))
94 | - Hassan Malik ([@hmalik88](https://github.com/hmalik88))
95 | - Olaf Tomalka ([@ritave](https://github.com/ritave))
96 | - Ziad Saab ([@ziad-saab](https://github.com/ziad-saab))
97 |
98 | At least one of the editors has to approve any incoming pull requests that update files in the [SIPs folder](./).
99 |
100 | ## History
101 |
102 | The Snaps Improvement Proposals have been inspired by [EIPs](https://github.com/ethereum/EIPs), [CAIPs](https://github.com/ChainAgnostic/CAIPs) and [TC39 Stages](https://tc39.es/process-document/).
103 |
104 | ## Copyright
105 |
106 | Copyright and related rights waived via [CC0](../LICENSE).
107 |
--------------------------------------------------------------------------------
/SIPS/sip-23.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 23
3 | title: JSX for Snap interfaces
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/137
6 | author: Maarten Zuidhoorn (@mrtenz)
7 | created: 2024-03-22
8 | ---
9 |
10 | ## Abstract
11 |
12 | This proposal presents a specification for the use of JSX (JavaScript XML) in
13 | Snap interfaces. It includes a set of definitions, components, and rules for
14 | usage. The document delves into the details of various components such as
15 | `SnapElement`, `SnapComponent`, and `SnapNode`. It also elaborates on specific
16 | elements like , `Address`, `Bold`, `Button`, and `Text` among others, explaining
17 | their structure and purpose.
18 |
19 | Moreover, the proposal takes into account backward compatibility considerations.
20 | It outlines a systematic approach to translate the components from the previous
21 | SIP-7 format into the newly proposed format.
22 |
23 | The proposal aims to create a more robust, flexible, and versatile system for
24 | Snap interfaces. It strives to enhance the user experience and improve the
25 | efficiency of the system by offering a structured and standardised component
26 | framework.
27 |
28 | ## Motivation
29 |
30 | The motivation behind this proposal is to leverage JSX, a popular syntax
31 | extension for JavaScript, for designing and implementing Snap interfaces. JSX
32 | offers several advantages that make it a preferred choice among developers.
33 | Primarily, it allows for writing HTML-like syntax directly in the JavaScript
34 | code, which makes it more readable and intuitive. This facilitates easier
35 | development and maintenance of complex UI structures.
36 |
37 | Furthermore, JSX is universally recognised and widely adopted in the JavaScript
38 | community, especially within the React ecosystem. By using JSX for Snap
39 | interfaces, we enable a vast number of developers familiar with this syntax to
40 | contribute effectively in a shorter time frame.
41 |
42 | Adopting JSX also ensures better integration with modern development tools and
43 | practices. It allows for integration with linters, formatters, and type
44 | checkers, thus improving the development workflow.
45 |
46 | In summary, the use of JSX in Snap interfaces aims to improve developer
47 | experience, enhance code maintainability, and ultimately, lead to the creation
48 | of more robust and efficient Snap interfaces.
49 |
50 | ## Specification
51 |
52 | > Formal specifications are written in TypeScript.
53 |
54 | ### Language
55 |
56 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
57 | "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" written in
58 | uppercase in this document are to be interpreted as described in
59 | [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
60 |
61 | ### Definitions
62 |
63 | #### Key
64 |
65 | A JSX key-like value, i.e., a `string`, `number`, or `null`.
66 |
67 | ```typescript
68 | type Key = string | number | null;
69 | ```
70 |
71 | #### SnapElement
72 |
73 | A rendered JSX element, i.e., an object with a `type`, `props`, and `key`.
74 |
75 | ```typescript
76 | type SnapElement> = {
77 | type: string;
78 | props: Props;
79 | key: Key | null;
80 | };
81 | ```
82 |
83 | #### SnapComponent
84 |
85 | A JSX component, i.e., a function which accepts `props`, and returns a
86 | `SnapElement`. All components MUST accept a `key` property in addition to the
87 | regular props.
88 |
89 | ```typescript
90 | type SnapComponent> =
91 | (props: Props & { key: Key }) => SnapElement;
92 | ```
93 |
94 | #### SnapNode
95 |
96 | A rendered JSX node, i.e., a `SnapElement`, `string`, `null`, or an array of
97 | `SnapNode`s. Note that HTML elements are not supported in Snap nodes.
98 |
99 | ```typescript
100 | type SnapNode =
101 | | SnapElement
102 | | string
103 | | null
104 | | SnapNode[];
105 | ```
106 |
107 | ### Interface structure
108 |
109 | The Snap interface structure is defined using JSX components, and consists of
110 | either:
111 |
112 | - A container element, `Container`, which wraps the entire interface.
113 | - A box element, `Box`, which contains the main content of the interface.
114 | - An optional footer element, `Footer`, which appears at the bottom of the
115 | interface.
116 |
117 | 
118 |
119 | Or:
120 |
121 | - A box element, `Box`, which contains the main content of the interface.
122 |
123 | #### Example
124 |
125 | Below is an example of a simple Snap interface structure using JSX components:
126 |
127 | ```typescript jsx
128 |
129 |
130 | My Snap
131 | Hello, world!
132 |
133 |
136 |
137 | ```
138 |
139 | ### JSX runtime
140 |
141 | The JSX runtime is a set of functions that are used to render JSX elements,
142 | typically provided by a library like React. Since Snap interfaces are rendered
143 | in a custom environment, the JSX runtime MUST be provided by the Snaps platform.
144 |
145 | The Snaps JSX runtime only supports the modern JSX factory functions, i.e.,
146 | `jsx` and `jsxs`. The runtime MUST NOT support the legacy `createElement` and
147 | `Fragment` functions.
148 |
149 | Both the `jsx` and `jsxs` functions MUST return a `SnapElement`.
150 |
151 | ## Backward compatibility
152 |
153 | To ensure backward compatibility with the previous SIP-7 format, the legacy
154 | components MUST be translated into the new JSX format.
155 |
156 | This SIP does not cover the translation process in detail, but simply outlines
157 | the components and rules for usage in the new format. All features and
158 | functionalities of the previous format are supported in the new format, so
159 | existing Snap interfaces can be easily translated into the new format.
160 |
161 | Most components in the new format have a one-to-one correspondence with the
162 | components in the previous format. The `Text` component in the new format
163 | replaces the Markdown syntax in the previous format, and the `Bold`, `Italic`,
164 | and `Link` components can be used to achieve similar effects. The `Box`
165 | component in the new format replaces the `panel` component in the previous
166 | format.
167 |
168 | ## Copyright
169 |
170 | Copyright and related rights waived via [CC0](../LICENSE).
171 |
--------------------------------------------------------------------------------
/SIPS/sip-3.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 3
3 | title: Transaction Insights
4 | status: Final
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/31
6 | author: Hassan Malik (@hmalik88), Frederik Bolding (@frederikbolding)
7 | created: 2022-08-23
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP proposes a way for snaps to provide "insights" into transactions that users are signing. These insights can then be displayed in the MetaMask confirmation UI, helping the user to make informed decisions about signing transactions.
13 |
14 | Example use cases for transaction insights are phishing detection, malicious contract detection and transaction simulation.
15 |
16 | ## Motivation
17 |
18 | One of the most difficult problems blockchain wallets solve for their users is "signature comprehension", i.e. making cryptographic signature inputs intelligible to the user.
19 | Blockchain transactions are signed before being submitted to a node, and constitute an important subset of this problem space.
20 | A single wallet may not be able to provide all relevant information to any given user for any given transaction.
21 | To alleviate this problem, this SIP aims to expand the kinds of information MetaMask provides to a user before signing a transaction.
22 |
23 | The current MetaMask extension already has a "transaction insights" feature that decodes transactions and displays the result to the user.
24 | To expand on this feature, this SIP allows the community to build snaps that provide arbitrary "insights" into transactions.
25 | These insights can then be displayed in the MetaMask UI alongside any information provided by MetaMask itself.
26 |
27 | ## Specification
28 |
29 | > Formal specifications are written in Typescript. Usage of `CAIP-N` specifications, where `N` is a number, are references to [Chain Agnostic Improvement Proposals](https://github.com/ChainAgnostic/CAIPs).
30 |
31 | ### Language
32 |
33 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
34 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
35 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
36 |
37 | ### Definitions
38 |
39 | > This section is non-normative, and merely recapitulates some definitions from [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md).
40 |
41 | - `ChainId` - a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) string.
42 | It identifies a specific chain among all blockchains recognized by the CAIP standards.
43 | - `ChainId` consists of a `Namespace` and a `Reference`
44 | - `Namespace` - a class of similar blockchains. For example EVM-based blockchains.
45 | - `Reference` - a way to identify a concrete chain inside a `Namespace`. For example Ethereum Mainnet or one of its test networks.
46 |
47 | ### Snap Manifest
48 |
49 | This SIP specifies a permission named `endowment:transaction-insight`.
50 | The permission grants a snap read-only access to raw transaction payloads, before they are accepted for signing by the user.
51 |
52 | This permission is specified as follows in `snap.manifest.json` files:
53 |
54 | ```json
55 | {
56 | "initialPermissions": {
57 | "endowment:transaction-insight": {}
58 | }
59 | }
60 | ```
61 |
62 | ### Snap Implementation
63 |
64 | Any snap that wishes to provide transaction insight features **MUST** implement the following API:
65 |
66 | ```typescript
67 | import { OnTransactionHandler } from "@metamask/snap-types";
68 |
69 | export const onTransaction: OnTransactionHandler = async ({
70 | transaction,
71 | chainId,
72 | }) => {
73 | const insights = /* Get insights */;
74 | return { insights };
75 | };
76 | ```
77 |
78 | The interface for an `onTransaction` handler function’s arguments is:
79 |
80 | ```typescript
81 | interface OnTransactionArgs {
82 | transaction: Record;
83 | chainId: string;
84 | }
85 | ```
86 |
87 | `transaction` - The transaction object is intentionally not defined in this SIP because different chains may specify different transaction formats.
88 | It is beyond the scope of the SIP standards to define interfaces for every chain.
89 | Instead, it is the Snap developer's responsibility to be cognizant of the shape of transaction objects for relevant chains.
90 | Nevertheless, you can refer to [Appendix I](#appendix-i-ethereum-transaction-objects) for the interfaces of the Ethereum transaction objects available in MetaMask at the time of this SIP's creation.
91 |
92 | `chainId` - This is a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) `chainId` string.
93 | The snap is expected to parse and utilize this string as needed.
94 |
95 | The interface for the return value of an `onTransaction` export is:
96 |
97 | ```typescript
98 | interface OnTransactionReturn {
99 | insights: Record;
100 | }
101 | ```
102 |
103 | ### MetaMask Integration
104 |
105 | The `insights` object returned by the snap will be displayed alongside the confirmation for the `transaction` that `onTransaction` was called with.
106 | Keys and values will be displayed in the order received, with each key rendered as a title and each value rendered as follows:
107 |
108 | - If the value is an array or an object, it will be rendered as text after being converted to a string.
109 | - If the value is neither an array nor an object, it will be rendered directly as text.
110 |
111 | ## Appendix I: Ethereum Transaction Objects
112 |
113 | The following transaction objects may appear for any `chainId` of `eip155:*` where `*` is some positive integer.
114 | This includes all Ethereum or "EVM-compatible" chains.
115 | As of the time of creation of this SIP, they are the only possible transaction objects for Ethereum chains.
116 |
117 | ### EIP-1559
118 |
119 | ```typescript
120 | interface TransactionObject {
121 | from: string;
122 | to: string;
123 | nonce: string;
124 | value: string;
125 | data: string;
126 | gas: string;
127 | maxFeePerGas: string;
128 | maxPriorityFeePerGas: string;
129 | type: string;
130 | estimateSuggested: string;
131 | estimateUsed: string;
132 | }
133 | ```
134 |
135 | ### Legacy (non-EIP-1559)
136 |
137 | ```typescript
138 | interface LegacyTransactionObject {
139 | from: string;
140 | to: string;
141 | nonce: string;
142 | value: string;
143 | data: string;
144 | gas: string;
145 | gasPrice: string;
146 | type: string;
147 | estimateSuggested: string;
148 | estimateUsed: string;
149 | }
150 | ```
151 |
152 | ## Copyright
153 |
154 | Copyright and related rights waived via [CC0](../LICENSE).
155 |
--------------------------------------------------------------------------------
/SIPS/sip-9.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 9
3 | title: snap.manifest.json v0.1
4 | status: Final
5 | author: Erik Marks (@rekmarks), Olaf Tomalka (@ritave)
6 | created: 2022-11-07
7 | ---
8 |
9 | ## Abstract
10 |
11 | This document specifies version `0.1` of the Snaps manifest file, `snap.manifest.json`.
12 |
13 | ## Motivation
14 |
15 | The goal of this SIP is to supersede [Snaps Publishing Specification v0.1](https://github.com/MetaMask/specifications/blob/c226cbaca1deb83d3e85941d06fc7534ff972336/snaps/publishing.md), and move Snaps specifications into one place - Snaps Improvement Proposals.
16 |
17 | ## Specification
18 |
19 | > Indented sections like this are considered non-normative.
20 |
21 | Paths that traverse JSON objects are using [jq syntax](https://stedolan.github.io/jq/manual/#Basicfilters).
22 |
23 | ### Language
24 |
25 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
26 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
27 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
28 |
29 | ## Folder Structure
30 |
31 | > The published files of an example Snap published to npm under the package name `@metamask/example-snap` may look like this:
32 | >
33 | > ```
34 | > example-snap/
35 | > ├─ dist/
36 | > │ ├─ bundle.js
37 | > ├─ package.json
38 | > ├─ snap.manifest.json
39 | > ```
40 |
41 | The snap MUST contain both [`package.json`](#packagejson) and [`snap.manifest.json`](#snapmanifestjson) files in the root directory of the snap package.
42 |
43 | ### `package.json`
44 |
45 | The `package.json` file MUST adhere to [the requirements of npm](https://docs.npmjs.com/cli/v7/configuring-npm/package-json).
46 |
47 | ### `snap.manifest.json`
48 |
49 | > Note that the manifest intentionally does not contain any information explicitly identifying its author.
50 | > Author information should be verifiable out-of-band at the point of Snap installation, and is beyond the scope of this specification.
51 |
52 | - `snap.manifest.json` - The contents of the file MUST be a JSON object.
53 |
54 | - `.version` - MUST be a valid [SemVer][] version string and equal to the [corresponding `package.json` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#version).
55 | - `.proposedName` - MUST be a string less than or equal to 214 characters.
56 | The proposed name SHOULD be human-readable.
57 |
58 | > The snap's author proposed name for the snap.
59 | >
60 | > The Snap host application may display this name unmodified in its user interface.
61 |
62 | - `.description` - MUST be a non-empty string less than or equal to 280 characters.
63 | MAY differ from the [corresponding `package.json:.description` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#description-1)
64 | > A short description of the Snap.
65 | >
66 | > The Snap host application may display this description unmodified in its user interface.
67 | - `.repository` - MAY be omitted. If present, MUST be equal to the [corresponding `package.json:.repository` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#repository).
68 | - `.source` - MUST be a JSON object.
69 | - `.shasum` - MUST hash of the snap source file as specified in [Checksum](#checksum) paragraph.
70 | - `.location` - MUST be a JSON object.
71 | - `.npm` - MUST be a JSON object.
72 | - `.filePath` - MUST be a [Unix-style][unix filesystem] path relative to the package root directory pointing to the Snap source file.
73 | - `.packageName` - MUST be equal to the [`package.json:.name` field](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#name).
74 | - `.iconPath` - MAY be omitted. If present, MUST be a [Unix-style][unix filesystem] path relative to the package root directory pointing to an `.svg` file.
75 | - `.registry` - MUST be string `https://registry.npmjs.org`.
76 | - `.initialPermissions` - MUST be a valid [EIP-2255][] `wallet_requestPermissions` parameter object.
77 | > Specifies the initial permissions that will be requested when the Snap is added to the host application.
78 | - `.manifestVersion` - MUST be the string `0.1`.
79 |
80 | ### Checksum
81 |
82 | The checksum MUST be calculated using SHA-256 algorithm as specified in NIST's [FIPS PUB 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
83 |
84 | The checksum MUST be calculated over the file located under `snap.manifest.json:.source.location.npm.filePath` path and saved under `snap.manifest.json:.source.shasum` as Base64 field with exactly 44 characters. The Base64 character set MUST be `A-Z`, `a-z`, `0-9`, `+`, `/` with `=` used for padding. The padding MUST NOT be optional.
85 |
86 | ### Snap Source File
87 |
88 | > Represented in the [example](../assets/sip-9/example-snap/) as `dist/bundle.js`. The Snap "source" or "bundle" file can be named anything and kept anywhere in the package file hierarchy.
89 |
90 | The snap source file, located under `snap.manifest.json:.source.location.npm.filePath` path MUST:
91 |
92 | - have the `.js` file extension.
93 | - contain the entire source of the Snap program, including all dependencies.
94 | - execute under [SES][].
95 |
96 | ## Test vectors
97 |
98 | ### Snap package
99 |
100 | > A full example snap package can be found in the [assets](../assets/sip-9/example-snap/).
101 |
102 | ### Manifest
103 |
104 | > A complete JSON Schema can be [found in the assets](../assets/sip-9/snap.manifest.schema.json).
105 |
106 | ### Checksum
107 |
108 | > The shashum was generated using `shasum -a 256 assets/sip-9/source.js | cut -d ' ' -f 1 | xxd -r -p | base64` command
109 |
110 | - [`assets/sip-9/source.js`](../assets/sip-9/source.js) - `x3coXGvZxPMsVCqPA1zr9SG/bw8SzrCPncClIClCfwA=`
111 | - `` - `47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=`
112 |
113 | ## Errata
114 |
115 | 2023-05-08:
116 |
117 | - Fix [JSON Schema](../assets/sip-9/snap.manifest.schema.json) having some properties at the wrong nesting level.
118 | - Fix [JSON Schema](../assets/sip-9/snap.manifest.schema.json) not supporting NPM registry with `/` suffix.
119 | - Added titles and descriptions to all properties in [JSON Schema](../assets/sip-9/snap.manifest.schema.json) which are required for suggestions in Visual Studio Code.
120 |
121 | ## Copyright
122 |
123 | Copyright and related rights waived via [CC0](../LICENSE).
124 |
125 | [eip-2255]: https://eips.ethereum.org/EIPS/eip-2255
126 | [semver]: https://semver.org/
127 | [ses]: https://www.npmjs.com/package/ses
128 | [unix filesystem]: https://en.wikipedia.org/wiki/Unix_filesystem
129 |
--------------------------------------------------------------------------------
/SIPS/sip-26.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 26
3 | title: Non-EVM protocol support
4 | status: Draft
5 | author: Daniel Rocha (@danroc), Frederik Bolding (@FrederikBolding), Alex Donesky (@adonesky1)
6 | created: 2024-08-28
7 | ---
8 |
9 | ## Abstract
10 |
11 | This SIP presents an architecture to enable Snaps to expose blockchain-specific
12 | methods to dapps, extending MetaMask's functionality to support a multichain
13 | ecosystem.
14 |
15 | ## Motivation
16 |
17 | Currently, MetaMask is limited to EVM-compatible networks. This proposal aims
18 | to empower developers, both first- and third-party, to use Snaps to add native
19 | support for non-EVM-compatible chains within MetaMask.
20 |
21 | ## Specification
22 |
23 | > Formal specifications are written in TypeScript.
24 |
25 | ### Language
26 |
27 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
28 | "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" written
29 | in uppercase in this document are to be interpreted as described in [RFC
30 | 2119](https://www.ietf.org/rfc/rfc2119.txt).
31 |
32 | ### High-Level architecture
33 |
34 | The diagram below represents a high-level architecture of how the RPC Router
35 | integrates inside MetaMask to allow Snaps to expose protocol methods to dapps
36 | and the MetaMask clients.
37 |
38 | 
39 |
40 | - **Account Snaps**: Snaps that implement the [Keyring API][keyring-api] and are responsible
41 | for signing requests and managing accounts.
42 |
43 | - **Protocol Snaps**: Snaps that implement protocol methods that do not require
44 | an account to be executed.
45 |
46 | - **RPC Router**: Native component that forwards RPC requests to the
47 | appropriate Protocol Snap or Account Snap.
48 |
49 | - **Keyring Controller**: Native component responsible for forwarding signing
50 | requests to the appropriate keyring implementation.
51 |
52 | - **Accounts Controller**: Native component responsible for managing accounts
53 | inside MetaMask. It stores all non-sensitive account information.
54 |
55 | - **Snaps Keyring**: Native component that acts as a bridge between the
56 | Keyring Controller and the Account Snaps.
57 |
58 | ### Components
59 |
60 | Here is a brief description of the components involved in this architecture
61 | which will require to be implemented or modified.
62 |
63 | #### RPC Router
64 |
65 | The RPC Router will be a new native component responsible for routing JSON-RPC
66 | requests to the appropriate Snap or keyring.
67 |
68 | To route a request, the RPC Router MUST extract the method name and [CAIP-2 chainId][caip-2]
69 | from the request object. It then determines whether the method is supported by
70 | a Protocol Snap or an Account Snap, with Account Snaps taking precedence over
71 | Protocol Snaps.
72 |
73 | If the method is supported by an Account Snap, the RPC Router forwards the
74 | request to the Keyring Controller; otherwise, it forwards the request to the
75 | appropriate Protocol Snap.
76 |
77 | #### Snaps Keyring
78 |
79 | The Snaps Keyring is an existing native component that exposes the
80 | [snap_manageAccounts][snap-manage-accs] method, allowing Account Snaps to
81 | register, remove, and update accounts.
82 |
83 | For example, this code can be used by an Account Snap to register a new account
84 | with the Account Router:
85 |
86 | ```typescript
87 | // This will notify the Account Router that a new account was created, and the
88 | // Account Router will register this account as available for signing requests
89 | // using the `eth_signTypedData_v4` method.
90 | await snap.request({
91 | method: "snap_manageAccounts",
92 | params: {
93 | method: "notify:accountCreated",
94 | params: {
95 | account: {
96 | id: "74bb3393-f267-48ee-855a-2ba575291ab0",
97 | type: "eip155:eoa",
98 | address: "0x1234567890123456789012345678901234567890",
99 | methods: ["eth_signTypedData_v4"],
100 | options: {},
101 | },
102 | },
103 | },
104 | });
105 | ```
106 |
107 | Similar events are available to notify about the removal and update of
108 | accounts: `notify:accountRemoved` and `notify:accountUpdated`.
109 |
110 | Additionally, the Snaps Keyring expects the Account Snap to implement the
111 | Keyring API so it can forward signing requests to it through the
112 | [`keyring_submitRequest`][submit-request] method.
113 |
114 | #### Account Snaps
115 |
116 | As part of the Keyring API, non-EVM Account Snaps MUST also implement support
117 | for the `keyring_resolveAccountAddress` RPC method defined below. It is used
118 | by the RPC Router to extract the address of the account that should handle
119 | the signing request from the request object.
120 |
121 | ```typescript
122 | type ResolveAccountAddressRequest = {
123 | method: "keyring_resolveAccountAddress";
124 | params: {
125 | scope: CaipChainId;
126 | request: JsonRpcRequest;
127 | };
128 | };
129 | ```
130 | `scope` - The [CAIP-2][caip-2] chainId the request is targeting
131 |
132 | `request` - A `JsonRpcRequest` containing strictly JSON-serializable values.
133 |
134 | The implementation MUST return a value of the type `{ address: CaipAccountId }` or `null` (where `CaipAccountId` refers to a [CAIP-10][caip-10] identifier).
135 |
136 | #### Protocol Snaps
137 |
138 | Protocol Snaps implement and expose methods that do not require an account to
139 | execute and MUST list their supported methods and notifications in their manifest file:
140 |
141 | ```json5
142 | "initialPermissions": {
143 | "endowment:protocol": {
144 | "scopes": {
145 | "": {
146 | "methods": [
147 | // List of supported methods
148 | ],
149 | "notifications": [
150 | // List of supported notifications
151 | ]
152 | }
153 | }
154 | }
155 | }
156 | ```
157 |
158 | Additionally protocol Snaps MUST implement the `onProtocolRequest` handler:
159 |
160 | ```typescript
161 | import { OnProtocolRequestHandler } from "@metamask/snap-sdk";
162 |
163 | export const onProtocolRequest: OnProtocolRequestHandler = async ({
164 | origin,
165 | scope,
166 | request,
167 | }) => {
168 | // Return protocol responses
169 | };
170 | ```
171 |
172 | The interface for an `onProtocolRequest` handler function’s arguments is:
173 |
174 | ```typescript
175 | interface OnProtocolRequestArguments {
176 | origin: string;
177 | scope: CaipChainId;
178 | request: JsonRpcRequest;
179 | }
180 | ```
181 |
182 | `origin` - The origin making the protocol request (i.e. a dapp).
183 |
184 | `scope` - The [CAIP-2][caip-2] chainId the request is targeting.
185 |
186 | `request` - A `JsonRpcRequest` containing strictly JSON-serializable values.
187 |
188 | Any JSON-serializable value is allowed as the return value for `onProtocolRequest`.
189 |
190 | ## Copyright
191 |
192 | Copyright and related rights waived via [CC0](../LICENSE).
193 |
194 | [keyring-api]: https://github.com/MetaMask/accounts/tree/main/packages/keyring-api
195 | [snap-manage-accs]: https://docs.metamask.io/snaps/reference/snaps-api/#snap_manageaccounts
196 | [submit-request]: https://docs.metamask.io/snaps/reference/keyring-api/account-management/#keyring_submitrequest
197 | [caip-2]: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md
198 | [caip-10]: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md
199 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/SIPS/sip-5.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 5
3 | title: Creating JSON Web Tokens (JWTs)
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/64
6 | author: Vid Kersic (@Vid201), Andraz Vrecko (@andyv09)
7 | created: 2022-10-20
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP proposes a functionality of signing the data in the form of JSON Web Tokens (JWTs), exposed as an RPC method.
13 |
14 | JSON Web Tokens (JWTs) are an internet standard for creating data whose payload holds claims in the JSON. The standard is widely used in many applications on the web, often for authentication and authorization purposes. Other use cases are Verifiable Credentials (VCs) and Verifiable Presentations (VPs), also part of Decentralized Identity and Self-Sovereign Identity (SSI).
15 |
16 | ## Motivation
17 |
18 | Snaps were introduced to extend the functionalities of MetaMask to support more use cases and paradigms. MetaMask currently provides several RPC methods for digitally signing data (described [here](https://docs.metamask.io/guide/signing-data.html)), such as personal_sign and signTypedData. All methods add the prefix "\x19Ethereum Signed Message:\n" to the data, as described in [EIP 191](https://eips.ethereum.org/EIPS/eip-191) and [EIP 712](https://eips.ethereum.org/EIPS/eip-712), which prevents several attack vectors, most notably impersonating transactions. But this also prevents the ability to produce pure signatures over the data, which is needed for other internet data standards (such as JWTs). Actually, a pure signature is possible only with eth_sign, but that method was deprecated and advised not to use. Therefore, there is no safe way to create JWTs containing signatures signed by MetaMask accounts. This SIP proposes a new RPC method for Snaps that enables the creation of signed JWTs.
19 |
20 | ## Specification
21 |
22 | > Formal specifications are written in Typescript.
23 |
24 | ### Language
25 |
26 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
27 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
28 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
29 |
30 | ### Definitions
31 |
32 | JWTs are composed of three parts: header, payload, and signature. For more information, please check [JSON Web Token (JWT) Request for Comments (RFC)](https://www.rfc-editor.org/rfc/rfc7519).
33 |
34 | ### Snap Manifest
35 |
36 | This SIP specifies permission named `snap_signJwt`. This permission grants a snap the ability to create signed JWTs by introducing an additional signing method.
37 |
38 | This specification is specified as follows in `snap.manifest.json` files:
39 | ```typescript
40 | {
41 | "initialPermissions": {
42 | "snap_signJwt": {},
43 | }
44 | }
45 | ```
46 |
47 | ### Common Types
48 |
49 | The below common types are used throughout the specification.
50 |
51 | ```typescript
52 | interface JWTHeader {
53 | typ: string;
54 | alg: string;
55 | [params: string]: any;
56 | }
57 |
58 | interface JWTPayload {
59 | iss?: string;
60 | sub?: string;
61 | aud?: string | string[];
62 | jti?: string;
63 | nbf?: number;
64 | exp?: number;
65 | iat?: number;
66 | [claims: string]: any;
67 | }
68 | ```
69 |
70 | ### JWT Signing Implementation
71 |
72 | This implementation is modeled after implementations from libraries [did-jwt](https://github.com/decentralized-identity/did-jwt) and [jose](https://github.com/panva/jose).
73 |
74 | All hash functions that are implemented in the library [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography) are supported, e.g., SHA256, SHA512, and keccak256.
75 |
76 | ```typescript
77 | import * as u8a from "uint8arrays";
78 | const { sha256 } = require("ethereum-cryptography/sha256");
79 | const { keccak256 } = require("ethereum-cryptography/keccak");
80 | // import other hash functions
81 | const { utf8ToBytes } = require("ethereum-cryptography/utils");
82 |
83 | function hexToBytes(s: string): Uint8Array {
84 | const input = s.startsWith("0x") ? s.substring(2) : s;
85 | return u8a.fromString(input.toLowerCase(), "base16");
86 | }
87 |
88 | function bytesToBase64url(b: Uint8Array): string {
89 | return u8a.toString(b, "base64url");
90 | }
91 |
92 | function encodeSection(data: any): string {
93 | return encodeBase64url(JSON.stringify(data));
94 | }
95 |
96 | export function encodeBase64url(s: string): string {
97 | return bytesToBase64url(u8a.fromString(s));
98 | }
99 |
100 | export async function createJWT(
101 | header: Partial = {},
102 | payload: Partial = {},
103 | address: string,
104 | hashFunction: string
105 | ): Promise {
106 | if (!header.typ) header.typ = "JWT";
107 |
108 | const encodedPayload = (typeof payload === "string") ? payload : encodeSection(payload);
109 | const signingInput: string = [encodeSection(header), encodedPayload].join(
110 | "."
111 | );
112 | const bytes: Uint8Array = utf8ToBytes(signingInput);
113 |
114 | let hash: Uint8Array;
115 |
116 | switch(hashFunction) {
117 | case 'sha256':
118 | hash = sha256(bytes);
119 | break;
120 | case 'keccak256':
121 | hash = keccak256(bytes);
122 | break;
123 | ... // other hash functions
124 | }
125 |
126 | let signature = sign(hash); // Function sign can be the same as the MetaMask RPC method eth_sign. The header and payload that will be signed MUST be shown to the user.
127 | signature = hexToBytes(signature.slice(0, -2)); // remove byte appended by MetaMask
128 | const encodedSignature = bytesToBase64url(signature);
129 |
130 | return [signingInput, encodedSignature].join(".");
131 | }
132 | ```
133 |
134 | ### Snap Implementation
135 |
136 | Any snap that needs the capability of creating JWTs can do that by calling the JSON-RPC method in the following way:
137 |
138 | ```typescript
139 | const jwt = await wallet.request({
140 | method: 'snap_signJwt',
141 | params: [
142 | {
143 | header:
144 | {
145 | "alg": "ES256K",
146 | "typ": "JWT"
147 | },
148 | payload:
149 | {
150 | "iss": "Company",
151 | "sub": "Alice",
152 | "role": "employee"
153 | },
154 | address: '0x12345...',
155 | hashFunction: 'sha256'
156 | },
157 | ],
158 | });
159 | ```
160 |
161 | ## Appendix I: JSON Web Tokens (JWTs)
162 |
163 | Example of signed JWT:
164 |
165 | ```eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJDb21wYW55Iiwic3ViIjoiQWxpY2UiLCJyb2xlIjoiZW1wbG95ZWUifQ.E_QJXlLHIgO6xifadQRcsPty2LounknXq_O7HK3c1kZ0jGAG0pXgyAmkjqvpBtLsLNLonj3ilrrUEe5I_n9Clw```
166 |
167 | In the example above, the header contains fields ``alg`` and ``typ``. Algorithm ``ES256K`` uses the elliptic curve ``secp256k1`` (used in Ethereum) and hash function SHA256.
168 |
169 | ```json
170 | {
171 | "alg": "ES256K",
172 | "typ": "JWT"
173 | }
174 | ```
175 |
176 | Payload is a simple JSON object:
177 |
178 | ```json
179 | {
180 | "iss": "Company",
181 | "sub": "Alice",
182 | "role": "employee"
183 | }
184 | ```
185 |
186 | ## Appendix II: Suggested Alternative Approach
187 |
188 | There is another alternative approach for creating JWTs, which does not require the RPC signing method provided by MetaMask. Any Snap can already retrieve a private key for any account using the RPC method ``snap_getBip44Entropy``, which can be used to create signed JWTs (and for any other custom signing solution) - but this requires Snap to ask the user for special permission.
189 |
190 | ## Copyright
191 |
192 | Copyright and related rights waived via [CC0](../LICENSE).
--------------------------------------------------------------------------------
/SIPS/sip-4.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 4
3 | title: Merging of snap.manifest.json and package.json
4 | status: Withdrawn
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/51
6 | author: Olaf Tomalka (@ritave)
7 | created: 2022-09-23
8 | ---
9 |
10 | ## Abstract
11 |
12 | A proposed specification of a new `package.json` file that includes all the data needed to execute the snap, removing `snap.manifest.json` altogether.
13 |
14 | This SIP intends to supersede [Snaps Publishing Specification v0.1](https://github.com/MetaMask/specifications/blob/c226cbaca1deb83d3e85941d06fc7534ff972336/snaps/publishing.md).
15 |
16 | ## Motivation
17 |
18 | There are multiple fields that are the same in `package.json` files and `snap.manifest.json` files that routinely become desynchronized.
19 | Visual Studio Code adds it's own properties to the package.json successfully. Merging those two files will be beneficial for the developers while still allowing other snap sources outside of NPM.
20 |
21 | Because this SIP intends to supersede the previous specification, all behavior has been re-specified to serve as a singular source of truth.
22 |
23 | ## Specification
24 |
25 | > Such sections are non-normative
26 |
27 | ### Language
28 |
29 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
30 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
31 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
32 |
33 | ### package.json
34 |
35 | > The full JSON Schema for snap's package.json [can be found in the assets](../assets/sip-4/package.schema.json).
36 |
37 | The snap's `package.json` MUST adhere to `package.json` schema as [defined by the NPM organization](https://docs.npmjs.com/cli/v8/configuring-npm/package-json). That schema is extended with the following behavior:
38 |
39 | - `package.json`
40 | - `.description` - The wallet MAY use this field to display information about the snap to the user.
41 | - `.version` - MUST be a valid [SemVer](https://semver.org/spec/v2.0.0.html) string. DApps MAY request specific version ranges. If a mismatch occurs between requested version range and the version inside the fetched snap, that snap MUST NOT be installed.
42 | > Additionally, wallet MAY allow updating snaps to the requested version in some schemes.
43 | > For example, if the installed snap is `npm:my-snap@1.0.0` while the dapp requests `npm:my-snap@^2.0.0`, the wallet will try to get the newest version from [npm](https://npmjs.com) and will update the snap a version that satisfies `^2.0.0`.
44 | - `.main` - Filepath relative to `package.json` with location of the snaps bundled source code to be executed.
45 | - `.engines` - The wallet SHALL introduce `snaps` engine which will follow semver versioning. If the requested SemVer is not satisfied by the extension run by the end-user, the snap MUST NOT be executed.
46 | - The first version of `snaps` engine after implementing this SIP SHALL be `1.0.0`.
47 | - The MetaMask team SHALL adhere to the following social contract:
48 | - Any breaking changes to the API or changes to the `package.json` that require all snaps to update SHALL update the `major` part (`1.0.0` -> `2.0.0`).
49 | - Any new backwards-compatible new features SHALL update the `minor` part (`1.0.0` -> `1.1.0`).
50 | - Any bug fixes SHALL update the `patch` part (`1.0.0` -> `1.0.1`).
51 | - `.snap` - MUST exist and MUST be a JSON object with snap specific metadata.
52 | - `.snap.proposedName` - MUST exist. User readable name that the wallet MAY show in the UI. The name MUST be shorter or equal to 214 characters.
53 | - `.snap.permissions` - MUST exist. Permissions that the snap is requesting.
54 | - `.snap.checksum` - MUST exist and MUST be a JSON object specified below. The checksum in package.json MUST match the checksum of the code executed by the wallet.
55 | - `.snap.checksum.algorithm` - The algorithm field MUST be `sha-256`.
56 | - `.snap.checksum.hash` - The resulting checksum hash as described in [Checksum](#checksum) paragraph.
57 | - `.snap.icon` - MAY exist. The location of the icon that the wallet MAY use to identify the snap to the user in the UI. The icon MUST be in SVG file format.
58 |
59 | ### Checksum
60 |
61 | The checksum SHALL be calculated using SHA-256 algorithm as specified in NIST's [FIPS PUB 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
62 |
63 | The checksum SHALL be calculated over the file located under `package.json:.main` path and saved under `package.json:.snap.checksum.hash` as Base64 field with 44 characters. The Base64 character set MUST be `A-Z`, `a-z`, `0-9`, `+`, `/` with `=` used for padding.
64 |
65 | ## Test vectors
66 |
67 | ## `package.json`
68 |
69 | > You can find an example [`package.json` in the assets](../assets/sip-4/package.json).
70 |
71 | ## Checksum
72 |
73 | > The shashum was generated using `shasum -a 256 assets/sip-4/source.js | cut -d ' ' -f 1 | xxd -r -p | base64` command
74 |
75 | - [`assets/sip-4/source.js`](../assets/sip-4/source.js) - `x3coXGvZxPMsVCqPA1zr9SG/bw8SzrCPncClIClCfwA=`
76 | - `` - `47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=`
77 |
78 | ## Backwards compatibility
79 |
80 | This SIP intends to break backwards compatibility. We propose that MetaMask Flask follows our standard practice of informing the community of deprecation for one version of Flask and remove the old way altogether in the following one.
81 |
82 | List of breaking changes:
83 |
84 | - Removal of `snap.manifest.json` and reliance on `package.json`
85 |
86 | ### Potential incompatibilities
87 |
88 | While most of the properties of the files are interchangeable in the data they can represent, aside from the data structure, we've singled out `package.json:main` / `snap.manifest.json:source.location` as a potentially incompatible. `package.json` assumes that the bundle is included along with the `package.json` in one directory, while `snap.manifest.json` does not make that assumption. This means that `snap.manifest.json` can live in one location while source files in the second.
89 |
90 | We haven't identified any cases where that separation would be useful. NPM, IPFS, http and local all have concepts of directories that can be leveraged, and thus this SIP proposes that we remove the distinction of different locations for the manifest and the source.
91 |
92 | ## Appendix I: Identifying required data
93 |
94 | This is the minimal set of information we need to know about a snap to manage it properly and location of where it currently lives at the time of writing this SIP.
95 |
96 | - _Duplicate_
97 | - `snap.manifest.json:description` / `package.json:description` - User readable description of the snap.
98 | - `snap.manifest.json:version` / `package.json:version` - Version of the snap.
99 | - `snap.manifest.json:repository` / `package.json:repository` - The location of the source code.
100 | - `snap.manifest.json:manifestVersion` / `package.json:engines` - The version of the metamask for which the snap was written for.
101 | - `snap.manifest.json:source.location` / `package.json:main` - The location of the bundle file.
102 | - _`snap.manifest.json`_
103 | - `snap.manifest.json:proposedName` - The user readable name shown in the UI of the wallet.
104 | - `snap.manifest.json:initialPermissions` - Permissions that the snap is requesting from the user.
105 | - `snap.manifest.json:shasum` - The shashum of the bundled source required for security purposes.
106 | - `snap.manifest.json:source.location.npm.iconPath` - _(optional)_ The location of the icon file that represents the snap in the UI of the wallet.
107 |
108 | ## Copyright
109 |
110 | Copyright and related rights waived via [CC0](../LICENSE).
111 |
--------------------------------------------------------------------------------
/SIPS/sip-12.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 12
3 | title: Domain Resolution
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/103
6 | author: Hassan Malik (@hmalik88)
7 | created: 2023-07-18
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP proposes a new endowment, `endowment:name-lookup`, that enables a way for snaps to resolve a `domain` and `address` to their respective counterparts.
13 |
14 | ## Motivation
15 |
16 | Currently, the MetaMask wallet allows for ENS domain resolution.
17 | The implementation is hardcoded and limited to just the ENS protocol.
18 | In an effort to increasingly modularize the wallet and allow for resolution beyond ENS, we decided to open up domain/address resolution to snaps.
19 | A snap would be able to provide resolutions based on a domain or address provided with a chain ID.
20 | The address resolution is in essence "reverse resolution".
21 | The functionality provided by this API is also beneficial as a base layer for a petname system (**see definition**). With plans to bring petnames to MetaMask, resolutions would be fed into the petname system and used as a means for cache invalidation.
22 |
23 | ## Specification
24 |
25 | > Formal specifications are written in Typescript. Usage of `CAIP-N` specifications, where `N` is a number, are references to [Chain Agnostic Improvement Proposals](https://github.com/ChainAgnostic/CAIPs).
26 |
27 | ### Language
28 |
29 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
30 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
31 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
32 |
33 | ### Definitions
34 |
35 | > This section is non-normative, and merely recapitulates some definitions from [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md).
36 |
37 | - `ChainId` - a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) string.
38 | It identifies a specific chain among all blockchains recognized by the CAIP standards.
39 | - `ChainId` consists of a `Namespace` and a `Reference`
40 | - `Namespace` - a class of similar blockchains. For example EVM-based blockchains.
41 | - `Reference` - a way to identify a concrete chain inside a `Namespace`. For example Ethereum Mainnet or one of its test networks.
42 |
43 | - `AccountAddress` - The account address portion of a [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) ID.
44 |
45 | - `Petname system` - A naming system that finds balance between global, collision-free and memorable names. Please see this [article](http://www.skyhunter.com/marcs/petnames/IntroPetNames.html) on this topic.
46 |
47 | ### Snap Manifest
48 |
49 | This SIP specifies a permission named `endowment:name-lookup`.
50 | The permission grants a snap the ability to expose an `onNameLookup` export that receives an object with `chainId` and `domain` OR `address` fields.
51 |
52 | This permission is specified as follows in `snap.manifest.json` files:
53 |
54 | ```json
55 | {
56 | "initialPermissions": {
57 | "endowment:name-lookup": {
58 | "chains": ["eip155:1", "bip122:000000000019d6689c085ae165831e93"],
59 | "matchers": { "tlds": ["lens"], "schemes": ["farcaster"] }
60 | }
61 | }
62 | }
63 | ```
64 |
65 | `chains` - An optional non-empty array of CAIP-2 chain IDs that the snap supports. This field is useful for a client in order to avoid unnecessary overhead.
66 |
67 | `matchers` - An optional non-empty object that MUST contain 1 or both of the below properties. These matchers are useful for a client for validating input for domain resolution, also helpful in reducing overhead.
68 | 1. `tlds` - An optional non-empty array of top level domains that the snap will provide resolution for.
69 |
70 | 2. `schemes` - An optional non-empty array of prefixes that the snap expects for non-tld domain lookup.
71 |
72 | **Note:** TLD domains are presumed to end with "." and one of the `tlds`. Non-tld domains are presumed to start with one of the `schemes` followed by ":" then the domain. Respectively, an example of each would be `hassan.lens` and `farcaster:hbm88`.
73 |
74 | ### Snap Implementation
75 |
76 | Please see below for an example implementation of the API:
77 |
78 | ```typescript
79 | import { OnNameLookupHandler } from "@metamask/snap-types";
80 |
81 | export const onNameLookup: OnNameLookupHandler = async ({
82 | chainId,
83 | domain,
84 | address
85 | }) => {
86 | let resolution;
87 |
88 | if (domain) {
89 |
90 | resolution = { protocol: /* Domain protocol */ , resolvedAddress: /* Get domain resolution */, domainName: /* Domain name that the resolved address matches against */ };
91 | return { resolvedAddresses: [resolution] };
92 | }
93 |
94 | if (address) {
95 | resolution = { protocol: /* Domain protocol */, resolvedDomain: /* Get address resolution */ };
96 | return { resolvedDomains: [resolution] };
97 | }
98 |
99 | return null;
100 | };
101 | ```
102 |
103 | The type for an `onNameLookup` handler function's arguments is:
104 |
105 | ```typescript
106 | type OnNameLookupBaseArgs = {
107 | chainId: ChainId
108 | }
109 |
110 | type DomainLookupArgs = OnNameLookUpBaseArgs & { domain: string; address?: never };
111 | type AddressLookupArgs = OnNameLookUpBaseArgs & { address: string; domain?: never };
112 |
113 | type OnNameLookupArgs = DomainLookupArgs | AddressLookupArgs;
114 |
115 | ```
116 |
117 | `chainId` - This is a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) `chainId` string.
118 | The snap is expected to parse and utilize this string as needed.
119 |
120 | `domain` - This is a human-readable address. If the `domain` property is defined, the request is looking for resolution to an address.
121 |
122 | `address` - This is a non-readable address, this should be the native address format of the currently selected chain/protocol. If the `address` property is defined,
123 | the request is looking for resolution to a domain.
124 |
125 | The interface for the return value of an `onNameLookup` export is:
126 |
127 | ```typescript
128 | type AddressResolution = {
129 | protocol: string;
130 | resolvedAddress: AccountAddress;
131 | domainName: string;
132 | };
133 |
134 | type DomainResolution = {
135 | protocol: string;
136 | resolvedDomain: string;
137 | };
138 |
139 | type OnNameLookupResponse =
140 | | {
141 | resolvedAddresses: NonEmptyArray;
142 | resolvedDomains?: never;
143 | }
144 | | { resolvedDomains: NonEmptyArray; resolvedAddresses?: never }
145 | | null;
146 | ```
147 |
148 | **Note:**
149 | 1. The `resolvedDomain` or `resolvedAddress` in a resolution object MUST be the key that the address or domain being queried is indexed by in the protocol that the snap is resolving for. These returned values are un-opinionated at the API layer to allow the client to use them as they see fit.
150 | 2. There MUST NOT be duplicate resolutions for the same protocol in either `resolvedAddresses` or `resolvedDomains`.
151 | 3. `protocol` refers to the name of the protocol providing resolution for said `resolvedAddress`/`resolvedDomain`.
152 | 4. For address resolutions, the `domainName` property pertains to the domain that the address was resolved for. In most instances this will be just the `domain` passed into the lookup handler. However, the requirement of this property allows a snap to make fuzzy matches and indicate the domain name that was fuzzy matched in the response.
153 | 5. The returned resolution(s) MUST exist on the chain specificed by the `chainId` passed into the handler.
154 |
155 | ## Copyright
156 |
157 | Copyright and related rights waived via [CC0](../LICENSE).
158 |
--------------------------------------------------------------------------------
/SIPS/sip-30.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 30
3 | title: Entropy Source Identifiers
4 | status: Draft
5 | author: Shane T Odlum (@shane-t)
6 | created: 2024-12-17
7 | ---
8 |
9 | ## Abstract
10 |
11 | This SIP proposes additions to entropy retrieval APIs that allows snaps to request entropy from a specific source.
12 |
13 | ## Motivation
14 |
15 | Interoperability snaps and account management snaps use the methods `snap_getEntropy`, `snap_getBip44Entropy`, `snap_getBip32Entropy`, and `snap_getBip32PublicKey` to generate addresses and other key material.
16 |
17 | These methods assume the client contains a single entropy source (the user's primary keyring mnemonic). The proposed API changes will allow snaps to request entropy from a specific source such as a secondary mnemonic. A new method `snap_listEntropySources` will be added to allow snaps to request a list of available entropy sources.
18 |
19 | ## Specification
20 |
21 | > Formal specifications are written in TypeScript.
22 |
23 | ### Language
24 |
25 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",
26 | "NOT RECOMMENDED", "MAY", and "OPTIONAL" written in uppercase in this document are to be interpreted as described in
27 | [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
28 |
29 | ### Common Types
30 |
31 | ```typescript
32 | type SLIP10Node = {
33 | depth: number;
34 | parentFingerprint: number;
35 | index: number;
36 | privateKey: string;
37 | publicKey: string;
38 | chainCode: string;
39 | curve: "ed25519" | "ed25519Bip32" | "secp256k1";
40 | };
41 |
42 | export type BIP44Node = {
43 | coin_type: number;
44 | depth: number;
45 | privateKey: string;
46 | publicKey: string;
47 | chainCode: string;
48 | path: string[];
49 | };
50 |
51 | export type EntropySource = {
52 | name: string;
53 | id: string;
54 | type: "mnemonic";
55 | primary: boolean;
56 | }
57 | ```
58 |
59 | ### Scope
60 |
61 | This SIP applies to snaps that implement the [Keyring API][keyring-api] and any others which use the `snap_getEntropy`, `snap_getBip44Entropy`, `snap_getBip32Entropy`, and `snap_getBip32PublicKey` methods.
62 |
63 | ### Snap Manifest
64 |
65 | No changes are required to the snap manifest.
66 |
67 | ### Client Implementation
68 |
69 | #### Entropy Sources
70 |
71 | Permission to request `snap_listEntropySources` is endowed on any snap that has the permissions `snap_getEntropy`, `snap_getBip44Entropy`, `snap_getBip32Entropy` and/or `snap_getBip32PublicKey`.
72 |
73 | If a snap requests a list of available entropy sources, the client MUST return a list of `EntropySource` objects.
74 |
75 | The client MUST have a primary entropy source, which is used when no source is specified. In the list of available entropy sources, the primary source MUST be marked as `primary: true`.
76 |
77 | #### Handling Entropy Requests
78 |
79 | If a snap requests entropy and includes the `source` parameter for an entropy source of type `mnemonic`, the client MUST return entropy corresponding to that source, if it exists.
80 |
81 | If the source does not exist, the client MUST respond with an error.
82 |
83 | If the request does not include the `source` parameter, the client MUST return entropy from the primary source.
84 |
85 | #### Creating Accounts
86 |
87 | A client MAY invoke the `keyring.createAccount` method with an `entropySource` parameter in the `options` object.
88 |
89 | The `entropySource` parameter MUST be a string which uniquely identifies the entropy source to use. It is not guaranteed to be the same string visible to any other snap, but should always refer to the same source in the context of interactions between the snap and the client.
90 |
91 | ### Snap Implementation
92 |
93 | If a snap is asked to create an account via `keyring.createAccount`, and the `entropySource` parameter is provided, and the snap requires entropy to create an account, the snap SHOULD request the entropy from the specified source.
94 |
95 | ### New RPC Methods
96 |
97 | #### `snap_listEntropySources`
98 |
99 | The method returns an array of `EntropySource` objects, each representing an available entropy source (including the primary source). The Snap MAY choose to display this list to the user.
100 |
101 | ```typescript
102 | const entropySources = await snap.request({
103 | method: "snap_listEntropySources",
104 | });
105 | // [
106 | // { name: "Phrase 1", id: "phrase-1" },
107 | // { name: "Phrase 2", id: "phrase-2" },
108 | // ]
109 | ```
110 |
111 | ### Existing RPC Methods
112 |
113 | #### `snap_getEntropy`
114 |
115 | ##### Parameters
116 |
117 | An object containing:
118 |
119 | - `version` - The number 1.
120 | - `salt` (optional) - An arbitrary string to be used as a salt for the entropy. This can be used to generate different entropy for different purposes.
121 | - `source` (optional) - The ID of the entropy source to use. If not specified, the primary entropy source will be used.
122 |
123 | #### Returns
124 |
125 | The entropy as a hexadecimal string.
126 |
127 | #### Example
128 |
129 | ```typescript
130 | const entropy = await snap.request({
131 | method: "snap_getEntropy",
132 | params: {
133 | version: 1,
134 | salt: "my-salt",
135 | source: "1234-5678-9012-3456-7890",
136 | },
137 | });
138 | // '0x1234567890abcdef'
139 | ```
140 |
141 | #### `snap_getBip32Entropy`
142 |
143 | ##### Parameters
144 |
145 | - `path` - An array starting with `m` containing the BIP-32 derivation path of the key to retrieve.
146 | - `curve` - The curve to use - `ed25519`, `ed25519Bip32` or `secp256k1`.
147 | - `source` (optional) - The ID of the entropy source to use. If not specified, the primary entropy source will be used.
148 |
149 | ##### Returns
150 |
151 | A `SLIP10Node` object representing the BIP-32 HD tree node and containing its corresponding key material.
152 |
153 | ##### Example
154 |
155 | ```typescript
156 | const node = await snap.request({
157 | method: "snap_getBip32Entropy",
158 | params: {
159 | path: ["m", "44", "0", "0", "0"],
160 | source: "1234-5678-9012-3456-7890",
161 | curve: "secp256k1",
162 | },
163 | });
164 | // {
165 | // depth: 5,
166 | // parentFingerprint: 1234567890,
167 | // index: 0,
168 | // privateKey: '0x1234567890abcdef',
169 | // publicKey: '0x1234567890abcdef',
170 | // chainCode: '0x1234567890abcdef',
171 | // curve: 'secp256k1',
172 | // }
173 | ```
174 |
175 | #### `snap_getBip32PublicKey`
176 |
177 | ##### Parameters
178 |
179 | - `path` An array starting with `m` containing the BIP-32 derivation path of the key to retrieve.
180 | - `curve` - The curve to use - `ed25519` or `ed25519Bip32`, `secp256k1`.
181 | - `compressed` (optional) - Whether to return the public key in compressed format. (defaults to `false`)
182 | - `source` (optional) - The ID of the entropy source to use. If not specified, the primary entropy source will be used.
183 |
184 | ##### Returns
185 |
186 | The public key as a hexadecimal string.
187 |
188 | ##### Example
189 |
190 | ```typescript
191 | const publicKey = await snap.request({
192 | method: "snap_getBip32PublicKey",
193 | params: {
194 | path: ["m", "44", "0", "0", "0"],
195 | source: "1234-5678-9012-3456-7890",
196 | curve: "secp256k1",
197 | compressed: true,
198 | },
199 | });
200 | // '0x1234567890abcdef'
201 | ```
202 |
203 | #### `snap_getBip44Entropy`
204 |
205 | ##### Parameters
206 |
207 | An object containing:
208 |
209 | - `coin_type` - The BIP-44 coin type value of the node.
210 | - `source` (optional) - The ID of the entropy source to use. If not specified, the primary entropy source will be used.
211 |
212 | ##### Returns
213 |
214 | A `BIP44Node` object representing the BIP-44 `coin_type` HD tree node and containing its corresponding key material.
215 |
216 | ##### Example
217 |
218 | ```typescript
219 | const node = await snap.request({
220 | method: "snap_getBip44Entropy",
221 | params: {
222 | coin_type: 1,
223 | source: "1234-5678-9012-3456-7890",
224 | },
225 | });
226 | // {
227 | // coin_type: 1,
228 | // depth: 5,
229 | // privateKey: '0x1234567890abcdef',
230 | // publicKey: '0x1234567890abcdef',
231 | // chainCode: '0x1234567890abcdef',
232 | // path: ['m', '44', '0', '0', '0'],
233 | // }
234 | ```
235 |
236 | ## Copyright
237 |
238 | Copyright and related rights waived via [CC0](../LICENSE).
239 |
240 | [keyring-api]: https://github.com/MetaMask/accounts/tree/main/packages/keyring-api
--------------------------------------------------------------------------------
/SIPS/sip-16.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 16
3 | title: Signature Insights
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/118
6 | author: Christian Montoya (@Montoya), Hassan Malik (@hmalik88)
7 | created: 2023-11-01
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP proposes a way for Snaps to provide "insights" into signatures requested by dapps. These insights can then be displayed in the MetaMask confirmation UI, helping the user to make informed decisions before signing messages.
13 |
14 | Example use cases for signature insights are phishing detection, scam prevention and signature simulation.
15 |
16 | This SIP closely follows [SIP-3: Transaction Insights](sip-3.md) and [SIP-11: Transaction insight severity levels](sip-11.md).
17 |
18 | ## Motivation
19 |
20 | One of the most difficult problems blockchain wallets solve for their users is "signature comprehension," i.e. making cryptographic signature inputs intelligible to the user.
21 | A signature in the wrong hands can give an attacker the ability to steal user assets.
22 | A single wallet may not be able to provide all relevant information to any given user for any given signature request.
23 | To alleviate this problem, this SIP aims to expand the kinds of information MetaMask provides to a user before signing a message.
24 |
25 | The current Snaps API in the MetaMask extension already has a "transaction insights" feature that allows Snaps to decode transactions and provide insights to users.
26 | These transaction insights can also specify a transaction severity level to provide extra friction in the transaction confirmation.
27 | To expand on this feature, this SIP allows the community to build Snaps that provide arbitrary "insights" into signatures in a similar manner.
28 | These insights can then be displayed in the MetaMask UI alongside any information provided by MetaMask itself.
29 | Signature insight Snaps can also return a `severity` key to indicate the severity level of the content being returned.
30 | This key uses the same `SeverityLevel` enum used by transaction insight Snaps.
31 |
32 | ## Specification
33 |
34 | > Formal specifications are written in Typescript. Usage of `CAIP-N` specifications, where `N` is a number, are references to [Chain Agnostic Improvement Proposals](https://github.com/ChainAgnostic/CAIPs).
35 |
36 | ### Language
37 |
38 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
39 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
40 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
41 |
42 | ### Definitions
43 |
44 | > This section is non-normative, and merely recapitulates some definitions from [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md).
45 |
46 | - `ChainId` - a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) string.
47 | It identifies a specific chain among all blockchains recognized by the CAIP standards. The ChainId refers to the network that the signing account is connected to. It is not necessarily the network where the signed message will be broadcast.
48 | - `ChainId` consists of a `Namespace` and a `Reference`
49 | - `Namespace` - a class of similar blockchains. For example EVM-based blockchains.
50 | - `Reference` - a way to identify a concrete chain inside a `Namespace`. For example Ethereum Mainnet or one of its test networks.
51 | - `Method` - a string which can be any of the following 4 [signing methods](https://docs.metamask.io/wallet/concepts/signing-methods/) supported by the MetaMask wallet API. The set of possible signature types may expand in the future as new signing methods are supported. The signing method is required by MetaMask for the dapp to make a signature request. It is not part of the signature payload.
52 | 1. `eth_signTypedData_v4`
53 | 2. `eth_signTypedData_v3`
54 | 3. `eth_signTypedData_v1`
55 | 4. `personal_sign`
56 |
57 | ### Snap Manifest
58 |
59 | This SIP specifies a permission named `endowment:signature-insight`.
60 | The permission grants a Snap read-only access to raw signature payloads, before they are accepted for signing by the user.
61 |
62 | The permission is specified as follows in `snap.manifest.json` files:
63 |
64 | ```json
65 | {
66 | "initialPermissions": {
67 | "endowment:signature-insight": {}
68 | }
69 | }
70 | ```
71 |
72 | The permission includes an OPTIONAL caveat `allowSignatureOrigin`.
73 | The caveat grants a Snap read-only access to the URL requesting the signature.
74 | It can be specified as follows:
75 |
76 | ```json
77 | {
78 | "initialPermissions": {
79 | "endowment:signature-insight": {
80 | "allowSignatureOrigin": true
81 | }
82 | },
83 | }
84 | ```
85 |
86 | ### Snap Implementation
87 |
88 | The following is an example implementation of the API:
89 |
90 | ```typescript
91 | import { OnSignatureHandler, SeverityLevel } from "@metamask/snaps-sdk";
92 |
93 | export const onSignature: OnSignatureHandler = async ({
94 | signature: Record,
95 | signatureOrigin: string, /* If allowSignatureOrigin is set to true */
96 | }) => {
97 | const content = /* Get UI component with insights */;
98 | const isContentCritical = /* Boolean checking if content is critical */
99 | return isContentCritical ? { content, severity: SeverityLevel.Critical } : { content };
100 | };
101 | ```
102 | The interface for an `onSignature` handler function’s arguments is:
103 |
104 | ```typescript
105 | interface OnSignatureArgs {
106 | signature: Record;
107 | signatureOrigin?: string;
108 | }
109 | ```
110 |
111 | `signature` - The signature object is intentionally not defined in this SIP because different chains may specify different signature formats.
112 | It is beyond the scope of the SIP standards to define interfaces for every chain.
113 | Instead, it is the Snap developer's responsibility to be cognizant of the shape of signature objects for relevant chains.
114 | Nevertheless, you can refer to [Appendix I](#appendix-i-ethereum-signature-objects) for the interfaces of the Ethereum signature objects available in MetaMask at the time of this SIP's creation.
115 |
116 | `signatureOrigin` - The URL origin of the signature request. The existence of this property is dependent on the `allowSignatureOrigin` caveat.
117 |
118 |
119 | The interface for the return value of an `onSignature` export is:
120 |
121 | ```typescript
122 | interface OnSignatureResponse {
123 | content: Component | null;
124 | severity?: SeverityLevel;
125 | }
126 | ```
127 |
128 | **Note:** `severity` is an OPTIONAL field and the omission of such means that there is no escalation of the content being returned.
129 |
130 | ## Specification
131 |
132 | Please see [SIP-3](sip-3.md) for more information on the original transaction insights API.
133 |
134 | Please see [SIP-7](sip-7.md) for more information on the `Component` type returned in the `OnTransactionResponse`.
135 |
136 | Please see [SIP-11](sip-3.md) for more information on Transaction insight severity levels.
137 |
138 | ## Appendix I: Ethereum Signature Objects
139 |
140 | The following signature objects may appear for any `chainId` of `eip155:*` where `*` is some positive integer. This includes all Ethereum or "EVM-compatible" chains. As of the time of the creation of this SIP, they are the only possible signature objects for Ethereum chains.
141 |
142 | ### personal_sign
143 |
144 | ```typescript
145 | interface PersonalSignature {
146 | from: string;
147 | data: string;
148 | signatureMethod: 'personal_sign';
149 | }
150 | ```
151 |
152 | ### eth_signTypedData
153 |
154 | ```typescript
155 | interface SignTypedDataSignature {
156 | from: string;
157 | data: Record[];
158 | signatureMethod: 'eth_signTypedData';
159 | }
160 | ```
161 |
162 | ### eth_signTypedData_v3
163 |
164 | ```typescript
165 | interface SignTypedDataV3Signature {
166 | from: string;
167 | data: Record;
168 | signatureMethod: 'eth_signTypedData_v3';
169 | }
170 | ```
171 |
172 | ### eth_signTypedData_v4
173 |
174 | ```typescript
175 | interface SignTypedDataV4Signature {
176 | from: string;
177 | data: Record;
178 | signatureMethod: 'eth_signTypedData_v4';
179 | }
180 | ```
181 |
182 | **Note**: The `signatureMethod` property is MetaMask specific and not reflective of the standards defining the underlying signature methods. `signatureMethod` SHOULD be used by the signature insight snap as the source of the truth to identify the signature scheme it is providing insights for.
183 |
184 | ## Copyright
185 |
186 | Copyright and related rights waived via [CC0](../LICENSE).
187 |
--------------------------------------------------------------------------------
/_includes/anchor_headings.html:
--------------------------------------------------------------------------------
1 | {% capture headingsWorkspace %}
2 | {% comment %}
3 | Copyright (c) 2018 Vladimir "allejo" Jimenez
4 |
5 | Permission is hereby granted, free of charge, to any person
6 | obtaining a copy of this software and associated documentation
7 | files (the "Software"), to deal in the Software without
8 | restriction, including without limitation the rights to use,
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following
12 | conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 | OTHER DEALINGS IN THE SOFTWARE.
25 | {% endcomment %}
26 | {% comment %}
27 | Version 1.0.12
28 | https://github.com/allejo/jekyll-anchor-headings
29 |
30 | "Be the pull request you wish to see in the world." ~Ben Balter
31 |
32 | Usage:
33 | {% include anchor_headings.html html=content anchorBody="#" %}
34 |
35 | Parameters:
36 | * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
37 |
38 | Optional Parameters:
39 | * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
40 | * headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`;
41 | the `%heading%` and `%html_id%` placeholders are available
42 | * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `` tag; you may NOT use `href`, `class` or `title`;
43 | the `%heading%` and `%html_id%` placeholders are available
44 | * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
45 | * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
46 | * anchorTitle (string) : '' - The `title` attribute that will be used for anchors
47 | * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
48 | * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
49 | * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
50 | * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
51 | * generateId (true) : false - Set to true if a header without id should generate an id to use.
52 |
53 | Output:
54 | The original HTML with the addition of anchors inside of all of the h1-h6 headings.
55 | {% endcomment %}
56 |
57 | {% assign minHeader = include.h_min | default: 1 %}
58 | {% assign maxHeader = include.h_max | default: 6 %}
59 | {% assign beforeHeading = include.beforeHeading %}
60 | {% assign headerAttrs = include.headerAttrs %}
61 | {% assign nodes = include.html | split: '
76 | {% if headerLevel == 0 %}
77 |
78 | {% assign firstChunk = node | split: '>' | first %}
79 |
80 |
81 | {% unless firstChunk contains '<' %}
82 | {% capture node %}{% endcapture %}
90 | {% assign _workspace = node | split: _closingTag %}
91 | {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
92 | {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
93 | {% assign escaped_header = header | strip_html | strip %}
94 |
95 | {% assign _classWorkspace = _workspace[0] | split: 'class="' %}
96 | {% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
97 | {% assign _html_class = _classWorkspace[0] %}
98 |
99 | {% if _html_class contains "no_anchor" %}
100 | {% assign skip_anchor = true %}
101 | {% else %}
102 | {% assign skip_anchor = false %}
103 | {% endif %}
104 |
105 | {% assign _idWorkspace = _workspace[0] | split: 'id="' %}
106 | {% if _idWorkspace[1] %}
107 | {% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
108 | {% assign html_id = _idWorkspace[0] %}
109 | {% elsif include.generateId %}
110 |
111 | {% assign html_id = escaped_header | slugify %}
112 | {% if html_id == "" %}
113 | {% assign html_id = false %}
114 | {% endif %}
115 | {% capture headerAttrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %}
116 | {% endif %}
117 |
118 |
119 | {% capture anchor %}{% endcapture %}
120 |
121 | {% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
122 | {% if headerAttrs %}
123 | {% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ headerAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %}
124 | {% endif %}
125 |
126 | {% capture anchor %}href="#{{ html_id }}"{% endcapture %}
127 |
128 | {% if include.anchorClass %}
129 | {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
130 | {% endif %}
131 |
132 | {% if include.anchorTitle %}
133 | {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %}
134 | {% endif %}
135 |
136 | {% if include.anchorAttrs %}
137 | {% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %}
138 | {% endif %}
139 |
140 | {% capture anchor %}{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}{% endcapture %}
141 |
142 |
143 | {% if beforeHeading %}
144 | {% capture anchor %}{{ anchor }} {% endcapture %}
145 | {% else %}
146 | {% capture anchor %} {{ anchor }}{% endcapture %}
147 | {% endif %}
148 | {% endif %}
149 |
150 | {% capture new_heading %}
151 |
160 | {% endcapture %}
161 |
162 |
165 | {% assign chunkCount = _workspace | size %}
166 | {% if chunkCount > 1 %}
167 | {% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
168 | {% endif %}
169 |
170 | {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
171 | {% endfor %}
172 | {% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
173 |
--------------------------------------------------------------------------------
/SIPS/sip-6.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 6
3 | title: Deterministic Snap-specific entropy
4 | status: Draft
5 | discussions-to: https://github.com/MetaMask/SIPs/discussions/69
6 | author: Maarten Zuidhoorn (@Mrtenz)
7 | created: 2022-10-27
8 | ---
9 |
10 | ## Abstract
11 |
12 | This SIP describes a way for Snaps to get some deterministic Snap-specific entropy, based on the secret recovery phrase
13 | of the user and the snap ID. Since Snaps do not have access to the secret recovery phrase directly, other Snaps are
14 | unable to get the same entropy.
15 |
16 | Snaps can optionally specify a salt in order to generate different entropy for different use cases, for example, some
17 | entropy to derive new private keys from, and some other entropy to encrypt some data.
18 |
19 | The entropy can be accessed from within a Snap, using the `snap_getEntropy` JSON-RPC method.
20 |
21 | ## Motivation
22 |
23 | Before this SIP, Snaps did not have a way to get some kind of deterministic entropy, without storing it on the disk. If
24 | the user deletes the Snap, or deletes the data of MetaMask, this entropy is lost. This SIP proposes a new way to get
25 | deterministic entropy, which is tied to the secret recovery phrase of the user, combined with the Snap ID. In this case,
26 | the entropy can always be re-created, as long as the user has a copy of their secret recovery phrase.
27 |
28 | ## Specification
29 |
30 | > Formal specifications are written in TypeScript.
31 |
32 | ### Language
33 |
34 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",
35 | "NOT RECOMMENDED", "MAY", and "OPTIONAL" written in uppercase in this document are to be interpreted as described in
36 | [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
37 |
38 | ### Deriving the entropy
39 |
40 | Entropy is derived using a
41 | [BIP 32](https://github.com/bitcoin/bips/blob/6545b81022212a9f1c814f6ce1673e84bc02c910/bip-0032.mediawiki) derivation
42 | path. This derivation path MUST start with the magic value `0xd36e6170` (`1399742832'` in BIP 32 notation). The
43 | following eight indices are based on a hash of the Snap ID, with an optional salt. The hashing algorithm of choice is
44 | `keccak256`.
45 |
46 | To prevent other Snaps from getting the same entropy, Snaps MUST NOT be able to manually derive using the magic value
47 | `0xd36e6170`, i.e., through the `snap_getBip32Entropy` JSON-RPC method.
48 |
49 | The hash of the Snap ID is calculated as follows:
50 |
51 | ```typescript
52 | const hash = keccak256(snapId + keccak256(salt));
53 | ```
54 |
55 | If the salt is not provided, an empty string MUST be used instead:
56 |
57 | ```typescript
58 | const hash = keccak256(snapId + keccak256(''));
59 | ```
60 |
61 | The hash is then split into eight big endian `uint32 | 0x80000000` integers. The resulting derivation path is a
62 | combination of the magic value, and the eight integers:
63 |
64 | ```typescript
65 | const computedDerivationPath = getUin32Array(hash).map((index) => (index | 0x80000000) >>> 0);
66 | const derivationPath = [0xd36e6170, ...computedDerivationPath];
67 | ```
68 |
69 | The entropy is then derived using the secret recovery phrase of the user, and the derivation path. The derivation
70 | algorithm of choice is `secp256k1`. The entropy is the private key of the derived key pair.
71 |
72 | ```typescript
73 | const { privateKey: entropy } = bip32Derive(secretRecoveryPhrase, derivationPath);
74 | ```
75 |
76 | `bip32Derive` is defined as the `CKDpriv` function in
77 | [BIP 32](https://github.com/bitcoin/bips/blob/6545b81022212a9f1c814f6ce1673e84bc02c910/bip-0032.mediawiki), using a root
78 | node created as per
79 | [BIP 39](https://github.com/bitcoin/bips/blob/6545b81022212a9f1c814f6ce1673e84bc02c910/bip-0039.mediawiki).
80 |
81 | ### `snap_getEntropy` JSON-RPC method
82 |
83 | The `snap_getEntropy` JSON-RPC method is used to get the entropy for a Snap. It takes an object as parameters, which has
84 | the following properties:
85 |
86 | - `version` (required, `number`): A version number, which MUST be the number `1`.
87 | - `salt` (optional, `string`): A salt to use when deriving the entropy. If provided, this MUST be interpreted as a UTF-8
88 | string value. If not provided, an empty string MUST be used instead.
89 |
90 | ```json
91 | {
92 | "method": "snap_getEntropy",
93 | "params": {
94 | "version": 1,
95 | "salt": "foo"
96 | }
97 | }
98 | ```
99 |
100 | The method returns a `string` containing the entropy, encoded as a hexadecimal string.
101 |
102 | ## Reference implementation
103 |
104 | ```typescript
105 | import { SLIP10Node } from '@metamask/key-tree';
106 | import { concatBytes, stringToBytes } from '@metamask/utils';
107 | import { keccak_256 as keccak256 } from '@noble/hashes/sha3';
108 |
109 | const MAGIC_VALUE = 0xd36e6170;
110 | const HARDENED_VALUE = 0x80000000;
111 |
112 | /**
113 | * Get an array of `uint32 | 0x80000000` values from a hash. The hash is assumed
114 | * to be 32 bytes long.
115 | *
116 | * @param hash - The hash to derive indices from.
117 | * @returns The derived indices.
118 | */
119 | const getUint32Array = (hash: Uint8Array) => {
120 | const array = [];
121 | const view = new DataView(hash.buffer, hash.byteOffset, hash.byteLength);
122 |
123 | for (let index = 0; index < 8; index++) {
124 | const uint32 = view.getUint32(index * 4);
125 | array.push((uint32 | HARDENED_VALUE) >>> 0);
126 | }
127 |
128 | return array;
129 | };
130 |
131 | /**
132 | * Get a BIP-32 derivation path, compatible with `@metamask/key-tree`, from an
133 | * array of indices. The indices are assumed to be a `uint32 | 0x80000000`.
134 | *
135 | * @param indices - The indices to get the derivation path for.
136 | * @returns The derivation path.
137 | */
138 | const getDerivationPath = (indices: number[]) => {
139 | return indices.map((index) => `bip32:${index - HARDENED_VALUE}'` as const);
140 | };
141 |
142 | /**
143 | * Derive deterministic Snap-specific entropy from a mnemonic phrase. The
144 | * snap ID and salt are used to derive a BIP-32 derivation path, which is then
145 | * used to derive a private key from the mnemonic phrase.
146 | *
147 | * The derived private key is returned as entropy.
148 | *
149 | * @param mnemonicPhrase - The mnemonic phrase to derive entropy from.
150 | * @param snapId - The ID of the Snap.
151 | * @param salt - An optional salt to use in the derivation. If not provided, an
152 | * empty string is used.
153 | * @returns The derived entropy.
154 | */
155 | const getEntropy = async (
156 | mnemonicPhrase: string,
157 | snapId: string,
158 | salt = ''
159 | ): Promise => {
160 | const snapIdBytes = stringToBytes(snapId);
161 | const saltBytes = stringToBytes(salt);
162 |
163 | // Get the derivation path from the snap ID.
164 | const hash = keccak256(concatBytes([snapIdBytes, keccak256(saltBytes)]));
165 | const computedDerivationPath = getUint32Array(hash);
166 |
167 | // Derive the private key using BIP-32.
168 | const { privateKey } = await SLIP10Node.fromDerivationPath({
169 | derivationPath: [
170 | `bip39:${mnemonicPhrase}`,
171 | ...getDerivationPath([MAGIC_VALUE, ...computedDerivationPath]),
172 | ],
173 | curve: 'secp256k1',
174 | });
175 |
176 | if (!privateKey) {
177 | throw new Error('Failed to derive private key.');
178 | }
179 |
180 | return privateKey;
181 | };
182 | ```
183 |
184 | ## Test vectors
185 |
186 | These test vectors are generated using the reference implementation, and the following mnemonic phrase:
187 |
188 | ```
189 | test test test test test test test test test test test ball
190 | ```
191 |
192 | ### Test vector 1
193 |
194 | ```json
195 | {
196 | "snapId": "foo",
197 | "derivationPath": "m/1399742832'/1323571613'/1848851859'/458888073'/1339050117'/513522582'/1371866341'/2121938770'/1014285256'",
198 | "entropy": "0x8bbb59ec55a4a8dd5429268e367ebbbe54eee7467c0090ca835c64d45c33a155"
199 | }
200 | ```
201 |
202 | ### Test vector 2
203 |
204 | ```json
205 | {
206 | "snapId": "bar",
207 | "derivationPath": "m/1399742832'/767024459'/1206550137'/1427647479'/1048031962'/1656784813'/1860822351'/1362389435'/2133253878'",
208 | "entropy": "0xbdae5c0790d9189d8ae27fd4860b3b57bab420b6594c420ae9ae3a9f87c1ea14"
209 | }
210 | ```
211 |
212 | ### Test vector 3
213 |
214 | ```json
215 | {
216 | "snapId": "foo",
217 | "salt": "bar",
218 | "derivationPath": "m/1399742832'/2002032866'/301374032'/1159533269'/453247377'/187127851'/1859522268'/152471137'/187531423'",
219 | "entropy": "0x59cbec1fa877ecb38d88c3a2326b23bff374954b39ad9482c9b082306ac4b3ad"
220 | }
221 | ```
222 |
223 | ### Test vector 4
224 |
225 | ```json
226 | {
227 | "snapId": "bar",
228 | "salt": "baz",
229 | "derivationPath": "m/1399742832'/734358031'/701613791'/1618075622'/1535938847'/1610213550'/18831365'/356906080'/2095933563'",
230 | "entropy": "0x814c1f121eb4067d1e1d177246461e8a1cc6a1b1152756737aba7fa9c2161ba2"
231 | }
232 | ```
233 |
234 | ## Copyright
235 |
236 | Copyright and related rights waived via [CC0](../LICENSE).
237 |
--------------------------------------------------------------------------------
/_includes/toc.html:
--------------------------------------------------------------------------------
1 | {% capture tocWorkspace %}
2 | {% comment %}
3 | Copyright (c) 2017 Vladimir "allejo" Jimenez
4 |
5 | Permission is hereby granted, free of charge, to any person
6 | obtaining a copy of this software and associated documentation
7 | files (the "Software"), to deal in the Software without
8 | restriction, including without limitation the rights to use,
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following
12 | conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 | OTHER DEALINGS IN THE SOFTWARE.
25 | {% endcomment %}
26 | {% comment %}
27 | Version 1.2.0
28 | https://github.com/allejo/jekyll-toc
29 |
30 | "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe
31 |
32 | Usage:
33 | {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %}
34 |
35 | Parameters:
36 | * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
37 |
38 | Optional Parameters:
39 | * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC
40 | * class (string) : '' - a CSS class assigned to the TOC
41 | * id (string) : '' - an ID to assigned to the TOC
42 | * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored
43 | * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored
44 | * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list
45 | * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level
46 | * submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level
47 | * base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content
48 | * anchor_class (string) : '' - add custom class(es) for each anchor element
49 | * skip_no_ids (bool) : false - skip headers that do not have an `id` attribute
50 |
51 | Output:
52 | An ordered or unordered list representing the table of contents of a markdown block. This snippet will only
53 | generate the table of contents and will NOT output the markdown given to it
54 | {% endcomment %}
55 |
56 | {% capture newline %}
57 | {% endcapture %}
58 | {% assign newline = newline | rstrip %}
59 |
60 | {% capture deprecation_warnings %}{% endcapture %}
61 |
62 | {% if include.baseurl %}
63 | {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %}
64 | {% endif %}
65 |
66 | {% if include.skipNoIDs %}
67 | {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %}
68 | {% endif %}
69 |
70 | {% capture jekyll_toc %}{% endcapture %}
71 | {% assign orderedList = include.ordered | default: false %}
72 | {% assign baseURL = include.base_url | default: include.baseurl | default: '' %}
73 | {% assign skipNoIDs = include.skip_no_ids | default: include.skipNoIDs | default: false %}
74 | {% assign minHeader = include.h_min | default: 1 %}
75 | {% assign maxHeader = include.h_max | default: 6 %}
76 | {% assign nodes = include.html | strip | split: ' maxHeader %}
92 | {% continue %}
93 | {% endif %}
94 |
95 | {% assign _workspace = node | split: '' | first }}>{% endcapture %}
114 | {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
115 |
116 | {% if include.item_class and include.item_class != blank %}
117 | {% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %}
118 | {% endif %}
119 |
120 | {% if include.submenu_class and include.submenu_class != blank %}
121 | {% assign subMenuLevel = currLevel | minus: 1 %}
122 | {% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %}
123 | {% endif %}
124 |
125 | {% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %}
126 |
127 | {% if htmlID %}
128 | {% capture anchorAttributes %} href="{% if baseURL %}{{ baseURL }}{% endif %}#{{ htmlID }}"{% endcapture %}
129 |
130 | {% if include.anchor_class %}
131 | {% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %}
132 | {% endif %}
133 |
134 | {% capture listItem %}{{ anchorBody }}{% endcapture %}
135 | {% elsif skipNoIDs == true %}
136 | {% continue %}
137 | {% else %}
138 | {% capture listItem %}{{ anchorBody }}{% endcapture %}
139 | {% endif %}
140 |
141 | {% if currLevel > lastLevel %}
142 | {% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %}
143 | {% elsif currLevel < lastLevel %}
144 | {% assign repeatCount = lastLevel | minus: currLevel %}
145 |
146 | {% for i in (1..repeatCount) %}
147 | {% capture jekyll_toc %}{{ jekyll_toc }}{{ listModifier }}>{% endcapture %}
148 | {% endfor %}
149 |
150 | {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %}
151 | {% else %}
152 | {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %}
153 | {% endif %}
154 |
155 | {% capture jekyll_toc %}{{ jekyll_toc }}
{{ listModifier }}>{% endcapture %}
165 | {% endfor %}
166 |
167 | {% if jekyll_toc != '' %}
168 | {% assign rootAttributes = '' %}
169 | {% if include.class and include.class != blank %}
170 | {% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %}
171 | {% endif %}
172 |
173 | {% if include.id and include.id != blank %}
174 | {% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %}
175 | {% endif %}
176 |
177 | {% if rootAttributes %}
178 | {% assign nodes = jekyll_toc | split: '>' %}
179 | {% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %}
180 | {% endif %}
181 | {% endif %}
182 | {% endcapture %}{% assign tocWorkspace = '' %}{{ deprecation_warnings }}{{ jekyll_toc -}}
183 |
--------------------------------------------------------------------------------
/SIPS/sip-20.md:
--------------------------------------------------------------------------------
1 | ---
2 | sip: 20
3 | title: WebSockets
4 | status: Draft
5 | author: David Drazic (@david0xd), Frederik Bolding (@frederikbolding), Guillaume Roux (@guillaumerx)
6 | created: 2023-12-15
7 | updated: 2025-06-05
8 | ---
9 |
10 | ## Abstract
11 | 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.
12 |
13 | ## Motivation
14 | 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.
15 |
16 | ## Specification
17 |
18 | > Formal specifications are written in TypeScript.
19 |
20 | ### Language
21 |
22 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
23 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
24 | "OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
25 |
26 | ### Top-level architecture
27 |
28 | 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.
29 |
30 | The client has multiple responsibilities in this architecture:
31 |
32 | - 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.
33 |
34 | - Any incoming message from WebSocket connections MUST be handled by the client and forwarded to the appropriate Snap through `onWebSocketEvent`.
35 |
36 | - 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.
37 |
38 | - 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.
39 |
40 | - 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.
41 |
42 | - 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`.
43 |
44 |
45 | ### Snap Manifest
46 |
47 | This SIP doesn't introduce any new permissions, but rather extends `endowment:network-access` with new capabilities.
48 |
49 | ### RPC Methods
50 |
51 | #### `snap_openWebSocket`
52 |
53 | 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.
54 |
55 | ```typescript
56 |
57 | type OpenWebSocketParams = {
58 | url: string;
59 | protocols?: string[];
60 | };
61 | ```
62 | The RPC method takes two parameters:
63 |
64 | - `url` - The URL of the WebSocket service to connect to.
65 | - The URL MUST be a valid WebSocket URL, starting with `wss://` or `ws://`.
66 | - 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.
67 | - `protocols` - An optional array of subprotocols to use for the WebSocket connection.
68 |
69 | An example of usage is given below.
70 |
71 | ```typescript
72 | snap.request({
73 | method: "snap_openWebSocket",
74 | params: {
75 | url: "wss://example.com/websocket",
76 | protocols: ["soap", "wamp"],
77 | },
78 | });
79 |
80 | ```
81 |
82 | #### `snap_closeWebSocket`
83 | This method allows a Snap to close an existing WebSocket connection. The method takes the unique identifier of the connection as a parameter.
84 |
85 | ```typescript
86 | type CloseWebSocketParams = {
87 | id: string;
88 | };
89 | ```
90 | The RPC method takes one parameter:
91 | - `id` - The unique identifier of the WebSocket connection to close. This identifier is returned by the `snap_openWebSocket` method.
92 |
93 | An example of usage is given below.
94 |
95 | ```typescript
96 | snap.request({
97 | method: "snap_closeWebSocket",
98 | params: {
99 | id: "unique-connection-id",
100 | },
101 | });
102 | ```
103 | #### `snap_sendWebSocketMessage`
104 | 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.
105 |
106 | ```typescript
107 | type SendWebSocketMessageParams = {
108 | id: string;
109 | message: string | number[];
110 | };
111 | ```
112 |
113 | The RPC method takes two parameters:
114 | - `id` - The unique identifier of the WebSocket connection to send the message over. This identifier is returned by the `snap_openWebSocket` method.
115 | - `message` - The message to send over the WebSocket connection. It can be either a string or an array of bytes.
116 |
117 | An example of usage is given below.
118 |
119 | ```typescript
120 | snap.request({
121 | method: "snap_sendWebSocketMessage",
122 | params: {
123 | id: "unique-connection-id",
124 | message: "Hello, WebSocket!",
125 | },
126 | });
127 | ```
128 | #### `snap_getWebSockets`
129 | 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.
130 |
131 | - `id` - The unique identifier of the WebSocket connection.
132 | - `url` - The URL of the WebSocket connection.
133 | - `protocols` - The optional protocols of the WebSocket connection.
134 |
135 | ```typescript
136 | type WebSocketConnection = {
137 | id: string;
138 | url: string;
139 | protocols?: string[];
140 | };
141 |
142 | type GetWebSocketsResult = WebSocketConnection[];
143 | ```
144 |
145 | An example of usage is given below.
146 |
147 | ```typescript
148 | snap.request({
149 | method: "snap_getWebSockets",
150 | })
151 | ```
152 |
153 |
154 | ### Handling WebSocket Events
155 |
156 | 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.
157 |
158 | ```typescript
159 | import { OnWebSocketEventHandler } from "@metamask/snap-sdk";
160 |
161 | export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => {
162 | switch (event.type) {
163 | case "message":
164 | // Handle incoming message
165 | console.log(`Message received from ${event.origin}:`, event.data);
166 | break;
167 | case "open":
168 | // Handle connection opened
169 | console.log(`WebSocket connection opened with ID ${event.id} from ${event.origin}`);
170 | break;
171 | case "close":
172 | // Handle connection closed
173 | console.log(`WebSocket connection closed with ID ${event.id} code ${event.code} and reason ${event.reason} from ${event.origin}`);
174 | break;
175 | }
176 | };
177 | ```
178 |
179 | ```typescript
180 | type OnWebSocketEventArgs = {
181 | event: WebSocketEvent;
182 | };
183 | ```
184 |
185 | ```typescript
186 | type OnWebSocketEventResponse = void;
187 | ```
188 |
189 | #### Event Types
190 |
191 | The type for the `onWebSocketEvent` handler function's arguments.
192 |
193 | ```typescript
194 | export type WebSocketEvent =
195 | | WebSocketMessage
196 | | WebSocketOpenEvent
197 | | WebSocketCloseEvent;
198 | ```
199 |
200 | ##### Message Event
201 |
202 | This event is triggered when a message is received over a WebSocket connection. The message can be either text or binary data.
203 |
204 | ```typescript
205 | export type WebSocketTextMessage = {
206 | type: "text";
207 | message: string;
208 | };
209 |
210 | export type WebSocketBinaryMessage = {
211 | type: "binary";
212 | message: number[];
213 | };
214 |
215 | export type WebSocketMessageData =
216 | | WebSocketTextMessage
217 | | WebSocketBinaryMessage;
218 |
219 | export type WebSocketMessage = {
220 | type: "message";
221 | id: string;
222 | origin: string;
223 | data: WebSocketMessageData;
224 | };
225 | ```
226 |
227 | `type` - The type of the WebSocket event, which is `"message"` for this event type.
228 |
229 | `id` - The unique identifier of the WebSocket connection associated with the event.
230 |
231 | `origin` - The origin of the WebSocket event.
232 |
233 | `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.
234 |
235 | ##### Connection opened event
236 |
237 | 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.
238 |
239 | ```typescript
240 | export type WebSocketOpenEvent = {
241 | type: "open";
242 | id: string;
243 | origin: string;
244 | };
245 | ```
246 | `type` - The type of the WebSocket event, which is `"open"` for this event type.
247 |
248 | `id` - The unique identifier of the WebSocket connection associated with the event.
249 |
250 | `origin` - The origin of the WebSocket connection.
251 |
252 | ##### Connection closed event
253 |
254 | This event is triggered when a WebSocket connection is closed. It provides the unique identifier of the connection and the origin of the event.
255 |
256 | ```typescript
257 | export type WebSocketCloseEvent = {
258 | type: "close";
259 | id: string;
260 | origin: string;
261 | code: number;
262 | reason: string | null;
263 | wasClean: boolean | null;
264 | };
265 | ```
266 |
267 | `type` - The type of the WebSocket event, which is `"close"` for this event type.
268 |
269 | `id` - The unique identifier of the WebSocket connection associated with the event.
270 |
271 | `origin` - The origin of the WebSocket connection.
272 |
273 | `code` - The numeric code indicating the reason for the closure of the WebSocket connection. This is a standard WebSocket close code.
274 |
275 | `reason` - A string providing a human-readable explanation of why the WebSocket connection was closed.
276 |
277 | `wasClean` - A boolean indicating whether the connection was closed cleanly (i.e., without any errors).
278 |
279 | ## Copyright
280 |
281 | Copyright and related rights waived via [CC0](../LICENSE).
282 |
--------------------------------------------------------------------------------