CoinChoice Snap: 任意の通貨でガスをチャージ

ETHDenver ハッカソン受賞

読了時間:11分
CoinChoice Snap: 任意の通貨でガスをチャージ

MetaMask Snapsは、MetaMaskを世界で最も拡張性の高いウォレットにするためのロードマップです。開発者として、まったく新しい方法でMetaMaskに機能やAPIを追加することができます。Web3開発者はこの成長の中核を担っており、本シリーズでは現在開発中の革新的なMetaMask Snapsを紹介することを目的としています。

CoinChoice Snap

Snap リポジトリ: https://github.com/coinchoice/coinchoice/

開発した理由は?

CoinChoiceは、Web3ユーザーが価値のあるトークンを保有するウォレットを持ちながら、トランザクションを実行するためのガス代となる通貨を持っていないという、小さくて面倒ながらも広く見られる問題から生まれました。

この問題は、流動性が分散しているユーザーにとって特に顕著です。複数のウォレットを管理し、ブロックチェーンエコシステム全体でさまざまな機会を活用しているユーザーは、各ウォレットにガストークンを分散させることになり、デジタル資産やトランザクションを処理するために本来は投資に充てるべき資本を確保しなければなりません。さらに、ガスを保有していないネットワークにトークンを転送した場合、ユーザーはその後の操作を行う前にガスを入手する必要があります。通常、これはユーザーが中央集権型取引所(CEX)にアクセスしてガスを入手・出金することを意味し、CEXの出金手数料が発生します。

根本的に、このエクスペリエンスには欠陥があります。ユーザーがWeb3にアクセスするために中央集権型取引所に依存すべきではありません。ユーザーはガスの管理を心配することなく、分散型システムとシンプルにやり取りできるべきです。これはガスが不要だということではありません。ガスはネットワークのオペレーターへの報酬や、スパム・DDoS攻撃の防止という重要な役割を果たしています。ガスはただ、より柔軟であるべきなのです。dAppやゲームの開発者がネイティブトークンを持っている場合、そのトークンをガス代の支払い通貨として使用できるべきです。

このような考え方のもと、CoinChoiceが開発されました。

CoinChoiceはユーザーのブラウザ内に存在し、すべてのトランザクションに対して、ユーザーが希望する通貨でガスを管理する機能を提供します。ユーザーがETHよりもUSDCを保有したい場合、ガス代をUSDCで支払うことができます。

CoinChoiceのMetaMask Snapはこのエクスペリエンスをさらに補完します。MetaMaskブラウザ拡張機能内で直接、ユーザーは選択したガス通貨と引き換えにETHをチャージすることができます。これにより、MetaMaskは中央集権型取引所への訪問を不要にするソリューションとなり、Web3エクスペリエンスを統合・簡素化し、新規Web3ユーザーが増え続けるサービスやプラットフォームに圧倒されることなく、一部のシステムに慣れ親しむことを可能にします。

技術的な実装について説明してください。

CoinChoiceは、ガス代の支払いに使用するコインを選択できるMetaMask&ブラウザ拡張機能です。

パッケージ

  • backend:NestJS Webサーバーで、以下を担当します。Tenderly APIと0x Quote APIに対してトランザクションをシミュレートし、選択した通貨でのガス見積もりを算出する。クライアントのMetaMask Snapとブラウザ拡張機能間でメッセージを受信・配信する。リレーコントラクトに転送するmeta-txを受信する。

  • browser-extension:Plasmoフレームワークを使用したブラウザ拡張機能で、以下を提供します。ガス資金調達のためのETHへのスワップを注入するために、MetaMaskへのRPCリクエストをインターセプトする。ガスコインを選択できるポップアップを提供する。Cypher Onboarding SDKを使用したトークン管理のポップアップを提供する。すべてのデータはバックエンドサービスに送信され、ブロックチェーンのインタラクションとシミュレーションが管理される。

  • contracts:0x Protocolでのスワップを実現するためにmeta-transactionのリレーを処理するスマートコントラクト。

  • frpc:Fluence NetworkのfRPC Gatewayを活用するパッケージ。

  • meta-mask:React.jsサイトと関連するMetaMask Snapを含むパッケージ。SnapはMetaMaskインターフェース内で直接「Need ETH?」というメッセージを表示することで、ETHスワップを提供する役割を担います。Snapはバックエンドにリクエストを送信し、クライアントのブラウザ拡張機能にメッセージを転送することで、MetaMask SnapがブラウザExtensionを起動できるエクスペリエンスを実現します。関連サイトはMetaMask Snapのインストールを有効にするために使用されます。

技術的なセットアップ

CoinChoiceは、選択した通貨でガスをチャージする機能をトランザクションに追加するブラウザ拡張機能です。

ブラウザ拡張機能は、各トランザクションの前に、選択した通貨でガスを支払うようユーザーに促すよう設計されています。

ユーザーが承認した場合、選択したトークンの推定量をETHに変換するmeta-transactionがキャプチャされます。

このmeta-transactionはバックエンドサービスに転送され、そこからリレースマートコントラクトに転送されて実行されます。

meta-transactionが完了すると、適切な量のガスが入金されたことで、ユーザーの元のトランザクションが実行可能な状態になります。

MetaMask Snapは、MetaMask拡張機能内に孤立していたトランザクションをCoinChoice拡張機能に転送するよう設計されています。これは以下の手順で行われます。

  1. トランザクションの詳細をバックエンドサービスに送信する

  2. バックエンドがWebSocket接続を通じてブラウザ拡張機能にトランザクションデータを転送する

  3. ブラウザ拡張機能がこのWebSocketメッセージを受信すると、選択した通貨と引き換えに自動的に推定されたガス量をチャージするフローを開始する

メッセージバスとしてのバックエンド

CoinChoiceは、MetaMask Snapのサンドボックス化された性質により、2つのフロントエンドコンポーネント間のメッセージバスとしてバックエンドを使用するアプローチを採用しました。

Snapのコード分離により、MetaMask拡張機能の外部のコンテキストに存在するフロントエンドコンポーネントとの通信が妨げられます。

ただし、Snapsはサーバーサイドのリクエストを処理できるため、ユーザーセッションを管理できるバックエンドサービスを使用して、フロントエンドのコンテキスト間でメッセージを受け渡すことができます。

この例は、ソースコードを通じて示すことができます。

MetaMask Snap内で、サーバーにリクエストを送信します。

参照: https://github.com/Coinchoice/coinchoice/blob/main/packages/meta-mask/packages/snap/src/index.ts#L12

import { OnTransactionHandler } from '@metamask/snaps-types';
import { heading, panel, text } from '@metamask/snaps-ui';

export interface JsonRpcRequest {
    id: string | undefined;
    jsonrpc: '2.0';
    method: string;
    params?: Array<any>;
}

const onTriggerAPI = async (transaction: JsonRpcRequest) => {
    await fetch(`https://api.coinchoice.link/metamask`, {
            method: 'POST',
            body: JSON.stringify(transaction),
            headers: { 'Content-Type': 'application/json' },
        })
    .then((res) => {
        if (!res.ok) {
            throw new Error('Bad response from server');
        }
        return res.json();
    })
    .then((json) => console.log(json))
    .catch((err) => console.error(err));
};

// Handle outgoing transactions
export const onTransaction: OnTransactionHandler = async ({ transaction }) => {
    // trigger browser extension for swap
    const response = await onTriggerAPI(transaction);
    // this is shown if no transaction is triggered
    if (typeof transaction.data === 'string' && transaction.data !== '0x') {
        return {
            content: panel([
                heading('Need ETH for gas?'),
                text('Receive ETH gasless for any currency with Coinchoice!'),
            ]),
        };
    }

    const currentGasPrice = await ethereum.request<string>({
        method: 'eth_gasPrice',
    });

    const transactionGas = parseInt(transaction.gas as string, 16);
    const currentGasPriceInWei = parseInt(currentGasPrice ?? '', 16);
    const maxFeePerGasInWei = parseInt(transaction.maxFeePerGas as string, 16);
    const maxPriorityFeePerGasInWei = parseInt(transaction.maxPriorityFeePerGas as string, 16);

    const gasFees = Math.min(
        maxFeePerGasInWei * transactionGas,
        (currentGasPriceInWei + maxPriorityFeePerGasInWei) * transactionGas
    );

    const transactionValueInWei = parseInt(transaction.value as string, 16);
    const gasFeesPercentage = (gasFees / (gasFees + transactionValueInWei)) * 100;

    return {
        content: panel([
            heading('Need ETH for gas?'),
            text(
                `As setup, you are paying **${gasFeesPercentage.toFixed(
                2
                )}%** in gas fees for this transaction.`
            ),
            text(`Also: **${response}**`),
        ]),
    };
};
コピー

次に、サーバー内で、受信したトランザクションペイロード内のfromパラメータを使用してウォレットと関連するセッション識別子を特定します。

参照: https://github.com/Coinchoice/coinchoice/blob/main/packages/backend/src/app.controller.ts#LL36-L49C3

@Post('metamask')
async metamask(@Body() data: any) {
    const address = data.from;
    const wallet = await this.walletService.findOne(address);
    if (wallet) {
        console.log(`clientId for ${address} is: ${wallet.clientId}`);
        if (wallet.clientId) {
            this.appGateway.server.to(wallet.clientId).emit('onMetamask', {
                msg: data,
                content: 'Hello from the backend!',
            });
        } else throw new NotFoundException('ClientId not found for this wallet');
    } else throw new NotFoundException('Wallet not found');
}
コピー

最後に、ブラウザ拡張機能内でWebSocket接続を通じてバックエンドサービスに接続し、その接続自体がユーザーセッション識別子を確立します。参照: https://github.com/Coinchoice/coinchoice/blob/main/packages/browser-extension/src/contents/main.ts#L107

const socket = io(API_HOST);
socket.on('connect', function () {
    console.log('CS SOCKET: Connected');
});
socket.on('events', function (data) {
    console.log('CS SOCKET: event', data);
});
socket.on('onMessage', async function (data) {
    console.log('CS SOCKET: onMessage', data);
    if (data.msg.startsWith('Hello client')) {
        const [_, clientId] = data.msg.split('#');
        wallet.clientId = clientId;
        facade.setWallet(wallet);
        try {
            await busPromise('connect-wallet', { wallet });
        } catch (e) {
            console.log('CS SOCKET ERROR: onMessage - connect wallet');
            console.error(e);
        }
    }
});
socket.on('onMetamask', async function (data) {
    console.log('CS SOCKET: onMetamask', data);
    // On Metamask, open the Notification.
    const { msg: tx }: { msg: TxRequest } = data;
    // Cancel the original tx

    await facade.waitForSignature({ method: 'need_eth', params: [tx] });
});
コピー

このコードスニペットでは、onMetamaskメッセージを通じて受信したトランザクションデータが、署名(meta-transaction)のプロンプトをどのように開始するかを確認できます。

Snapインストール用ウェブサイト

MetaMask Snapをインストールするには、ウェブサイトが適切なJSON RPCエンドポイントを呼び出す必要があります。

/**
 * Connect a snap to MetaMask.
 *
 * @param snapId - The ID of the snap.
 * @param params - The params to pass with the snap to connect.
 */
export const connectSnap = async (
  snapId: string = defaultSnapOrigin,
  params: Record<'version' | string, unknown> = {},
) => {
  await window.ethereum.request({
    method: 'wallet_requestSnaps',
    params: {
      [snapId]: params,
    },
  });
};
コピー

https://cchoice.xyz/でホストされているMetaMask Snapインストール用のCoinChoiceウェブサイトは、MetaMaskチームのリポジトリ(https://github.com/ziad-saab/snappy-recovery)からインスピレーションを得ています。

CoinChoiceを始める

始めるには、https://app.coinchoice.link/のガイドを使用して、テストネット対応版のCoinChoiceをインストールしてください。

今後の実装予定は?

CoinChoiceはGoerli Ethereumテストネット向けに実装・テストされていますが、まだかなり実験的な段階です。

CoinChoiceの継続的な開発を可能にするインセンティブが整っていると仮定すると、次のステップはすべての主要なEVM互換ブロックチェーンでCoinChoiceを有効にし、できるだけ多くのユーザーにブラウザ拡張機能を届けることです。

露出と配布は常に課題であり、そのためコミュニティ開発への投資と、ガス要件に関するこの懸念を軽減しようとしているWeb3プラットフォーム、特にクロスチェーンプラットフォームとのパートナーシップが必要になります。

アプリケーションが使用されてエンドユーザーからフィードバックが収集されると、バグ修正やその他の改善点を特定し、エンドユーザーエクスペリエンスをさらに向上させることができます。

将来の開発については、呼び出し元コンテキストに関連する問題により実装されなかったAccount Abstractionの初期検討が再考される可能性があります。また、CoinChoiceのコア機能をモジュール化して、dApp開発者が自分たちのアプリケーションにガス管理を簡単に組み込めるようにすることも考えられます。

チームについて教えてください。

CoinChoiceチームは非常に多様なバックグラウンドを持つメンバーで構成されていますが、分散型の未来に向けた素晴らしいソフトウェアを作るという共通の目標を共有しています。

私はRyanです。CoinChoiceブラウザ拡張機能を作成し、ソリューションのアーキテクチャをサポートしたエンジニアです。EthDenverのDiscordを通じてチームメンバーを探しました。チームメンバー全員が実際に対面で会ったのはデンバーでのことでした。それぞれが独自のスキルセットを持ち寄り、非常に良いチームワークを発揮することができました。意見の相違や問題には正面から向き合い、責任と成功はすべてのメンバーで平等に分かち合いました。ハッカソンの経験を通じて、チーム全員が一緒に働くことは喜びだったと自信を持って言えます。この取り組みから新たな機会が生まれた場合、全員がお互いをサポートするために参加できます。

チームの経験について言えば、Achimは経済学と数学が交差するエンジニアの最良の例です。Achimはレバレッジをかけたデジタル資産管理の可能性を押し広げるDeFiプロトコル1Delta.ioを開発しました。この経験は、本番環境レベルのセキュリティでCoinChoiceのスマートコントラクトを開発・デプロイする上で非常に重要でした。

Andreはweb2のバックグラウンドを持ち、meta-transactionの管理・実行やその他のアプリケーションデータに不可欠なサーバーサイドプロセスの開発・管理に最適な人材でした。Andrewはブラジルのweb2エージェンシーWeb Elevenを率いています。

Christieはデッキ作成、ブランディング、コンテンツ、プロジェクト管理を通じて非技術的な面でチームをサポートしました。Christieはオーストラリアのweb2エージェンシーWeb Doodleを率いています。

私自身はソリューションアーキテクチャとエンジニアリングのバックグラウンドを持っています。パートナープログラムプラットフォームを開発し、そのようなプラットフォームをサポートする新しい分散型インフラを探求しているWeb3スタートアップUsher.soを率いています。フロントエンド開発とソリューションアーキテクチャを担当しながら、EthDenverハッカソンにプロジェクトとチームをコーディネートした経験を活かすことができました。

MetaMask SnapsとWeb3空間に対してどのような可能性を感じていますか?

MetaMaskは、ユーザーがWeb3に関わる際に最初に触れるアプリケーションの一つとしての地位を確立しつつあります。ユーザーエクスペリエンスの拡張性を可能にすることで、MetaMaskは開発者が早期かつ親しみやすいタッチポイントで自分たちの価値提案を紹介することを可能にします。これは、次世代のWeb3ユーザーに向けたWeb3へのゲートウェイが統合されると、さらに重要になります。これは、私たちがすでによく知っているインターネットへのゲートウェイの統合に似ており、ソーシャルネットワークや検索エンジンのようなエンドユーザー向けアプリケーションが多くのインターネットユーザーにとって最初の接点となっています。

MetaMask Snapsはより文脈に即したエクスペリエンスを可能にします。例えば、MetaMaskウォレットがやり取りしているdAppに応じて異なる動作をするSnapが考えられます。Web3へのオーバーレイユーザーインターフェースの上に開発者がビルドできるようにすることで、Web3との関わり方を根本的に変える、より興味深い開発を取り込むことができます。特定のプロトコルのUIを提供するdAppとは異なり、Snap開発者はすべてのプロトコルとの関わり方を変えることができます。なぜなら、イノベーションはこれらのインタラクションを可能にするウォレット内に存在するからです。

最後に、トランザクションの透明性を高め、Web3のインタラクションを簡素化し、エンドユーザーに追加のセキュリティを提供することを目指すMetaMask Snapsは、Web3を根本的により安全で、より多くのユーザーにとって実用的なものにするでしょう。

MetaMask Snapsを試したい開発者へのアドバイスはありますか?

MetaMask Snapsを実装した生のソースコードを参照して、これらの新しい機能がどのように機能するかについてより深い洞察を得てください。MetaMask Snapsチームは、プロジェクト内でMetaMask Snapsを示す、よく開発されたコードを公開するという素晴らしい仕事をしています。ドキュメントが機能の概要を提供する一方で、ソースコードはこれらの機能がどのように実装・活用されているかを示しています。

初期開発段階では、Snapsフレームワーク内で可能なことへの期待値を下げてください。MetaMask Snapsは最終的にMetaMaskの完全な拡張性を実現しますが、Snapsフレームワーク内のさまざまなコンポーネントへのアクセスは段階的にリリースされます。MetaMaskはエンドユーザーのセキュリティを最大化する際にバランスを取る必要があり、初期ビルドに含まれる一部の機能は再検討される可能性があります。ソリューションがユーザーのブラウザコンテキストへのより多くのアクセスを必要とする場合は、CoinChoiceのソースコードを参照して、MetaMask Snap内のアクションによってトリガーされるブラウザ拡張機能の開発を検討してください。

MetaMaskチームに連絡する。MetaMaskのチームは拡張性の実現に非常に積極的に取り組んでおり、フィードバックやこの価値提案をより良く実現するための機会を求めています。

MetaMask Snapsでビルドを始める

MetaMask Snapsを始めるには:

  1. 開発者ドキュメントをチェックする

  2. MetaMask Flaskをインストールする

  3. MetaMask Snapsガイドを確認する

  4. TwitterGitHubディスカッションDiscordでつながり続ける

お近くで開催される次のハッカソンでチームに注目してください!Happy BUIDLing ⚒️

免責事項:MetaMask Snapsは一般的にConsensys Software以外のサードパーティによって開発されています。サードパーティが開発したMetaMask Snapsの使用は、ご自身の判断とリスクで行われ、そのような活動から生じる損失や損害についてはお客様が単独で責任を負うことに同意したものとみなされます。Consensysはサードパーティが開発したMetaMask Snapsについて、口頭または書面による明示的または黙示的な保証を行わず、サードパーティが開発したSnapsに関するすべての責任を否認します。ブロックチェーン関連ソフトウェアの使用にはリスクが伴い、MetaMask Snapsを使用する際にはそのリスクをすべて自己負担するものとします。

AIによる翻訳です。エラーが含まれている可能性があります。常に情報を確認してください。

翻訳を評価する
  • MetaMask
    MetaMask

    ConsenSysが構築した、Web3への入口となる主要なセルフカストディ型暗号資産ウォレット。

    すべての記事を読む