You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a list of common pitfalls you may encounter when developing your snap and how to avoid them. Below you will find code samples and design advice. This list will be updated over time.
Use copyable instead of text when displaying arbitrary content
A common use case for a snap dialog is to show a confirmation with some arbitrary content, such as for signing a message. One common caveat when using dialogs is that the input may contain special characters that will render Markdown. For example:
The problem is that these special characters: * and _ may render Markdown formatting and thus what the user sees will not match the content. To avoid this, use copyable instead:
Copyable does not render Markdown and has the added benefit that the user can click to copy the content. Also, the formatting provides a visual delineator to separate arbitrary input or fields from user interface text.
Display dapp origin in dialogs
If your snap is designed to interact with websites, you will probably have functionality that allows a website to initiate a dialog flow with your snap. It's important for users to understand which website is initiating this flow. The easiest way to do that is to show the requesting site in the content of the dialog. Here's an example using the template-snap:
import { OnRpcRequestHandler } from '@metamask/snaps-types';
import { panel, text, copyable } from '@metamask/snaps-ui';
export const onRpcRequest: OnRpcRequestHandler = ({ origin, request }) => {
switch (request.method) {
case 'hello':
return snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: panel([
text(`Request origin:`),
copyable(`${origin}`),
text(`Content:`),
copyable('This custom confirmation is just for display purposes. '+
'But you can edit the snap source code to make it do something, if you want to!'),
]),
},
});
default:
throw new Error('Method not found.');
}
};
And here's how this looks:
Restrict access to critical API methods by using an allowlist
If you are building a snap that is design to talk to websites and you have custom RPC methods that provide critical features such as modifying the internal state of your snap or exposing sensitive data, you probably want to have a dedicated dapp that is used as an "admin dapp" to interact with your snap and prevent other dapps from accessing those same methods. The best way to do this is to check the origin in the request and only proceed if the origin matches your allowlist. Using the previous example, you can do the following:
export const onRpcRequest: OnRpcRequestHandler = ({ origin, request }) => {
switch (request.method) {
case 'hello':
if( null === origin.match( /^https:\/\/(www\.){0,1}metamask\.io/ ) ) {
throw new Error('Unauthorized call from unknown origin.');
}
return snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: panel([
text(`Request origin:`),
copyable(`${origin}`),
text(`Content:`),
copyable('This custom confirmation is just for display purposes. '+
'But you can edit the snap source code to make it do something, if you want to!'),
]),
},
});
default:
throw new Error('Method not found.');
}
};
This regular expression guarantees that the request must be made from https and that the domain must match metamask.io or www.metamask.io. Anything else will be rejected. In this case an error is shown, but another option is to display a dialog:
export const onRpcRequest: OnRpcRequestHandler = ({ origin, request }) => {
switch (request.method) {
case 'hello':
if( null === origin.match( /^https:\/\/(www\.){0,1}metamask\.io/ ) ) {
return snap.request({
method: 'snap_dialog',
params: {
type: 'alert',
content: panel([
text(`**Unauthorized call to method "hello"**`),
text(`This method may only be called by:`),
copyable(`metamask.io.`),
text(`Request origin:`),
copyable(`${origin}`),
]),
},
});
}
return snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: panel([
text(`Request origin:`),
copyable(`${origin}`),
text(`Content:`),
copyable('This custom confirmation is just for display purposes. '+
'But you can edit the snap source code to make it do something, if you want to!'),
]),
},
});
default:
throw new Error('Method not found.');
}
};
This produces the following dialog when called by a different origin:
This approach is not very elegant. Alternatively, you can try using a package like domain-match instead.
Thanks for reading
Do you have more common pitfalls that should be covered here? Let me know in the replies.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
This is a list of common pitfalls you may encounter when developing your snap and how to avoid them. Below you will find code samples and design advice. This list will be updated over time.
Use
copyable
instead oftext
when displaying arbitrary contentA common use case for a snap dialog is to show a confirmation with some arbitrary content, such as for signing a message. One common caveat when using dialogs is that the input may contain special characters that will render Markdown. For example:
The problem is that these special characters: * and _ may render Markdown formatting and thus what the user sees will not match the content. To avoid this, use
copyable
instead:Copyable does not render Markdown and has the added benefit that the user can click to copy the content. Also, the formatting provides a visual delineator to separate arbitrary input or fields from user interface text.
Display dapp origin in dialogs
If your snap is designed to interact with websites, you will probably have functionality that allows a website to initiate a dialog flow with your snap. It's important for users to understand which website is initiating this flow. The easiest way to do that is to show the requesting site in the content of the dialog. Here's an example using the template-snap:
And here's how this looks:
Restrict access to critical API methods by using an allowlist
If you are building a snap that is design to talk to websites and you have custom RPC methods that provide critical features such as modifying the internal state of your snap or exposing sensitive data, you probably want to have a dedicated dapp that is used as an "admin dapp" to interact with your snap and prevent other dapps from accessing those same methods. The best way to do this is to check the origin in the request and only proceed if the origin matches your allowlist. Using the previous example, you can do the following:
This regular expression guarantees that the request must be made from
https
and that the domain must matchmetamask.io
orwww.metamask.io
. Anything else will be rejected. In this case an error is shown, but another option is to display a dialog:This produces the following dialog when called by a different origin:
This approach is not very elegant. Alternatively, you can try using a package like domain-match instead.
Thanks for reading
Do you have more common pitfalls that should be covered here? Let me know in the replies.
Here are the tools I used to make this list:
Beta Was this translation helpful? Give feedback.
All reactions