-
-
Notifications
You must be signed in to change notification settings - Fork 57
Snaps API
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:
- the global
fetch
API - the global
XMLHttpRequest
API - the global
crypto
API - the global
SubtleCrypto
API - A new special global
wallet
API which is a superset of the Ethereum provider, with some special methods for the purposes of this beta.
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 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 yourpackage.json
'sinitialPermissions
object. e.g.You can then listen for the event in your Snap as follows:"initialPermissions": { "metamask_newUnapprovedTx": {} }
wallet.onMetaMaskEvent('newUnapprovedTx', (tx) => { console.log('new tx! ', tx) })
- Events that you can listen for within your Snap include:
-
.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.
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!'
})
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()
}
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.
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.
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 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.
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.
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