INTMAX Wallet SDKを使ってガスレスDappを実装してみた!
はじめに
皆さん、こんにちは!
7月下旬にEDCONのサイドイベントとして開催した INTMAX というスケーリングソリューションのミートアップを開催した時のハンズオンの内容を技術ブログとしてまとめ直しました!!
スライドは以下で公開しています!
また、日置さんに作成していただいたステートレスロールアップの技術解説スライドも下記で公開されています!!
ステートレスロールアップの基礎について学べる非常に参考になる内容です!!
ぜひ最後まで読んでください!!
INTMAXとは
INTMAXは数あるレイヤー2ソリューションの中でも独自のアプローチを採用しているプロトコルです。
NTMAXでは、わずか5バイトのオンチェーン情報を活用し計算の有効性を用いる事で、従来のブロックチェーンシステムに見られる計算やストレージにおける典型的な諸コストを大幅に削減しようというアプローチを取っています。このアプローチは、スケーラビリティを向上させるだけでなく、ネットワーク全体のセンサーシップレジスタンスを一層強固なものにするとされています。
INTMAXの概要の解説については以下の記事が非常にわかりやすかったのでおすすめです!!
真に億人レベルが使用されるアーキテクチャを目指して ステートレスなロールアップのアーキテクチャを具現化されようと奮闘されています。
あのEthereumの創業者であるヴィタリック・ブテリン氏からも注目されており、Web3業界の中でも非常に期待されています。
彼のブログの中でも INTMAX が言及されていたりします。
そのINTMAXチームが展開しているプロダクトがもう一つありそれが INTMAX Walletになります。
このINTMAX Walletを導入することで生体認証とMPC、FHEの最新の暗号技術を搭載し、今までにないシームレスなユーザー体験を提供することができます!
今回はそんな INTMAX WalletのSDKを使ってガスレスDappを実装してみるハンズオンを開催してみました!!
このブログを見れば皆さんも実装することができます!!
INTMAX Wallet SDKを使ってガスレスDappを実装してみた!
ではここからはハンズオン用のアプリの動かし方やコードの解説を行なっていきます!!
INTMAX Wallet SDK 用のサンプルアプリ起動方法
-
リポジトリをクローンしてくる。
git clone https://github.com/mashharuki/IntmaxRepo
-
事前準備
-
Scroll Sepolia の faucet を取得すること
例えば以下のサイトで取得できます。
- Covalent faucet
- ETHGlobal faucet
- Scroll が紹介している faucet 用のサイト
Scroll Sepolia の faucet が取得できればどのサイトでも良いのでそこにアクセスして Faucet を取得すること!!
-
ScrollScan の API を取得すること
デプロイしたコントラクトを Verify するのに使うので以下サイトにアクセスして API キーを作成する。
ScrollScan API Key
-
OpenZepplin Defender にログインして ScrollSepolia 上で Relayer を作成し、API キーを取得すること。
OpenZeppelin Defender Relayer
後ほど環境変数に使用するため以下の情報をコピペしておく。
-
上記で作成した Relayer のウォレットアドレスに少額の ETH (0.5 Sepolia ETH くらい)を送金する(Scroll Sepolia 上で送金してください!!)。
OpenZeppelin Defender で作成した Relayer アドレス - ScrollScan
各作成した Relayer のアドレスが表示されているはずなのでそのアドレスに入金すること
-
コントラクトのデプロイに使用するウォレットアドレスにも少額の ETH (0.5 Sepolia ETH くらい)を送金する(Scroll Sepolia 上で送金してください!!)。
-
環境変数の設定
環境変数は
backend
とfrontend
でそれぞれ設定する。-
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_KEY
とDEFENDER_API_KEY
とDEFENDER_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_KEY
とDEFENDER_SECRET_KEY
は上記で取得してきたものを貼り付ける。
-
-
-
インストール
yarn
-
スマートコントラクトのコンパイル
yarn backend compile
-
スマートコントラクトのテスト
yarn backend test
-
スマートコントラクト デプロイ
yarn backend deploy --network scrollSepolia
デプロイされたコントラクトのアドレスは、
pkgs/backend/outputs/contracts-scrollSepolia.json
に記載されます。もしデプロイがうまくいかないという場合には以下のデプロイ済みコントラクトのアドレスを使ってみてください!
デプロイ済みコントラクト(ScrollSepolia)
-
フロントエンドの定数ファイルの値を更新する。
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
にまとめてあります!!
このファイルには次の機能を実装しています。
- SDK 用のインスタンスを生成するメソッド
- connect するメソッド
- トランザクションを送信するメソッド
- ガスレスでトランザクションを送信するメソッド
順番に説明していきます
- の部分では
intmax-walletsdk/dapp
のethereumProvider
とintmaxDappClient
を使って実装しています!!
/**
* 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.で作成したインスタンスの機能を使って connect しています。
※ 今回は同時にeth_sign
API も呼び出して署名も実施するようにしています!
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.で作成したインスタンスの機能を使ってトランザクションを送信することになります。
/**
* トランザクションを送信するメソッド
*/
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 ロールアップのことも学べそうな参考リンクも貼り付けています。
- Scaling Ethereum 2023
- GitHub - webmax.js Public
- Intmax Wallet
- IntMax の公式サイト
- GetStarted
- Scroll bridge
- CLI のガイドライン
- intmax rolluo cli
- hardhat-Plugin
- Sample-Auction-dapp
- PRTIMES - INTMAX Walletless Wallet
- INTMAX Wallet Home Page
- GitHub - intmax-walletsdk
- npm - INTMAX WalletSDK
- INTMAX WalletSDK サンプル実装
- INTMAX Wallet SDK - GitBook
- レイヤー 2「INTMAX」とは?真の金融インフラを開発する日置玲於奈氏の展望に迫る
- INTMAX、「Plasma Next」メイネット α をローンチ。Plasma の完成により拡張性向上
- 大衆向けイーサリアムのスケーリング: INTMAX が Plasma Next を発表
- INTMAX ホワイトペーパー
- Plasma Next: Plasma without Online Requirements
- Youtube - INTMAX のステートレスなロールアップが他の ZK ロールアップと何が違うのが概要だけ解説してくれている動画
- 【完全保存版】zkEVM とは何か
- 初心者向け: #zkEVM とは?
- いま話題の「zkEVM」とは何か?~農業への応用を考察~
- イーサリアム開発者ドキュメント - プラズマチェーンとは何か?
- レイヤー 2 技術の Plasma は、提案から 2 年を経て実用段階に近づく= BlockChainJam 2019
- ヴィタリック、スケーリングソリューション「Plasma」の評価を再検討すべきと主張
- GitHub - Plasma ホワイトペーパー日本語訳
- Medium - Recursive Zero-Knowledge Proofs
- Intmax Wallet SDK のサンプル実装例を取り上げたブログ記事
- INTMAX Wallet SDK Sampple - GitHub - Sports-Voting-Demo
- PLASMACON - 公式サイト
- INTMAX、ラテンアメリカの著名な Web3 ビルダーを発表。Plasma Free、革新的な EVM 互換プロトコルの開発を主導
- INTMAX DeveloperHub
- Plasma Free についての X の投稿
- Exit games for EVM validiums: the return of Plasma
- INTMAX Wallet ブラウザアプリ
Discussion