Skip to content

Commit

Permalink
Merge branch 'master' into 5-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandratran committed Jan 31, 2023
2 parents f7cdc95 + 8f38ff7 commit 2dd1c66
Show file tree
Hide file tree
Showing 19 changed files with 1,219 additions and 770 deletions.
20 changes: 10 additions & 10 deletions api-sdk-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,6 @@ const sidebar = {
"how-to/migrate-api",
],
},
{
type: "category",
label: "Reference",
link: { type: "generated-index" },
items: [
"reference/provider-api",
"reference/rpc-api",
"reference/sdk-js-options",
],
},
{
type: "category",
label: "Concepts",
Expand All @@ -78,6 +68,16 @@ const sidebar = {
"tutorials/create-simple-dapp-with-sdk",
],
},
{
type: "category",
label: "Reference",
link: { type: "generated-index" },
items: [
"reference/provider-api",
"reference/rpc-api",
"reference/sdk-js-options",
],
},
],
};

Expand Down
38 changes: 34 additions & 4 deletions snaps-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,41 @@ const sidebar = {
"index",
{
type: "category",
label: "How To",
label: "Get started",
link: { type: "generated-index" },
collapsed: false,
items: [
"how-to/guide",
"how-to/patching-dependencies",
"get-started/install-snaps",
"get-started/quickstart",
],
},
{
type: "category",
label: "How to",
link: { type: "generated-index" },
items: [
"how-to/develop-a-snap",
"how-to/request-permissions",
"how-to/troubleshoot",
],
},
{
type: "category",
label: "Concepts",
link: { type: "generated-index" },
items: [
"concepts/anatomy",
"concepts/lifecycle",
"concepts/user-interface",
"concepts/execution-environment",
],
},
{
type: "category",
label: "Tutorials",
link: { type: "generated-index" },
items: [
"tutorials/tutorials",
],
},
{
Expand All @@ -32,8 +62,8 @@ const sidebar = {
link: { type: "generated-index" },
items: [
"reference/rpc-api",
"reference/permissions",
"reference/exports",
"reference/options",
],
},
],
Expand Down
209 changes: 209 additions & 0 deletions snaps/concepts/anatomy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Snap anatomy

If you look at the directory structure of the
[Snaps template repository](https://github.com/MetaMask/template-snap-monorepo) used in the
[Snaps quickstart](../get-started/quickstart.md), it looks something like this:

```text
template-snap-monorepo/
├─ packages/
│ ├─ site/
| | |- src/
| | | |- App.tsx
| | ├─ package.json
| | |- ...(react app content)
| |
│ ├─ snap/
| | ├─ src/
| | | |- index.ts
| | ├─ snap.manifest.json
| | ├─ package.json
| | |- ... (snap content)
├─ package.json
├─ ... (other stuff)
```

Source files other than `index.js` are located through its imports.
The defaults can be overwritten in the [configuration file](#configuration-file).

:::tip Create a snap project
When you create a new snap project using `mm-snap init`, it has all these files.
Still, we recommend
[cloning the template snap repository to get started](../get-started/quickstart.md).
:::

This page examines the major components of a snap:

- [The source code](#source-code)
- [The manifest file](#manifest-file)
- [The configuration file](#configuration-file)
- [The bundle file](#bundle-file)

## Source code

If you're familiar with JavaScript or TypeScript development, developing a snap might feel familiar
to you.
Consider this simple snap, `hello-snap`:

```javascript
module.exports.onRpcRequest = async ({ origin, request }) => {
switch (request.method) {
case 'hello':
return 'world!';

default:
throw new Error('Method not found.');
}
};
```

To communicate with the outside world, the snap must implement its own JSON-RPC API by exposing
the exported function [`onRpcRequest`](../reference/exports.md#onrpcrequest).
Whenever the snap receives a JSON-RPC request from a dapp or another snap, this handler function is
called with the specified parameters.

In addition to being able to expose a JSON-RPC API, snaps can access the global object `wallet`.
This object exposes a very similar API using `window.ethereum`.
MetaMask receives and processes any message sent using `wallet.request()`.

If a dapp wants to use `hello-snap`, it can implement something like this:

```javascript
await ethereum.request({
method: 'wallet_enable',
params: [
{
wallet_snap: {
'npm:hello-snap': {
version: '^1.0.0',
},
},
},
],
});

const hello = await ethereum.request({
method: 'wallet_invokeSnap',
params: ['npm:hello-snap', { method: 'hello' }],
});

console.log(hello); // 'world!'
```

The snap's RPC API is completely up to you, so long as it's a valid
[JSON-RPC](https://www.jsonrpc.org/specification) API.

:::tip Does my snap need to have an RPC API?
No, that's also up to you!
If your snap can do something useful without receiving and responding to JSON-RPC requests, then you
can skip exporting `onRpcRequest`.
However, if you want to do something such as manage the user's keys for a particular protocol and
create a dapp that, for example, sends transactions for that protocol using your snap, you must
specify an RPC API.
:::

## Manifest file

To get MetaMask to execute your snap, you must have a valid manifest file named `snap.manifest.json`,
located in your package root directory.
The manifest file of `hello-snap` would look something like this:

```json
{
"version": "1.0.0",
"proposedName": "hello-snap",
"description": "A snap that says hello!",
"repository": {
"type": "git",
"url": "https://github.com/Hello/hello-snap.git"
},
"source": {
"shasum": "w3FltkDjKQZiPwM+AThnmypt0OFF7hj4ycg/kxxv+nU=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
"iconPath": "images/icon.svg",
"packageName": "hello-snap",
"registry": "https://registry.npmjs.org/"
}
}
},
"initialPermissions": {},
"manifestVersion": "0.1"
}
```

The manifest tells MetaMask important information about your snap, such as where it's published
(using `source.location`) and how to verify the integrity of the snap source code (by attempting to
reproduce the `source.shasum` value).

:::note
Currently, snaps can only be
[published to the official npm registry](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry),
and the manifest must also match the corresponding fields of the `package.json` file.
In the future, developers will be able to distribute snaps in different ways, and the manifest will
expand to support different publishing solutions.

The [snaps publishing specification](https://github.com/MetaMask/specifications/blob/main/snaps/publishing.md)
details the requirements of both `snap.manifest.json` and its relationship to `package.json`.
:::

You might need to modify some manifest fields manually.
For example, if you change the location of the (optional) icon SVG file, you must update
`source.location.npm.iconPath` to match.
You can also use the [command line](../reference/options.md) to update some fields for you.
For example, `mm-snap build` or `mm-snap manifest --fix` updates `source.shasum`.

## Configuration file

The snap configuration file, `snap.config.js`, should be placed in the project root directory.
You can override [CLI options](../reference/options.md) in the configuration file by specifying
string keys matching command arguments in the property `cliOptions`.
Values become argument defaults, which you can still override on the command line.
For example:

```javascript
module.exports = {
cliOptions: {
src: 'lib/index.js',
dist: 'out',
port: 9000,
},
};
```

If you want to customize the Browserify build process, you can provide the `bundlerCustomizer` property.
It's a function that takes one argument, the
[browserify object](https://github.com/browserify/browserify#api-example) which MetaMask uses
internally to bundle the snap.
You can transform it in any way you want, for example, adding plugins.
The `bundleCustomizer` function looks something like this:

```javascript
const brfs = require('brfs');

module.exports = {
cliOptions: {
/* ... */
},
bundlerCustomizer: (bundler) => {
bundler.transform(brfs);
},
};
```

:::caution
You should **not** publish the configuration file.
:::

## Bundle file

Because of the way snaps are executed, they must be published as a single `.js` file containing the
entire source code and all dependencies.
Moreover, the [Snaps execution environment](execution-environment.md) has no DOM, no Node.js
APIs, and no filesystem access, so anything that relies on the DOM doesn't work, and any Node
built-ins must be bundled along with the snap.

Use the command `mm-snap build` to bundle your snap using [Browserify](https://browserify.org).
This command finds all dependencies using your specified main entry point and outputs a bundle
file to your specified output path.
46 changes: 46 additions & 0 deletions snaps/concepts/execution-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Snap execution environment
---

# Snaps execution environment

Snaps are untrusted JavaScript programs that execute safely in a sandboxed environment that runs
[Secure EcmaScript (SES)](#secure-ecmascript-ses).
There's no DOM, no Node.js built-ins, and no platform-specific APIs other than MetaMask's `wallet`
global object.
Almost all standard JavaScript globals contained in
[this list](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) that
are also in Node.js are available as normal.
This includes globals such as `Promise`, `Error`, `Math`, `Set`, and `Reflect`.

The following globals are also available:

- `console`
- `crypto`
- `fetch` / `WebSocket` (with the [appropriate permission](../how-to/request-permissions.md#endowmentnetwork-access))
- `setTimeout` / `clearTimeout`
- `setInterval` / `clearInterval`
- `SubtleCrypto`
- `WebAssembly`
- `TextEncoder` / `TextDecoder`
- `atob` / `btoa`
- `URL`

The execution environment is instrumented in this way to:

1. Prevent snaps from influencing any other running code, including MetaMask itself.
That is, prevent all snaps from polluting the global environment and malicious snaps from
stealing the user's stuff.
1. Prevent snaps from accessing sensitive JavaScript APIs (such as `fetch`) without permission.
1. Ensure that the execution environment is "fully virtualizable," that is, platform-independent.

This allows you to safely execute snaps anywhere, without the snap needing to worry about where and
how it's executed.

## Secure EcmaScript (SES)

[Secure EcmaScript (SES)](https://github.com/endojs/endo/tree/master/packages/ses), is a subset of
the JavaScript language designed to enable mutually suspicious programs to execute in the same
JavaScript process (or more accurately, the same [realm](https://tc39.es/ecma262/#realm)).
You can think of it as a more severe form of
[strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode).
17 changes: 17 additions & 0 deletions snaps/concepts/lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Snap lifecycle

Just like [service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) or
AWS lambda functions, snaps are designed to wake up in response to messages/events, and shut down
when idle.
We say that snaps have an "ephemeral" lifecycle: here one moment, gone the next.
Also, if MetaMask detects that a snap becomes unresponsive, it shuts the snap down.
This doesn't mean that you can't create long-running snaps, but it does mean that your snaps must
handle being shut down, especially when they're not within the JSON-RPC request/response cycle.

A snap is considered "unresponsive" if:

1. It hasn't received a JSON-RPC request for 30 seconds.
1. It takes more than 60 seconds to process a JSON-RPC request.

Stopped snaps start whenever they receive a JSON-RPC request, unless they're disabled.
If a snap is disabled, the user must re-enable it before it can start again.
18 changes: 18 additions & 0 deletions snaps/concepts/user-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Snap user interface

Any snap must represent itself and what it does to the end user.
Using the MetaMask settings page, the user can see their installed snaps.
For each snap, the user can:

- See most of its manifest data.
- See its execution status (running, stopped, or crashed).
- Enable and disable the snap.

Other than the settings page, the only way a snap can modify the MetaMask UI is by creating a
confirmation using the [`snap_confirm`](../reference/rpc-api.md#snap_confirm) API method.
This means that many snaps must rely on dapps/websites and their own API methods to
present their data to the user.

Providing more ways for snaps to modify the MetaMask UI is an important goal of the Snaps system,
and over time more and more snaps will be able to contain their user interfaces entirely within
MetaMask itself.
Loading

0 comments on commit 2dd1c66

Please sign in to comment.