今回はMetamask Snapsの概要についてまとめていきます。
以下のドキュメントをもとにまとめていきます。
Memtask Snapsを使用すると、サーバーパーティー開発者によって作成された機能をMetaask追加できるようになります。これによりMetamaskウォレットをカスタマイズできるようになります。Metamask Snapsを使用することで以下のことができるようになります。
EVMチェーン以外のブロックチェーンに接続する。
トランザクションに署名をするときに、実行しようとしているトランザクションに問題がないか分析をする。
Metamaskに直接通知を飛ばして最新情報を取得する。
また、各Snapの設定からSnapを無効にすることもできます。以下からSnapの一覧を確認できます。
Snapsはサンドボックス環境で実行され、アクセス許可モデルを使用してデータを保護します。 SnapsはMetamaskアカウントのデータにアクセスすることはできません。ただ、Snapsをインストールするときは、セキュリティの観点からどのような権限を付与しているかを理解する必要はあります。
現在のMetamask Snapsはオープンベータ版であり、Metamaskチームとサードパーティによって監査され、ホワイトリストに登録された機能だけを使用することができます。いずれはこの部分をパーミッションレスにすることを目指しています。
Metamask Snapsとは、ウォレットをカスタムする、隔離された環境(サンドボックス)で実行されるJavaScriptpプログラムです。Snapはインストール中にユーザーが付与した権限によって決定される、制限された機能セットにアクセスすることができます。
Metamask Snapsでは以下のことができます。
ダイアログ
通知
暗号化ストレージ
非EVMチェーンのサポート
トランザクションの分析
Cronジョブ
カスタムUI
ネットワークアクセス
fetch
を使用してAPIを呼び出す機能。Snap開発に使用される上記のディレクトリ構造は以下のようになっています。
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)
snap/src/index.ts
module.exports.onRpcRequest = async ({ origin, request }) => {
switch (request.method) {
// Expose a "hello" RPC method to dapps
case "cardene":
return "welcome!";
default:
throw new Error("Method not found.");
}
};
Snapが外部と通信するには、エクスポートされた関数を公開して独自のJSON-RPC APIであるonRpcRequest
を実装する必要があります。SnapがDapp、または別のSnapからJSON-RPCリクエストを受け取るたびに、指定されたパラメータを使用してハンドラー関数を呼び出します。
JSON-ROC APIを公開できることに加えて、Snapはグローバルオブジェクトにアクセスできます。 このオブジェクトを使用して、Snaps固有のJSON-RPCリクエストを実行することができます。
Dappが使用する場合を想定して、Snapのをnpmパッケージとして公開するために以下のような実装をすることができます。
// Connect to the snap, enabling its usage inside the dapp
// If the snap is not already installed, the MetaMask user
// will be prompted to install it
await window.ethereum.request({
method: "wallet_requestSnaps",
params: {
"npm:cardene-snap": {},
},
});
// Invoke the "hello" RPC method exposed by the snap
const response = await window.ethereum.request({
method: "wallet_invokeSnap",
params: { snapId: "npm:cardene-snap", request: { method: "cardene" } },
});
console.log(response); // 'welcome!'
JSON-RPC APIが有効である限り、SnapのRPC APIはユーザーの好きなようにカスタムできます。トランザクションを送信するDappを作成する場合はJSON-RPC APIが必要ですが、それ以外の場合は必ずしもJSON-RPC APIが必要とは限りません。
MetamaskにSnapを実行させるには、パッケージのルートディレクトリに以下のようなsnap.manifest.json
というファイルが必要になります。
{
"version": "1.0.0",
"proposedName": "Cardebe Welcome",
"description": "A Snap that says welcome!",
"repository": {
"type": "git",
"url": "https://github.com/cardene777/base-code/metamask/snaps/cardene-snap"
},
"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"
}
マニフェストは、Snapの公開場所やSnapのソースコードの生合成を確認する方法など、Snapに関する重要な情報をMetamaskに伝えます。
現在(2023年9月24日)、Snapは公式のnpmレジストリにのみ公開でき、マニフェストも
package.json
ファイルの対応するフィールドと一致する必要があります。 開発者はさまざまな方法でSnapを配布できるようになり、マニフェストは様々な公開ソリューションをサポートするように拡張予定とのことです。SVGアイコンファイルの場所を変更した場合にsource.location.npm.iconPath
をそれに合わせて更新する必要があるなど、一部の場合にフィールドを更新する必要が出てきます。
Snap構成ファイルは、snap.config.ts
プロジェクトのルートディレクトリに配置する必要があります。 Snap CLIオプションのデフォルト値は、config
設定ファイルのオブジェクトで指定することで上書きできます。
import { resolve } from 'path';
import type { SnapConfig } from '@metamask/snaps-cli';
const config: SnapConfig = {
bundler: 'webpack',
input: resolve(__dirname, 'src/index.ts'),
server: {
port: 8080,
},
polyfills: {
buffer: true,
},
};
export default config;
構成ファイルは開発と構築にのみ使用されるため、npmに公開してはいけません。
Snapの実行方法により、Snapは.js
ソースコード全体と全ての依存関係を含む単一のファイルとして公開する必要があります。Snap実行環境には、DOM
、Node.js API
、ファイルシステムへのアクセスがないため、DOM
に依存するものは全て機能せず、NodeビルトインはSnapとともにバンドルする必要があります。
Snap実行環境には、以下の制約があります。
DOM (Document Object Model) へのアクセスがない
Snap実行環境では、ウェブページの要素にアクセスするためのDOM操作ができません。
つまり、ウェブページ上の要素を操作したり、変更したりすることはできません。
Node.js API の利用不可
Snap実行環境では、Node.jsのビルトイン機能やモジュールを使用することができません。
これは、サーバーサイドの機能やファイルシステムへのアクセスができないことを意味します。 したがって、Snap実行環境で開発を行う場合、以下の点に留意する必要があります。
DOMに依存する機能は利用不可
- Snap実行環境では、ウェブページ上での要素の操作や表示に関連するコードは動作しないため、注意してコードを設計する必要があります。
Node.jsのビルトイン機能をバンドル
Snap実行環境でNode.jsのビルトイン機能を使用する必要がある場合、それらの機能をSnapと一緒にパッケージ化(バンドル)する必要があります。
これにより、実行環境内で利用可能になります。
Snap実行環境の制約を理解し、それに応じて開発を行うことが重要です。 必要な場合は、Snap実行環境用にカスタマイズされたコードを作成するか、代替の方法を見つける必要があります。
Service Workerや AWS Lambda 関数と同様に、Snapはメッセージ/イベントに応答して起動し、アイドル状態になるとシャットダウンするように設計されています。Snapには一時的なライフサイクルがあり、ある瞬間には存在し次の瞬間には消えてしまいます。また、MetaMask はSnapが応答しなくなったことを検出すると、Snapをシャットダウンします。
次の場合、Snapは応答しないとみなされます。
30
秒間JSON-RPCリクエストを受信していない。
JSON-RPCリクエストの処理には60
秒以上かかる。
停止されたSnapは、無効になっていない限り、JSON-RPCリクエストを受信するたびに開始されます。 Snapが無効になっている場合、Snapを再度開始するにはユーザーが再度有効にする必要があります。
MetaMask 設定ページを使用すると、ユーザーはインストールされたSnapを確認できます。 Snapごとに、ユーザーは以下のことができます。
マニフェストデータの参照。
実行ステータス (実行中、停止、またはクラッシュ) の確認。
Snapを有効または無効に対する。
設定ページ以外では、Snapで カスタム UIを使用して MetaMask UI を変更できるのは次の 2 つの方法のみです。
snap_dialog
RPCメソッドを使用してダイアログを開く。
onTransaction
エクスポートからトランザクションの分析情報を返す。
ほとんどのSnapは、データをユーザーに提示するために dapps/Web サイトと独自のRPCメソッドに依存する必要があることを意味します。Snapがより多くのMetaMask UIを変更する方法を提供することは、Snaps システムの重要な目標であり、時間の経過とともにより多くのSnapがユーザーインターフェースを完全にMetaMask自体に含めることができるようになります。
Snapは、Secure ECMAScript (SES)を実行するサンドボックス環境で安全に実行される信頼されていないJavaScript プログラムです。DOM
もNode.js
ビルトインも、MetaMaskのwallet
グローバル オブジェクト以外のプラットフォーム固有のAPIもありません。Node.jsにあるほとんどすべての標準JavaScript グローバルは通常どおり使用できます。
console
crypto
fetch
( endowment:network-access
許可を得て)
setTimeout
/clearTimeout
setInterval
/clearInterval
SubtleCrypto
WebAssembly
( endowment:webassembly
許可を得て)
TextEncoder
/TextDecoder
atob
/btoa
URL
実行環境は、次の目的で用意されています。
SnapがMetaMask自体を含む他の実行中のコードに影響を与えないようにし、すべての悪意のあるSnapがユーザーの資産などを盗んだりするのを防ぎます。
Snapが機密性の高いJavaScript APIに許可なくアクセスできないようにします。
実行環境がプラットフォームに依存しないこと。
これにより、Snapがどこでどのように実行されるかを気にする必要がなく、どこでも安全にSnapを実行できます。
Secure ECMAScript (SES)は、相互に疑わしいプログラムが同じJavaScriptプロセスで実行できるように設計されたJavaScript言語のサブセットです。
Metamaskのデザインを変更するときのガイドラインがまとまっています。詳細は以下のドキュメントページを見てください。
Snaps Keyring APIは、MetaMask内にカスタムEVMアカウントを統合します。 これにより、カスタムアカウントをUIで通常のMetaMaskアカウントと同じように表示できるようになります。
ブロックチェーンアカウント
リクエスト
キーリング アカウント
Keyring snap
Keyring request
MetaMaskからKeyring Snapへのリクエスト。
MetaMaskは、Dappによって送信された元のリクエストをラップし、それにメタデータを追加します。
以下の図はキーリングSnapによって管理されるアカウントを操作する時に発生するコンポーネントを表しています。
User
Dapp
MetaMask
Dappが接続するウォレット。
MetaMaskはリクエストをキーリングSnapにルーティングし、ユーザーがある程度のレベルのアカウント管理を実行できるようにします。
Snap
Snap UI
キーリングSnapを作成する最初のステップは、Keyring
インターフェイスを実装することです。このインターフェイスは、カスタムEVMアカウントを独自のロジックを使用してMetaMask内で動作させるために必要なすべてのメソッドを実装しています。
ユーザーとキーリングSnapの間の最初のやり取りは、Snapアカウントの作成プロセスで、流れは以下になります。
Metamaskアカウントのモーダル内には「Add snap account」というオプションがあります。
このオプションは、キーリングSnapのリストを表示します。各Snapは、Snapを構成および管理するためのすべてのDappにユーザーをリダイレクトさせます。
DappはカスタムUIを表示し、ユーザーがカスタムEVMアカウントを構成できるようにします。 Dappは、同じ名前のインターフェイスのメソッドを呼び出すcreateAccount
メソッドを使用します。 createAccount
インターフェイスのメソッドは、メソッドKeyring
に渡されたパラメータに基づいてアカウントを作成します。Snapは、 snap_manageState
を使用して作成したアカウントを追跡します。 Snapがアカウントを作成すると、 snap_manageAccounts
メソッドを使用して MetaMask に通知します。Snapがアカウントを作成すると、そのアカウントを使用してメッセージやトランザクションに署名できます。
キーリングSnapがトランザクションに直接署名できる場合、単純な同期署名フローが実装されます。Snapにハードウェア キーや2番目のアカウントの署名 (閾値署名スキームなど)のサードパーティが必要な場合、非同期署名フローが実装されます。 同期フローは次のようになります。
このフローは、DappがMetamask JSON-RPCメソッドを呼び出した時やユーザーがMetamask UIから新しい資金の移動開始した時に実行開始されます。その時点で、MetaMaskはこのやりとりがキーリングSnapによって制御されるアカウントに対して要求されていることを検出します。ユーザーがUIでトランザクションを承認した後、MetaMaskはインターフェイスsubmitRequest
メソッドを呼び出します。submitRequest
は、元のRPCリクエストを受信し、 pending
をfalse
に設定し、resuilt
を要求された署名に設定したSubmitRequestResponse
を返します。
キーリングSnapが
eth_sendTransaction
リクエストを受信した場合、それをeth_signTransaction
リクエストのように扱う必要があります。 Snapは応答で署名を提供する責任を負い、メタマスクはトランザクションをブロードキャストする責任を負います。
キーリングSnapがしきい値署名などの複雑なスキームを実装する場合、より多くのKeyring
メソッドを使用して非同期署名フローを実装します。非同期フローは次のようになります。
フローは同期フローと同じように開始します。 Dappまたはユーザーが、トランザクションまたは任意のデータに署名するリクエストを開始します。 承認後、SnapのKeyring
インターフェースのsubmitRequest
メソッドが呼び出される。
Snapはリクエストに直接応答しないため、snap_manageState
を使用して保留中のリクエストを内部ステートに格納します。 Keyring
インタフェースのlistRequests
メソッドかgetRequest
メソッドが呼び出されると、保留中のリクエストのリストが返されます。保留中のリクエストを保存した後、Snapはsnap_dialog
を使用してポップアップを作成し、コンパニオンDappのURLにアクセスするようユーザーに指示します。
Dappは、KeyringSnapRpcClient
のlistRequests
メソッドによって促進されるRPCコールを使用して、Snapの保留中のリクエストを一覧表示します。 その後、ユーザーはSnapに適用される処理を使用して、これらのリクエストに対処できます。署名プロセスが完了すると、コンパニオンDappはKeyringSnapRpcClient
のapproveRequest
メソッドを使用してリクエストを承認し、同名のKeyring
インターフェースのメソッドを呼び出します。 このメソッドは、リクエストのIDと最終結果を受け取ります。approveRequest
が呼び出されると、snap_manageAccounts
のsubmitResponse
サブメソッドを使用して保留中のリクエストを処理できます。
今回はMetamask Snapsの概要についてまとめてきました。
実装については別の記事でまとめていきます。
今後も技術的な部分を中心にわかりやすくまとめていこうと思います。
他の媒体でも情報発信しているのでよければ見ていってください!