Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Snaps API

Erik Marks edited this page Nov 26, 2019 · 2 revisions

Note: At the moment, the beta only supports Google Chrome.

We have a fork of MetaMask that allows you to add a script at runtime which receives almost no authority except for:

Other exceptions can be viewed within the MetaMask plugins controller.

These APIs (especially fetch) are provided for convenience purposes, and may be subject to replacement with a restricted method in the future, ensuring user consent, and reducing user tracking.

The Snaps wallet API

The wallet API has all the normal APIs that the Ethereum provider has, but also:

  • .registerRpcMessageHandler(rpcMessageHandler) - Used to extend the MetaMask API exposed to dapps. Probably the most powerful method exposed, discussed in detail below.
  • .registerApiRequestHandler(handler) - Used to provide an API object to a requesting site, enabling responsive, event-driven APIs.
  • .onMetaMaskEvent(eventName, callback) - Just for beta purposes, exposes every event internal to the MetaMask controllers for Transactions, Networks, and Block tracking. You can trace how these are added here.
    • Events that you can listen for within your Snap include:
      'tx:status-update': 'Be notified when the status of your transactions changes',
      latest: 'Be notified when the new blocks are added to the blockchain',
      networkDidChange: 'Be notified when your selected network changes',
      newUnapprovedTx: 'Be notified with details of your new transactions',
      
    • To request permission to listen for these events, preface the above event names with metamask_ in your package.json's initialPermissions object. e.g.
      "initialPermissions": {
          "metamask_newUnapprovedTx": {}
      }
      
      You can then listen for the event in your Snap as follows:
      wallet.onMetaMaskEvent('newUnapprovedTx', (tx) => {
          console.log('new tx! ', tx)
      })
      
  • .onUnlock(callback) to get notified when the wallet is unlocked. Useful if you need to take some action after the wallet is unlocked.
  • .getAppKey() - Discussed below.
  • .updatePluginState(yourStateToPersist) - Used to persist state to our store.
  • .getPluginState() - Returns whatever the most recent value you passed to .updatePluginState(). Useful when first starting up your Snap to restore its state.

Additionally, there are more RPC restricted methods available that you can request, too many to list here, but you can view the full list here. They lack great API documentation, but if you really need one, we can help you figure it out.

These APIs are not set in stone at all, and are instead meant to be a minimum viable platform for us all to experiment with what it means to make a wallet extensible at runtime.

On the current draft of this branch, adding a Snap is itself a restricted method, and so getting permission to add a script and adding it is a two-call process. (We will almost certainly tighten this flow up a bit, but we wanted to get it out to you as quckly as possible.) You can see a few example Snaps and compatible dapps on the mm-snap repository.

Let's say you were hosting a Snap package.json file on http://localhost:8080 (eventually we hope to support loading from an ethereum name or ENS address, but we're supporting http first for the beta and local development purposes!). There are currently two ways to install a Snap, one eager and one lazy.

Requesting a Connection to a Snap

When you request the permission to contact a Snap (as if it were a restrictedMethod named wallet_plugin_${pluginOrigin}), the user is prompted with a permission. If they consent, the Snap is installed if it is not already, and the application is now able to send messages to the Snap via that same method name.

// Assume an async function so I can use await:
async () => {

// In development your Snap URL is probably just on localhost:
const snapUrl = 'http://localhost:8080'
const snapId = `wallet_plugin_${snapUrl}`

// First request permission to send messages to the Snap:
await ethereum.send({
  method: 'wallet_requestPermissions',
  params:[{
    [snapId]: {},
  }],
})

// Once that Snap has been permitted, you can send requests to it!
const response = await ethereum.send({
  method: snapId,
  params: [yourSnapWillReceiveThisObject],
})

console.log(response.result)

}()

The Snap itself is able to handle those requests like this:

wallet.registerRpcMessageHandler(async (originString, requestObject) => {
  // The Snap can now respond to the requesting origin in any way it likes!
  // You might switch on originString to enforce permissions,
  // You might switch on requestObject.method to adjust behavior!

  return 'Success!'
})

wallet.registerApiRequestHandler(handler)

Alternatively, if you'd like to expose a slightly more flexible or event-driven API to pages, you can register

Allows Snaps to define a very flexible JS-based API like this (example Snap):

wallet.registerApiRequestHandler((fromOrigin) => {
  return {
    ping: () => 'pong!',
    on: myEventEmitter.on.bind(myEventEmitter),
  }
})

The value returned can be a simple JavaScript object, function, or value. All methods are converted to async methods, as done by capnode, which manages the api transmission under the hood, and so you can read about it to understand the limitations better. Passing constructors over this handler also will not work.

Sites can then request the flexible "index" API like this:

async function requestIndex () {
  index = await ethereum.requestIndex()
  pluginApi = await index.getPluginApi(pluginOrigin)

  // Use the API provided by the Snap here!
  const pong = await pluginApi.ping()
}

Additional Methods

To make the ethereum API less constricting, we are exposing nearly every API that MetaMask's UI uses internally behind a metamask_ prefix, and making it available via the permissions system. If that doesn't make sense to you, hold tight, we are composing docs on that as well.

We are also going to expose some basic methods you might want to use, like confirm and alert for hacking together basic user confirmations, but ultimately for quickly experimenting, you will want to get comfortable adding your own restricted methods (just search restrictedMethods on the permissions.js controller), or for even quicker hacking, you can just pass fully unprotected objects and functions to the Snap context here. (As you can see, we're exposing console directly for debugging purposes)

The metamask_getState function will give you a full state object that the MetaMask UI uses to render everything. This is an absolutely private API that will not be available in production, but is designed to get you started quickly, while we are still designing the API surface that we should expose.

At this stage of the Snap system, we have not yet built many APIs specific to any particular Snap's use case. This stage of the beta is designed for developers who are interested in helping define APIs that they would like Snaps to have.

App Keys

The one special API we have shipped for this Snap beta is an experimental early-version of EIP 1775. The API for now is very simple: Every Snap can request a unique secret seed based on hash(script_origin + user_private_key). It is available on the Snap global as wallet.getAppKey(). This method returns a promise, which resolves to a 32 byte (64 character) hex-encoded string which will be re-generated if the user were to have their computer wiped but restored MetaMask from the same seed phrase. Later on we will probably allow requesting a unique app key per exposed user account, for the sake of coherent persona management.

This was a required feature for multiple layer-2 teams, especially ones dealing with unsupported cryptography.

Custom Asset Management

This branch also implements support for an experimental feature which allows a domain to create and manage its own custom assets, which appear in the user's wallet and open a custom URL when clicked.

This customViewUrl is intended to allow the Snap to provide a UI for managing that asset, and can create a branch in its RPC handler for providing special methods to that domain, to allow privileged management. As usual, for this feature to be secure, it relies on highly secure domains, and we continue to work towards secure and reliable ENS/IPFS/Swarm support.

You can request this permission either using the EIP 2255 permissions API, or by adding "wallet_manageAssets": {} to the web3Wallet.initialPermissions object in your Snap's package.json.

The asset schema would be defined in TypeScript like this:

type asset = {
  symbol:string,
  balance: string,
  identifier: string,
  image: string,
  decimals: number,
  customViewUrl: string,
}

An example asset would be:

const customAsset = {
  symbol: 'TEST_ASSET',
  balance: '200000',
  identifier: 'test:asset',
  image: YOUR_IMAGE_LINK,
  decimals: 5,
  customViewUrl: 'https://metamask.io'
}

Introduces wallet_manageAssets method. Takes two parameters:

  • method name (string)
  • asset options (object)

Method name

Method name is one of:

  • addAsset
  • updateAsset
  • removeAsset

And dictates the action to perform. By tucking these methods all behind one restricted method, we are able to ask the user for one permission that allows the permitted domain to perform multiple actions.

Asset options object

This object has some required properties:

  • symbol: A string for displaying the asset.
  • balance: A string representing the full balance (no decimals)
  • identifier: An identifier that must be unique to all assets from the requesting domain.
  • decimals: A number for how many digits to tuck behind a decimal.
  • customViewUrl: A link to a page that can show more details on the asset when selected. This parameter will probably become optional or different in the future, consider it unstable.

Recipient Address Auditing

We have added API support for Snap developers that wish to help users determine if the addresses to which they are sending transactions are trustworthy. This api can be used to update the internal metamask state such that recipient addresses on confirmation screens can be rendered with warning or approval methods. The below images show how these will look:

The addAddressAudit method can be requested as part of a Snap's initialPermissions. This method receives an object with the following properties:

  • address: the '0x' prefixed public address, of a transaction recipient, to be audited
  • auditor: the name of your Snap, to appear on the transaction confirmation screen
  • status: a string strictly equal to either 'warning' or 'approval', which will be rendered with your audit message; 'warning's will be rendered with red text and 'approval's will be green text
  • message: a message to be shown to the user on the transaction confirmation screen

Once the addAddressAudit method is called by a Snap, any transaction sent to that address will show the audit message on the transaction's confirmation screen.

Note that to successfully use this api, you will also need to use an event api to be able to identify when new transactions are created. An example Snap that does this can be found here: https://github.com/MetaMask/snaps-cli/tree/master/examples/recipient-address-auditor