Skip to content

mashharuki/IntmaxRepo

Repository files navigation

IntmaxRepo

ステートレスなロールアップ INTMAX および INTMAX Wallet を調査・学習するためのリポジトリです。

Build and Test on GitHub Actions Netlify Status

注意事項

node のバージョンは、18.18.0 以上であることが必要です!

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 {
    // .. 以下略
  }
};

参考文献

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

他の 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 ブラウザアプリ
  39. GitHub - IntMax privacy mining CLI
  40. GitHub - InternetMaximalism/intmax2-mining-cli
  41. intmax2-mining-cli - mainnet-quickstart.md