【完全保存版】Autonomous WorldのMUDについてしっかり学ぼう!
0 注意!(はじめる前に)
実際に実行すると、私の環境では、こちらの「.foundry」フォルダにJSONファイルがどんどん溜まっていきました。
PCの容量がどんどん減っていきますので、不要なものは削除したり、必要時以外はシステムを止めるのが良いと思います。
1 事前準備
こちらの公式に沿って進めていきます。
下の4つを準備してください。
ちなみに、「pnpm」は「performant npm」の略のようです。
その名の通り、パフォーマンスの最適化を図っているようです。
こちらは参考に、chatGPTからです。
ちなみに、「pnpm」のコマンドがうまくいかない場合、第6章も参考にして見てください。
2 実際にやってみよう
では、実際にやってみましょう。
1 プロジェクトの作成
まずは、下のコマンドを実行し、「vanilla」を選択します。
pnpm create mud@next my-project
ちなみに、依存関係として、「@latticexyz/cli」を入れています。
この「Autonomous World」を作っている会社です。
2 プロジェクトの実行
では、次のコマンドで、実行してみましょう。
cd my-project
pnpm run dev
3 mud.config.tsからの生成
まずは、下のように、「mud.config.ts」というファイルによって、二つのsolidityファイルが作られています。
ちなみに、2つの関係性はこのようになっています。
今回は、1ずつ増えるだけの「Counter」を保持しています。
4 コントラクトフォルダからの生成
そして、コントラクトフォルダを元に、インターフェースが作られます。
具体的な仕組みの部分が「IIncrementSystem」になります。
5 Systemコントラクトのデプロイ
次に、「IncrementSystem」というコントラクトができました。
6 モジュールコントラクトのデプロイ
次に、下の4つのモジュールがデプロイされました。
7 Worldコントラクトのデプロイ
そして、最後に「World」がデプロイされました。
8 Coreモジュールのインポート
次に、こちらの「Core」モジュールをインポートしています。
9 テーブルの登録
次に、「Counter」というテーブルが登録されました。
10 システムの登録
その後、システムが登録されています。
11 関数の登録
その上で、関数が登録されたようです。
最後に、結果が「Worlds.json」に格納されています。
12 動作を確認してみよう
では、実際に確認してみましょう。
http://localhost:3000/
に行き、「Increment」を選択すると、イベントが発生し、その結果がフロントにも反映されているようです。
下の場合、「ブロック番号」が7477で発火しているようです。
では、コードを見てみましょう。
3 全体構成について
まずは、全体像として、「packages」に「client」と「contracts」があります。
1 contractsについて
まずは、「contracts」については、主に下のようになります。
登場人物はこのようなイメージです。
2 clientについて
一方、「client」は下のようになります。
後ほど、一つずつ見ていこうと思います。
4 contractsについて
では、contractsについて細かく見てみましょう。
1 mud.config.tsについて
では、「mud.config.ts」を見てみましょう。
こちらは、使用するテーブルなどを指定します。
例えば、下では、「Counter」というテーブルを作っています。
今回は、すでに作られていますが、「contracts」フォルダ内で、下のコマンドを実行することで、ファイルが作成されます。
pnpm mud tablegen
2 Counter.solについて
コードの中身は下のようになっています。
下はゲッター系と登録を行う関数が並んでいます。
また、最後まで見てみますと、セッター系やレコードの削除を行う関数もあります。
このように、テーブルの構成が決まれば、あとは自動でsolidityのファイルを作成しています。
つまり、下のようなことが行われています。
3 IncrementSystem.solについて
次に、「systems」内の「IncrementSystem」コントラクトを見てみましょう。
下のように、ロジックと値が分離しています。
このコントラクトは、あくまでもロジックの部分のみで、値は「Counter」の「get」と「set」を使っています。
4 PostDeploy.s.solについて
次に、「PostDeploy.s.sol」を見てみましょう。
ここはその名の通り、デプロイ後に、特定の処理を行うコントラクトです。
ただ、個人的に気になったのはこちらです。
処理は、「Iworld」から行われています。
他の箇所でも書かれていたのですが、このように、処理は「World」を起点にして行われているようです。
5 Tables.solについて
ちなみに、「Counter.sol」では、下のように、IDも取得しています。
この、「Counter」ライブラリと「ID(CounterTableId)」を「Table.sol」はインポートしています。
下の部分です。
5 clientについて
では、次に、「client」について見てみましょう。
1 index.tsについて
1ー1 setup関数について
まずは、index.tsを見てみましょう。
トップレベルの非同期処理として、setup関数が実行されています。
そして、「components」「increment」「network」に分割代入しています。
1ー2 observableについて
次に、RxJSのobservableの部分を見てみましょう。
ご不明の方は、この辺りの記事が参考になると思います。
「components.Counter.update$」の部分がObservableになります。
「subscribe」関数で購読を開始しています。
データが流れてくると、中のコールバック関数が実行され、表示が変わります。
なお、「subscribe」関数は登録を行なっているので、1回のみ行われます。
(その後、データが流れてくると、コールバック関数は実行されます。)
1ー3 windowオブジェクトへの関数の追加
下の部分では、「window」オブジェクトに「increment」関数を追加しています。
これにより、consoleから直接実行ができるようになります。
2 setup.tsについて
では、「index.ts」で行われた、「setup」関数を見てみましょう。
下のように構成されています。
3 getNetworkConfig.tsについて(ネットワーク構成の取得)
次に、「getNetworkConfig.ts」の「getNetworkConfig」関数を見てみましょう。
その名の通り、Networkの構成を取得すると考えられます。
なお、結論としましては、下のような情報を取得します。
3ー1 paramsの取得について
まずは、下の部分で、「クエリストリング」のキーと値のペアのオブジェクトを取得しました。
実際に見た方がイメージが湧くかもしれません。
下のような感じです。
3ー2 チェーン情報の取得について
次に、上で取得したparamsを元に、チェーンの情報を取得しています。
3ー3 Worldアドレスの取得について
また、下の部分では、「World」のコントラクトアドレスを取得しています。
3ー4 初期ブロック番号の取得について
さらに、下の部分で最初のブロック番号についても設定しています。
3ー5 秘密鍵の取得について
なお、下の部分を見ると、秘密鍵は、一時的に実行しているアカウントのプライベートキーを取得しているようです。
3ー6 一時アカウントの秘密鍵の取得関数について
少しわきにそれますが、「getBurnerPrivateKey」関数を見てみましょう。
これは、一時的に作成したアカウントの秘密鍵を取得するものです。
まずは「localStorage」になないかを確認します。
もしあるようなら、正しいものかを確認し、正しければ、その秘密鍵を返しています。
3ー7 秘密鍵生成関数について
ないようなら、「generatePrivateKey」関数を使って、秘密鍵を作成し、「localStorage」に保管しています。
ちなみに、「generatePrivateKey」関数は下のように、「secp256k1」の「randomPrivateKey」関数を使っているようです。
3ー8 faucetについて
また、「faucetServiceUrl」では該当するものがあれば、「faucetUrl」を指定しているようです。
4 Worldについて
4ー1 Worldについて
次に、「world」を確認しましょう。
このように、createWorld関数の結果が格納されています。
4ー2 createWorld関数について
下のようになっています。
まとめると、このようになっていました。
5 setupNetworkについて
次は、「setupNetwork」関数を見てみましょう。
5ー1 networkConfigについて
まずは、「networkConfig」を取得しています。
ちなみに、network configは次のようになっていました。
5ー2 戻り値について
「setupNetwork」関数の戻り値は、こちらを返しています。
つまり、このようになります。
5ー3 clientOptinosについて
まずは、「clientOptinos」が次のように設定されています。
下のようになりました。
つまり、どのチェーンで、どのようにして、どのくらいの間隔で、ブロックチェーンとやりとりするのかを選択しています。
5ー4 publicClientについて
そして、この「clientOptions」を元にして、「publicClient」が作成されます。
下のようになりました。
5ー5 burnerWalletClientについて
次は、下の部分の「burnerWalletClient」を見てみましょう。
このようにして、「client Options」を元にして、「burnerWalletClient」を作成しています。
5ー6 write$について
なお、write$では、Subject のインスタンスを作成しています。
SubjectはObservableとObserverの両方の特徴を持ちます。
Subjectについてはこの辺りの記事がわかりやすいです。
これで、データストリームの変更が監視できます。
5ー7 worldContractについて
この部分で、「worldContract」を作成しています。
状況としては、このようになっています。
重要なのはこの部分かと思います。
ここで、受け取ったwriteデータをwrite$に送信しています。
これにより、このSubjectを購読しているすべてのObserverにデータが配信されます。
5ー8 createStoreSync関数について
続いて、「createStoreSync」関数を確認してみましょう。
その名の通り、ここでブロックチェーンとStoreとの同期を行うための情報(主に、「blockStorateOperations$」) が作成されています。
構成としては、下のようになっています。
「開始ブロック」や「最大ブロック範囲」で同期するブロックを指定しています。
その他、「onProgress」で進行状況を報告し、「storageAdapter」はデータの読み書きのインターフェースとして利用されています。
これを元にして、最新ブロックやオペレーション(blockStorageOperations$)などを返しています。
5ー9 blockStorageOperations$について
また、こちらの「blockStorageOperations$」の部分を確認します。
初期のストレージ操作(initialStorageOperations$)とその後のブロックログ(blockLogs$)に基づいてストレージ操作を生成するストリームのようです。
5ー10 syncToRecs関数について
では、これを踏まえて、「syncToRecs」関数を見てみましょう。
大まかに、下のようになっています。
下のように、途中で、「createStorageSync」を呼び出しています。
つまり、このようになっています。
そして、下の部分で、Observableからのデータやイベントを受け取るための購読を開始しています。
では、「setupNetwork」関数に戻ります。
下のように、「syncToRecs」関数が実行されていることがわかります。
つまり、こんな感じになりました。
5ー11 戻り値について
そして、「setupNetwork関数」の戻り値は下のようになっています。
それを含めると、次のようになりました。
6 createClientComponent.tsについて
引数として、「setupNetwork」関数の結果として取得した「components」を使用しています。
そして、下のように、「components」をそのまま返しています。
コメントにあるように、「client components」の追加や上書きも想定しているようです。
7 createSystemCalls.tsについて
最後に、「createSystemCalls」関数を見てみましょう。
まずは、引数として、次を取得しています。
そして、こちらの「increment」関数を返しています。
下のように、「increment」は「worldContract」を通じて行われていることが確認できます。
これで以上です。
ただ、まだまだ足りないと感じているため、こちらのノートは都度追加を行なっていきたいと思います。
6 追記 pnpmのコマンドが通らない時
pnpmのコマンドがうまく通らないとの連絡をいただきました。
パスが問題なのではと思いました。
下の部分ができているか、確認して見てください。
1 pnpmの保存場所の確認
まずは、次のコマンドで、pnpmがどこにあるのかを確認しましょう。
which pnpm
私の場合は、「/Users/ytakahashi/.npm-global/bin」の中にありました。
2 パスを確認しよう
次のコマンドで、パスを確認してみましょう。
echo $PATH
「:」は区切り文字です。
私の場合は、このように先頭にありました。
3 PATHを更新する場合
永続的に、PATHを更新する場合は、下のコマンドを行います。
echo 'export PATH=$PATH:<PATHの情報をここに>' >> ~/.zshrc
なお、bashの場合は、「.zshrc」の部分を「/.bashrc」にします。
なお、「bash」か「zsh」かは、次のコマンドで確認ができます。
echo $0
私のPCは「zsh」です。
ちなみに、先ほどのこちらのコマンド、最初はとっつきにくいと思います。
ただ、内容としては、$PATHに新しいパスを追記して、環境変数としてexportしているだけです。
最後に、下のコマンドで、現在のシェルセッションに反映させます。
source ~/.zshrc
シェルセッションとは、現在開いているターミナル(コマンドプロンプト)の一連の操作のことです。
これは、この窓を閉じると、このシェルセッションは閉じられます。
4 うまくいったかを確認しよう
最後に、パスが通っているかを確認してみましょう。
pnpm --version
下のように、バージョンが表示されましたら、成功です。
サポートをしていただけたらすごく嬉しいです😄 いただけたサポートを励みに、これからもコツコツ頑張っていきます😊