Metamask Snaps[概要編]

今回はMetamask Snapsの概要についてまとめていきます。

以下のドキュメントをもとにまとめていきます。

概要

Memtask Snapsを使用すると、サーバーパーティー開発者によって作成された機能をMetaask追加できるようになります。これによりMetamaskウォレットをカスタマイズできるようになります。Metamask Snapsを使用することで以下のことができるようになります。

  • EVMチェーン以外のブロックチェーンに接続する。

  • トランザクションに署名をするときに、実行しようとしているトランザクションに問題がないか分析をする。

  • Metamaskに直接通知を飛ばして最新情報を取得する。

また、各Snapの設定からSnapを無効にすることもできます。以下からSnapの一覧を確認できます。

Metamask Snapsは安全か?

Snapsはサンドボックス環境で実行され、アクセス許可モデルを使用してデータを保護します。 SnapsはMetamaskアカウントのデータにアクセスすることはできません。ただ、Snapsをインストールするときは、セキュリティの観点からどのような権限を付与しているかを理解する必要はあります。

現在のMetamask Snaps

現在のMetamask Snapsはオープンベータ版であり、Metamaskチームとサードパーティによって監査され、ホワイトリストに登録された機能だけを使用することができます。いずれはこの部分をパーミッションレスにすることを目指しています。

Metamask Snapsとは?

Metamask Snapsとは、ウォレットをカスタムする、隔離された環境(サンドボックス)で実行されるJavaScriptpプログラムです。Snapはインストール中にユーザーが付与した権限によって決定される、制限された機能セットにアクセスすることができます。

Metamask Snapsでは以下のことができます。

  • ダイアログ

    • Metamaskにカスタムのアラートや確認画面を表示できます。
  • 通知

    • Metamaskに直接通知できる機能。
  • 暗号化ストレージ

    • ユーザーのデバイスにデータを安全に保存、管理する機能。
  • 非EVMチェーンのサポート

    • MetamaskでEVM以外のブロックチェーンアカウントとアセットを管理できます。
  • トランザクションの分析

    • Metamaskのトランザクション実行前に、トランザクションに問題がないかチェックします。
  • Cronジョブ

    • ユーザーに対する定期的なアクションを実行します。
  • カスタムUI

    • 用意されたコンポーネントを使用して、MetamaskでカスタムUIを表示する機能。
  • ネットワークアクセス

    • fetchを使用してAPIを呼び出す機能。

コンセプト

Snapのファイル構造

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実行環境には、DOMNode.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は応答しないとみなされます。

  1. 30秒間JSON-RPCリクエストを受信して​​いない。

  2. 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 プログラムです。DOMNode.jsビルトインも、MetaMaskのwalletグローバル オブジェクト以外のプラットフォーム固有のAPIもありません。Node.jsにあるほとんどすべての標準JavaScript グローバルは通常どおり使用できます。

  • console

  • crypto

  • fetchendowment:network-access許可を得て)

  • setTimeout/clearTimeout

  • setInterval/clearInterval

  • SubtleCrypto

  • WebAssemblyendowment:webassembly許可を得て)

  • TextEncoder/TextDecoder

  • atob/btoa

  • URL

実行環境は、次の目的で用意されています。

  1. SnapがMetaMask自体を含む他の実行中のコードに影響を与えないようにし、すべての悪意のあるSnapがユーザーの資産などを盗んだりするのを防ぎます。

  2. Snapが機密性の高いJavaScript APIに許可なくアクセスできないようにします。

  3. 実行環境がプラットフォームに依存しないこと。

これにより、Snapがどこでどのように実行されるかを気にする必要がなく、どこでも安全にSnapを実行できます。

セキュア ECMAScript (SES )

Secure ECMAScript (SES)は、相互に疑わしいプログラムが同じJavaScriptプロセスで実行できるように設計されたJavaScript言語のサブセットです。

Snapのデザインガイドライン

Metamaskのデザインを変更するときのガイドラインがまとまっています。詳細は以下のドキュメントページを見てください。

キーリングAPIについて

Snaps Keyring APIは、MetaMask内にカスタムEVMアカウントを統合します。 これにより、カスタムアカウントをUIで通常のMetaMaskアカウントと同じように表示できるようになります。

https://docs.metamask.io/snaps/concepts/keyring-api/
https://docs.metamask.io/snaps/concepts/keyring-api/

関連用語

  • ブロックチェーンアカウント

    • 残高、ノンス、その他のアカウントの詳細を含むアカウントを表す、単一のブロックチェーン内のオブジェクト。
  • リクエスト

    • DappからMetaMaskへのリクエスト。
  • キーリング アカウント

    • 1つ以上のブロックチェーンアカウントを表すアカウントモデル。
  • Keyring snap

    • Keyring APIを実装するSnap。
  • Keyring request

    • MetaMaskからKeyring Snapへのリクエスト。

    • MetaMaskは、Dappによって送信された元のリクエストをラップし、それにメタデータを追加します。

コンポーネント

以下の図はキーリングSnapによって管理されるアカウントを操作する時に発生するコンポーネントを表しています。

https://docs.metamask.io/snaps/concepts/keyring-api/
https://docs.metamask.io/snaps/concepts/keyring-api/
  • User

    • Snap、dapp、MetaMaskを操作するユーザー。
  • Dapp

    • アカウントに対して実行されるアクションをリクエストするDapp。
  • MetaMask

    • Dappが接続するウォレット。

    • 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/
https://docs.metamask.io/snaps/concepts/keyring-api/

Metamaskアカウントのモーダル内には「Add snap account」というオプションがあります。

https://docs.metamask.io/snaps/concepts/keyring-api/
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/
https://docs.metamask.io/snaps/concepts/keyring-api/

このフローは、DappがMetamask JSON-RPCメソッドを呼び出した時やユーザーがMetamask UIから新しい資金の移動開始した時に実行開始されます。その時点で、MetaMaskはこのやりとりがキーリングSnapによって制御されるアカウントに対して要求されていることを検出します。ユーザーがUIでトランザクションを承認した後、MetaMaskはインターフェイスsubmitRequestメソッドを呼び出します。submitRequestは、元のRPCリクエストを受信し、 pendingfalseに設定し、resuiltを要求された署名に設定したSubmitRequestResponse を返します。

⚠️注意

キーリングSnapが eth_sendTransactionリクエストを受信した場合、それをeth_signTransactionリクエストのように扱う必要があります。 Snapは応答で署名を提供する責任を負い、メタマスクはトランザクションをブロードキャストする責任を負います。

非同期署名

キーリングSnapがしきい値署名などの複雑なスキームを実装する場合、より多くのKeyringメソッドを使用して非同期署名フローを実装します。非同期フローは次のようになります。

https://docs.metamask.io/snaps/concepts/keyring-api/
https://docs.metamask.io/snaps/concepts/keyring-api/

フローは同期フローと同じように開始します。 Dappまたはユーザーが、トランザクションまたは任意のデータに署名するリクエストを開始します。 承認後、SnapのKeyringインターフェースのsubmitRequestメソッドが呼び出される。

Snapはリクエストに直接応答しないため、snap_manageStateを使用して保留中のリクエストを内部ステートに格納します。 KeyringインタフェースのlistRequestsメソッドかgetRequestメソッドが呼び出されると、保留中のリクエストのリストが返されます。保留中のリクエストを保存した後、Snapはsnap_dialogを使用してポップアップを作成し、コンパニオンDappのURLにアクセスするようユーザーに指示します。

Dappは、KeyringSnapRpcClientlistRequestsメソッドによって促進されるRPCコールを使用して、Snapの保留中のリクエストを一覧表示します。 その後、ユーザーはSnapに適用される処理を使用して、これらのリクエストに対処できます。署名プロセスが完了すると、コンパニオンDappはKeyringSnapRpcClientapproveRequestメソッドを使用してリクエストを承認し、同名のKeyringインターフェースのメソッドを呼び出します。 このメソッドは、リクエストのIDと最終結果を受け取ります。approveRequestが呼び出されると、snap_manageAccountssubmitResponseサブメソッドを使用して保留中のリクエストを処理できます。

最後に

今回はMetamask Snapsの概要についてまとめてきました。

実装については別の記事でまとめていきます。

今後も技術的な部分を中心にわかりやすくまとめていこうと思います。

他の媒体でも情報発信しているのでよければ見ていってください!

Subscribe to Cardene
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.