🛠

INTMAX Wallet SDKを使ってガスレスDappを実装してみた!

2024/09/23に公開

はじめに

皆さん、こんにちは!

7月下旬にEDCONのサイドイベントとして開催した INTMAX というスケーリングソリューションのミートアップを開催した時のハンズオンの内容を技術ブログとしてまとめ直しました!!

https://lu.ma/c18ubb0n

スライドは以下で公開しています!

https://www.canva.com/design/DAGGhlUKo4k/B-p8a_dfUjowiCPdp6XDMg/view?utm_content=DAGGhlUKo4k&utm_campaign=designshare&utm_medium=link&utm_source=editor

また、日置さんに作成していただいたステートレスロールアップの技術解説スライドも下記で公開されています!!

ステートレスロールアップの基礎について学べる非常に参考になる内容です!!

https://images.lumacdn.com/editor-attachments/gr/d01ca462-c8ce-4d9f-9534-68aab877c8d0

ぜひ最後まで読んでください!!

INTMAXとは

INTMAXは数あるレイヤー2ソリューションの中でも独自のアプローチを採用しているプロトコルです。

https://www.intmax.io/

NTMAXでは、わずか5バイトのオンチェーン情報を活用し計算の有効性を用いる事で、従来のブロックチェーンシステムに見られる計算やストレージにおける典型的な諸コストを大幅に削減しようというアプローチを取っています。このアプローチは、スケーラビリティを向上させるだけでなく、ネットワーク全体のセンサーシップレジスタンスを一層強固なものにするとされています。

INTMAXの概要の解説については以下の記事が非常にわかりやすかったのでおすすめです!!

https://coincheck.com/ja/article/629

真に億人レベルが使用されるアーキテクチャを目指して ステートレスなロールアップのアーキテクチャを具現化されようと奮闘されています。

あのEthereumの創業者であるヴィタリック・ブテリン氏からも注目されており、Web3業界の中でも非常に期待されています。

彼のブログの中でも INTMAX が言及されていたりします。

https://vitalik.eth.limo/general/2023/11/14/neoplasma.html

そのINTMAXチームが展開しているプロダクトがもう一つありそれが INTMAX Walletになります。

https://wallet.intmax.io/ja

このINTMAX Walletを導入することで生体認証とMPC、FHEの最新の暗号技術を搭載し、今までにないシームレスなユーザー体験を提供することができます!

今回はそんな INTMAX WalletのSDKを使ってガスレスDappを実装してみるハンズオンを開催してみました!!

このブログを見れば皆さんも実装することができます!!

INTMAX Wallet SDKを使ってガスレスDappを実装してみた!

ではここからはハンズオン用のアプリの動かし方やコードの解説を行なっていきます!!

INTMAX Wallet SDK 用のサンプルアプリ起動方法

  • リポジトリをクローンしてくる。

    git clone https://github.com/mashharuki/IntmaxRepo
    
  • 事前準備

    1. Scroll Sepolia の faucet を取得すること

      例えば以下のサイトで取得できます。

      Scroll Sepolia の faucet が取得できればどのサイトでも良いのでそこにアクセスして Faucet を取得すること!!

    2. ScrollScan の API を取得すること

      デプロイしたコントラクトを Verify するのに使うので以下サイトにアクセスして API キーを作成する。

      ScrollScan API Key

    3. OpenZepplin Defender にログインして ScrollSepolia 上で Relayer を作成し、API キーを取得すること。

      OpenZeppelin Defender Relayer

      後ほど環境変数に使用するため以下の情報をコピペしておく。

    4. 上記で作成した Relayer のウォレットアドレスに少額の ETH (0.5 Sepolia ETH くらい)を送金する(Scroll Sepolia 上で送金してください!!)。

      OpenZeppelin Defender で作成した Relayer アドレス - ScrollScan

      各作成した Relayer のアドレスが表示されているはずなのでそのアドレスに入金すること

    5. コントラクトのデプロイに使用するウォレットアドレスにも少額の  ETH (0.5 Sepolia ETH くらい)を送金する(Scroll Sepolia 上で送金してください!!)。

    6. 環境変数の設定

      環境変数はbackendfrontendでそれぞれ設定する。

      • backend 側の環境変数の設定

        .envファイルをbackendフォルダ配下に作成する。

        cp pkgs/backend/.env.example pkgs/backend/.env
        

        そして以下の環境変数を設定する。

        PRIVATE_KEY=
        SCROLLSCAN_API_KEY=
        DEFENDER_API_KEY=
        DEFENDER_SECRET_KEY=
        

        PRIVATE_KEYは Metamask からコピペしてくる。

        SCROLLSCAN_API_KEYDEFENDER_API_KEYDEFENDER_SECRET_KEYは上記で取得してきたものを貼り付ける。

      • frontend 側の環境変数の設定

        .env.localファイルをfrontendフォルダ配下に作成する。

        cp pkgs/frontend/.env.local.example pkgs/frontend/.env.local
        

        そして以下の環境変数を設定する。

        NEXT_PUBLIC_APP_ICON="https://intmaxwallet-sdk-wallet.vercel.app/vite.svg"
        NEXT_PUBLIC_WALLET_URL="https://intmaxwallet-sdk-wallet.vercel.app/"
        NEXT_PUBLIC_RPC_URL="https://sepolia-rpc.scroll.io/"
        DEFENDER_API_KEY=
        DEFENDER_SECRET_KEY=
        

        NEXT_PUBLIC_WALLET_URL の値は、https://preeminent-creponne-d7212b.netlify.app/でも良い。

        DEFENDER_API_KEYDEFENDER_SECRET_KEYは上記で取得してきたものを貼り付ける。

  • インストール

    yarn
    
  • スマートコントラクトのコンパイル

    yarn backend compile
    
  • スマートコントラクトのテスト

    yarn backend test
    
  • スマートコントラクト デプロイ

    yarn backend deploy --network scrollSepolia
    

    デプロイされたコントラクトのアドレスは、pkgs/backend/outputs/contracts-scrollSepolia.jsonに記載されます。

    もしデプロイがうまくいかないという場合には以下のデプロイ済みコントラクトのアドレスを使ってみてください!

    デプロイ済みコントラクト(ScrollSepolia)

    SampleForwarder

    HelloWorld

  • フロントエンドの定数ファイルの値を更新する。

    pkgs/frontend/src/utils/constants.tsでコントラクトのアドレスを設定しているので、上記でデプロイしたコントラクトのアドレスに更新します。
    ※ デプロイがうまく行かないようであれば、すでに貼り付けてある値をそのまま使用してください。

    export const FORWARDER_CONTRACT_ADDRESS = <デプロイしたアドレス>;
    export const HELLOWORLD_CONTRACT_ADDRESS = <デプロイしたアドレス>;
    
  • コントラクトを Verify する (オプション)

    yarn backend verify --network scrollSepolia
    
  • ガスレスでサンプルコントラクトの機能を呼び出す (オプション)

    yarn backend gaslessSetNewText --network scrollSepolia
    

    /pkgs/backend/scripts/relay/gaslessSetScore.tsファイルの内容を実行します!!

    OZ Defenderの機能を使ってリレイヤーからトランザクション実行させてます!!

    /**
     * レイヤー用のSignerオブジェクトを作成するメソッド
     */
    const getRelayer = async () => {
      const credentials: any = {
        apiKey: DEFENDER_API_KEY,
        apiSecret: DEFENDER_SECRET_KEY,
      };
    
      const ozProvider = new DefenderRelayProvider(credentials);
      const ozSigner = new DefenderRelaySigner(credentials, ozProvider, {
        speed: "fast",
      });
    
      return ozSigner;
    };
    
  • コントラクトに保存されている Text の値を取得する。

    初期値は、newTextになっているはず

    yarn backend getText --network scrollSepolia
    
  • フロントエンドをビルドする

    yarn frontend build
    
  • フロントエンドを起動する

    yarn frontend dev
    

    http://localhost:3000にアクセスします!!

    以下のような画面が立ち上がるのでLet's Loginボタンを押す!

    初回時は Wallet がないので新規作成するかどうか聞かれる。

    Create new walletを選択して新しくウォレットを作成する。

    ニーモニックコードが表示されるので忘れないようにメモかダウンロードを行う。

    Closeボタンを押して、サイトを接続する。

    ポップアップが表示されるのでSignボタンを押す!

    うまく処理されれば INTMAX Wallet SDK のメソッドが呼び出されて Wallet が作成される!!

  • ガスレスでコントラクトに保存されている値を更新してみる。

    うまく Wallet が作成できたらいよいよガスレストランザクションを実行!!

    SendGaslessRequestボタンを押す!

    ポップアップが表示されるのでSignボタンを押す!

    Success というポップアップが表示されたら OK!!

    一応、コンソールの方にも API の処理のログが出力されているので以下のような内容が出力されている確認する。

    トランザクションデータが出力されていれば OK!!

    ========================================= [RequestRaler: START] ==============================================
    request: {
      from: '0xbFc39B0230D743C8F7FAb716E622C1FD2894B148',
      to: '0xEbdef95c2f60D070bD5f10E9D69F55943169A108',
      value: 0n,
      gas: 360000n,
      nonce: 5n,
      deadline: 1717898498n,
      data: '0x2742d0f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000f68656c6c6f20494e544d41585821210000000000000000000000000000000000',
      signature: '0xd13fbf2b821d44c80dee699982183a47b34eb2b2fa29cb179478e41cc4f8dcce041bdd1e0106a1476d7843ea9f79418ee9e3db16273fa098bbfa305f84ba905e1c'
    }
    true
    tx hash: 0x95d24a93afeb154397b92f672fb500d6db52c80ee4ea9e4f13bcc67f66fe6c75
    ========================================= [RequestRaler: END] ==============================================
    

    こんな感じのログが出ていればガスレストランザクション発行できています!!

  • もう一回コントラクトに保存されている Text の値を取得する。

    初期値は空文字だったが、hello INTMAXX!!という文言が取得できるはず!!
    ※ 更新されていない場合は時間をおいて再度実行してみてください。

    別タブを開いて以下を実行。

    yarn backend getText --network scrollSepolia
    

ソースコードの解説(INTMAX Wallet SDK に関する部分)

INTMAX Wallet SDK に関する実装は全て pkgs/frontend/src/context/IntmaxProvider.tsxにまとめてあります!!

このファイルには次の機能を実装しています。

  1. SDK 用のインスタンスを生成するメソッド
  2. connect するメソッド
  3. トランザクションを送信するメソッド
  4. ガスレスでトランザクションを送信するメソッド

順番に説明していきます

  1. の部分ではintmax-walletsdk/dappethereumProviderintmaxDappClientを使って実装しています!!
/**
 * SDK用のインスタンスを生成するメソッド
 * @param walletUrl
 * @returns
 */
const createSdk = () => {
  setLoading(true);

  try {
    const client = intmaxDappClient({
      wallet: {
        url: DEFAULT_WALLET_URL,
        name: "DEMO Wallet",
        window: { mode: "iframe" }, // modeは iframeかpopupを選択できる
      },
      metadata: DAPP_METADATA,
      providers: {
        eip155: ethereumProvider({
          httpRpcUrls: {
            534351: RPC_URL, // 今回はScroll Sepoliaに接続するように設定
          },
        }),
      },
    });
    // SDK インスタンスをセット
    setSdk(client);
    return client;
  } catch (err: any) {
    console.error("err:", err);
  } finally {
    setLoading(false);
  }
};

これで connect する準備ができました!!

  1. の部分については 1.で作成したインスタンスの機能を使って connect しています。
    ※ 今回は同時にeth_signAPI も呼び出して署名も実施するようにしています!
const sdk = createSdk();

const ethereum = sdk!.provider(`eip155:${CHAIN_ID}`);
// ウォレット情報を取得する。
await ethereum.request({ method: "eth_requestAccounts", params: [] });
const accounts = (await ethereum.request({
  method: "eth_accounts",
  params: [],
})) as string[];
console.log("Account Info:", accounts);
setAccounts(accounts);
setAddress(accounts[0]);

// ログイン時に署名
const result = await ethereum.request({
  method: "eth_sign",
  params: [accounts[0], "Hello INTMAX WalletSDK Sample Dapp!!"],
});
console.log(result);
  1. についても同様に 1.で作成したインスタンスの機能を使ってトランザクションを送信することになります。
/**
 * トランザクションを送信するメソッド
 */
const sendTx = async (to: string, value: string) => {
  const ethereum = await sdk.provider(`eip155:${CHAIN_ID}`);

  setLoading(true);
  try {
    // send Simple Transaction
    const result = await ethereum.request({
      method: "eth_sendTransaction",
      params: [
        {
          from: address,
          to: to,
          value: parseEther(value),
        },
      ],
    });

    console.log("tx info:", `https://sepolia.etherscan.io/tx/${result}`);

    // .. 以下略
  } catch (err: any) {
    console.error("error:", err);
    // .. 以下略
  } finally {
    // .. 以下略
  }
};

4.についてもこれまでとほぼ同じ流れです。ここでは、メタトランザクションで使う署名データ生成のためにeth_signTypedData_v4の API を呼び出しています。

残りの実装部分についてはメタトランザクションを実装する時のほぼ同じ流れです!!

/**
 * ガスレスでコントラクトのメソッドを呼び出す
 */
const gasslessRequest = async () => {
  console.log(
    "================================= [gasless: START] ================================="
  );

  const ethereum = await sdk.provider(`eip155:${CHAIN_ID}`);
  const provider = await new ethers.JsonRpcProvider(RPC_URL);

  setLoading(true);
  try {
    // create forwarder contract instance
    const forwarder: any = new Contract(
      FORWARDER_CONTRACT_ADDRESS,
      SampleForwarderJson.abi,
      provider
    ) as any;
    // create ScoreValut contract instance
    const helloWorld: any = new Contract(
      HELLOWORLD_CONTRACT_ADDRESS,
      HelloWorldJson.abi,
      provider
    ) as any;

    // 呼び出すメソッドのエンコードデータを用意
    // 今回は"hello INTMAXX!!"という文字列を引数にして HelloWorldコントラクトのsetNewTextメソッドを呼び出したいと思います!
    const encodedData: any = helloWorld.interface.encodeFunctionData(
      "setNewText",
      ["hello INTMAXX!!"]
    );

    // get domain
    const domain = await forwarder.eip712Domain();
    // get unit48
    const uint48Time = getUint48();

    console.log("encodedData:", encodedData);
    console.log("domain:", domain);
    console.log("uint48Time:", uint48Time);

    // test sign messages
    const typedData = {
      domain: {
        name: domain[1],
        version: domain[2],
        chainId: CHAIN_ID, // scroll sepolia
        verifyingContract: domain[4].toString(),
      },
      types: {
        ForwardRequest: ForwardRequest,
      },
      primaryType: "ForwardRequest",
      message: {
        from: address.toString(),
        to: HELLOWORLD_CONTRACT_ADDRESS.toString(),
        value: 0,
        gas: 360000,
        nonce: (await forwarder.nonces(address)).toString(),
        deadline: uint48Time.toString(),
        data: encodedData.toString(),
      },
    };

    // create request data
    // eth_signTypedData_v4 のAPIを使って署名データを作成
    const sig = await ethereum.request({
      method: "eth_signTypedData_v4",
      params: [address, JSON.stringify(typedData)],
    });

    console.log("sig:", sig);

    // call requestRelayer API
    const gaslessResult = await fetch("/api/requestRelayer", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: address,
        to: HELLOWORLD_CONTRACT_ADDRESS,
        value: 0,
        gas: 360000,
        nonce: (await forwarder.nonces(address!)).toString(),
        deadline: uint48Time.toString(),
        data: encodedData,
        signature: sig,
      }),
    });

    console.log(await gaslessResult.json());

    // .. 以下略
  } catch (err: any) {
    // .. 以下略
  } finally {
    // .. 以下略
  }
};

コントラクト側のコードですが、 メタトランザクションの機能を試すための非常にシンプルなコントラクトを2つだけ用意したものになっています。

  • Forwarderコントラクト

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    import '@openzeppelin/contracts/metatx/ERC2771Forwarder.sol';
    
    contract SampleForwarder is ERC2771Forwarder {
      constructor() ERC2771Forwarder("SampleForwarder") {} 
    }
    
  • HelloWorldコントラクト

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    import '@openzeppelin/contracts/metatx/ERC2771Context.sol';
    
    contract HelloWorld is ERC2771Context {
    
      string public text;
    
      /**
      * constructor 
      */
      constructor(
        address _trustedForwarder
      ) ERC2771Context(_trustedForwarder) {}
    
      /**
      * setNewText
      */
      function setNewText(string memory _newText) public {
        text = _newText;
      }
    
      /**
      * getText
      */
      function getText() view public returns (string memory) {
        return text;
      }
    
      ///////////////////////////////// ERC2771 method /////////////////////////////////
    
      function _msgSender()
        internal
        view
        virtual
        override
        returns (address sender)
      {
        if (isTrustedForwarder(msg.sender)) {
          // The assembly code is more direct than the Solidity version using `abi.decode`.
          /// @solidity memory-safe-assembly
          assembly {
            sender := shr(96, calldataload(sub(calldatasize(), 20)))
          }
        } else {
          return super._msgSender();
        }
      }
    
      function _msgData()
        internal
        view
        virtual
        override
        returns (bytes calldata)
      {
        if (isTrustedForwarder(msg.sender)) {
          return msg.data[:msg.data.length - 20];
        } else {
          return super._msgData();
        }
      }
    }
    

まとめ

今回は INTMAXチームが提供しているプロダクトのうち INTMAX Wallet をフォーカスした記事を執筆しました。

導入しやすいですし、ユーザーはわかりやすいUI/UXとセキュリティの恩恵を享受できるため、非常に良いプロダクトだと思います!!

Solidityで書かれたスマートコントラクトもデプロイ可能な Plasma Free が来た時に再度調べ直して技術ブログを執筆したいと思います。

ここまで読んでいただきありがとうございました!!

参考文献

以下参考にしたサイトや文献です!!

他の ZK ロールアップのことも学べそうな参考リンクも貼り付けています。

  1. Scaling Ethereum 2023
  2. GitHub - webmax.js Public
  3. Intmax Wallet
  4. IntMax の公式サイト
  5. GetStarted
  6. Scroll bridge
  7. CLI のガイドライン
  8. intmax rolluo cli
  9. hardhat-Plugin
  10. Sample-Auction-dapp
  11. PRTIMES - INTMAX Walletless Wallet
  12. INTMAX Wallet Home Page
  13. GitHub - intmax-walletsdk
  14. npm - INTMAX WalletSDK
  15. INTMAX WalletSDK サンプル実装
  16. INTMAX Wallet SDK - GitBook
  17. レイヤー 2「INTMAX」とは?真の金融インフラを開発する日置玲於奈氏の展望に迫る
  18. INTMAX、「Plasma Next」メイネット α をローンチ。Plasma の完成により拡張性向上
  19. 大衆向けイーサリアムのスケーリング: INTMAX が Plasma Next を発表
  20. INTMAX ホワイトペーパー
  21. Plasma Next: Plasma without Online Requirements
  22. Youtube - INTMAX のステートレスなロールアップが他の ZK ロールアップと何が違うのが概要だけ解説してくれている動画
  23. 【完全保存版】zkEVM とは何か
  24. 初心者向け: #zkEVM とは?
  25. いま話題の「zkEVM」とは何か?~農業への応用を考察~
  26. イーサリアム開発者ドキュメント - プラズマチェーンとは何か?
  27. レイヤー 2 技術の Plasma は、提案から 2 年を経て実用段階に近づく= BlockChainJam 2019
  28. ヴィタリック、スケーリングソリューション「Plasma」の評価を再検討すべきと主張
  29. GitHub - Plasma ホワイトペーパー日本語訳
  30. Medium - Recursive Zero-Knowledge Proofs
  31. Intmax Wallet SDK のサンプル実装例を取り上げたブログ記事
  32. INTMAX Wallet SDK Sampple - GitHub - Sports-Voting-Demo
  33. PLASMACON - 公式サイト
  34. INTMAX、ラテンアメリカの著名な Web3 ビルダーを発表。Plasma Free、革新的な EVM 互換プロトコルの開発を主導
  35. INTMAX DeveloperHub
  36. Plasma Free についての X の投稿
  37. Exit games for EVM validiums: the return of Plasma
  38. INTMAX Wallet ブラウザアプリ
GitHubで編集を提案

Discussion