# Metamask Snaps[概要編] **Published by:** [Cardene](https://paragraph.com/@cardene-2/) **Published on:** 2023-09-24 **URL:** https://paragraph.com/@cardene-2/metamask-snaps ## Content 今回はMetamask Snapsの概要についてまとめていきます。 https://metamask.io/snaps/ 以下のドキュメントをもとにまとめていきます。 https://docs.metamask.io/snaps/概要Memtask Snapsを使用すると、サーバーパーティー開発者によって作成された機能をMetaask追加できるようになります。これによりMetamaskウォレットをカスタマイズできるようになります。Metamask Snapsを使用することで以下のことができるようになります。EVMチェーン以外のブロックチェーンに接続する。トランザクションに署名をするときに、実行しようとしているトランザクションに問題がないか分析をする。Metamaskに直接通知を飛ばして最新情報を取得する。また、各Snapの設定からSnapを無効にすることもできます。以下からSnapの一覧を確認できます。 https://snaps.metamask.io/Metamask Snapsは安全か?Snapsはサンドボックス環境で実行され、アクセス許可モデルを使用してデータを保護します。 SnapsはMetamaskアカウントのデータにアクセスすることはできません。ただ、Snapsをインストールするときは、セキュリティの観点からどのような権限を付与しているかを理解する必要はあります。現在のMetamask Snaps現在のMetamask Snapsはオープンベータ版であり、Metamaskチームとサードパーティによって監査され、ホワイトリストに登録された機能だけを使用することができます。いずれはこの部分をパーミッションレスにすることを目指しています。Metamask Snapsとは?https://docs.metamask.io/snaps/ Metamask Snapsとは、ウォレットをカスタムする、隔離された環境(サンドボックス)で実行されるJavaScriptpプログラムです。Snapはインストール中にユーザーが付与した権限によって決定される、制限された機能セットにアクセスすることができます。 Metamask Snapsでは以下のことができます。ダイアログMetamaskにカスタムのアラートや確認画面を表示できます。通知Metamaskに直接通知できる機能。暗号化ストレージユーザーのデバイスにデータを安全に保存、管理する機能。非EVMチェーンのサポートMetamaskでEVM以外のブロックチェーンアカウントとアセットを管理できます。トランザクションの分析Metamaskのトランザクション実行前に、トランザクションに問題がないかチェックします。Cronジョブユーザーに対する定期的なアクションを実行します。カスタムUI用意されたコンポーネントを使用して、MetamaskでカスタムUIを表示する機能。ネットワークアクセスfetchを使用してAPIを呼び出す機能。コンセプトSnapのファイル構造https://github.com/MetaMask/template-snap-monorepo 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.tsmodule.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実行環境用にカスタマイズされたコードを作成するか、代替の方法を見つける必要があります。SnapのライフサイクルService Workerや AWS Lambda 関数と同様に、Snapはメッセージ/イベントに応答して起動し、アイドル状態になるとシャットダウンするように設計されています。Snapには一時的なライフサイクルがあり、ある瞬間には存在し次の瞬間には消えてしまいます。また、MetaMask はSnapが応答しなくなったことを検出すると、Snapをシャットダウンします。 次の場合、Snapは応答しないとみなされます。30秒間JSON-RPCリクエストを受信して​​いない。JSON-RPCリクエストの処理には60秒以上かかる。停止されたSnapは、無効になっていない限り、JSON-RPCリクエストを受信するたびに開始されます。 Snapが無効になっている場合、Snapを再度開始するにはユーザーが再度有効にする必要があります。SnapのユーザーインターフェースMetaMask 設定ページを使用すると、ユーザーはインストールされたSnapを確認できます。 Snapごとに、ユーザーは以下のことができます。マニフェストデータの参照。実行ステータス (実行中、停止、またはクラッシュ) の確認。Snapを有効または無効に対する。設定ページ以外では、Snapで カスタム UIを使用して MetaMask UI を変更できるのは次の 2 つの方法のみです。snap_dialogRPCメソッドを使用してダイアログを開く。onTransactionエクスポートからトランザクションの分析情報を返す。ほとんどのSnapは、データをユーザーに提示するために dapps/Web サイトと独自のRPCメソッドに依存する必要があることを意味します。Snapがより多くのMetaMask UIを変更する方法を提供することは、Snaps システムの重要な目標であり、時間の経過とともにより多くのSnapがユーザーインターフェースを完全にMetaMask自体に含めることができるようになります。Snap実行環境Snapは、Secure ECMAScript (SES)を実行するサンドボックス環境で安全に実行される信頼されていないJavaScript プログラムです。DOMもNode.jsビルトインも、MetaMaskのwalletグローバル オブジェクト以外のプラットフォーム固有のAPIもありません。Node.jsにあるほとんどすべての標準JavaScript グローバルは通常どおり使用できます。consolecryptofetch( endowment:network-access許可を得て)setTimeout/clearTimeoutsetInterval/clearIntervalSubtleCryptoWebAssembly( endowment:webassembly許可を得て)TextEncoder/TextDecoderatob/btoaURL実行環境は、次の目的で用意されています。SnapがMetaMask自体を含む他の実行中のコードに影響を与えないようにし、すべての悪意のあるSnapがユーザーの資産などを盗んだりするのを防ぎます。Snapが機密性の高いJavaScript APIに許可なくアクセスできないようにします。実行環境がプラットフォームに依存しないこと。これにより、Snapがどこでどのように実行されるかを気にする必要がなく、どこでも安全にSnapを実行できます。セキュア ECMAScript (SES )Secure ECMAScript (SES)は、相互に疑わしいプログラムが同じJavaScriptプロセスで実行できるように設計されたJavaScript言語のサブセットです。SnapのデザインガイドラインMetamaskのデザインを変更するときのガイドラインがまとまっています。詳細は以下のドキュメントページを見てください。 https://docs.metamask.io/snaps/concepts/design-guidelines/キーリングAPIについてSnaps Keyring APIは、MetaMask内にカスタムEVMアカウントを統合します。 これにより、カスタムアカウントをUIで通常のMetaMaskアカウントと同じように表示できるようになります。https://docs.metamask.io/snaps/concepts/keyring-api/関連用語ブロックチェーンアカウント残高、ノンス、その他のアカウントの詳細を含むアカウントを表す、単一のブロックチェーン内のオブジェクト。リクエストDappからMetaMaskへのリクエスト。キーリング アカウント1つ以上のブロックチェーンアカウントを表すアカウントモデル。Keyring snapKeyring APIを実装するSnap。Keyring requestMetaMaskからKeyring Snapへのリクエスト。MetaMaskは、Dappによって送信された元のリクエストをラップし、それにメタデータを追加します。コンポーネント以下の図はキーリングSnapによって管理されるアカウントを操作する時に発生するコンポーネントを表しています。https://docs.metamask.io/snaps/concepts/keyring-api/UserSnap、dapp、MetaMaskを操作するユーザー。Dappアカウントに対して実行されるアクションをリクエストするDapp。MetaMaskDappが接続するウォレット。MetaMaskはリクエストをキーリングSnapにルーティングし、ユーザーがある程度のレベルのアカウント管理を実行できるようにします。Snapユーザーのアカウントを管理し、これらのアカウントを使用するリクエストを処理するために Keyring APIを実装するSnap。Snap UIユーザーがSnapを操作してアカウントやリクエストに対するカスタム操作を実行できるようにするSnapのUIコンポーネント。キーリングキーリングSnapを作成する最初のステップは、Keyringインターフェイスを実装することです。このインターフェイスは、カスタムEVMアカウントを独自のロジックを使用してMetaMask内で動作させるために必要なすべてのメソッドを実装しています。Snapアカウントの作成ユーザーとキーリングSnapの間の最初のやり取りは、Snapアカウントの作成プロセスで、流れは以下になります。https://docs.metamask.io/snaps/concepts/keyring-api/Metamaskアカウントのモーダル内には「Add snap account」というオプションがあります。https://docs.metamask.io/snaps/concepts/keyring-api/このオプションは、キーリングSnapのリストを表示します。各Snapは、Snapを構成および管理するためのすべてのDappにユーザーをリダイレクトさせます。 DappはカスタムUIを表示し、ユーザーがカスタムEVMアカウントを構成できるようにします。 Dappは、同じ名前のインターフェイスのメソッドを呼び出すcreateAccount メソッドを使用します。 createAccountインターフェイスのメソッドは、メソッドKeyringに渡されたパラメータに基づいてアカウントを作成します。Snapは、 snap_manageStateを使用して作成したアカウントを追跡します。 Snapがアカウントを作成すると、 snap_manageAccountsメソッドを使用して MetaMask に通知します。Snapがアカウントを作成すると、そのアカウントを使用してメッセージやトランザクションに署名できます。同期署名キーリングSnapがトランザクションに直接署名できる場合、単純な同期署名フローが実装されます。Snapにハードウェア キーや2番目のアカウントの署名 (閾値署名スキームなど)のサードパーティが必要な場合、非同期署名フローが実装されます。 同期フローは次のようになります。https://docs.metamask.io/snaps/concepts/keyring-api/このフローは、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メソッドを使用して非同期署名フローを実装します。非同期フローは次のようになります。https://docs.metamask.io/snaps/concepts/keyring-api/フローは同期フローと同じように開始します。 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の概要についてまとめてきました。 実装については別の記事でまとめていきます。 今後も技術的な部分を中心にわかりやすくまとめていこうと思います。 他の媒体でも情報発信しているのでよければ見ていってください! https://chaldene.net/ https://qiita.com/cardene https://zenn.dev/heku ## Publication Information - [Cardene](https://paragraph.com/@cardene-2/): Publication homepage - [All Posts](https://paragraph.com/@cardene-2/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@cardene-2): Subscribe to updates - [Twitter](https://twitter.com/cardene777): Follow on Twitter