見出し画像

【完全保存版】Autonomous WorldのMUDについてしっかり学ぼう!


0 注意!(はじめる前に)

実際に実行すると、私の環境では、こちらの「.foundry」フォルダにJSONファイルがどんどん溜まっていきました。

PCの容量がどんどん減っていきますので、不要なものは削除したり、必要時以外はシステムを止めるのが良いと思います。

1 事前準備

こちらの公式に沿って進めていきます。

下の4つを準備してください。

ちなみに、「pnpm」「performant npm」の略のようです。

その名の通り、パフォーマンスの最適化を図っているようです。

こちらは参考に、chatGPTからです。

chatGPT-4

ちなみに、「pnpm」のコマンドがうまくいかない場合、第6章も参考にして見てください。

2 実際にやってみよう

では、実際にやってみましょう。

1 プロジェクトの作成

まずは、下のコマンドを実行し、「vanilla」を選択します。

pnpm create mud@next my-project

ちなみに、依存関係として、「@latticexyz/cli」を入れています。

この「Autonomous World」を作っている会社です。

https://lattice.xyz/

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について

次に、RxJSobservableの部分を見てみましょう。

https://ugo.tokyo/rxjs-observable/

ご不明の方は、この辺りの記事が参考になると思います。

「components.Counter.update$」の部分がObservableになります。

「subscribe」関数で購読を開始しています。

データが流れてくると、中のコールバック関数が実行され、表示が変わります。

なお、「subscribe」関数は登録を行なっているので、1回のみ行われます。

(その後、データが流れてくると、コールバック関数は実行されます。)

1ー3 windowオブジェクトへの関数の追加

下の部分では、「window」オブジェクト「increment」関数を追加しています。

これにより、consoleから直接実行ができるようになります。

2 setup.tsについて

では、「index.ts」で行われた、「setup」関数を見てみましょう。

下のように構成されています。

① setupNetwork関数
② createClientComponents関数
③ createSystemCalls関数


3 getNetworkConfig.tsについて(ネットワーク構成の取得)

次に、「getNetworkConfig.ts」「getNetworkConfig」関数を見てみましょう。

その名の通り、Networkの構成を取得すると考えられます。

なお、結論としましては、下のような情報を取得します。

3ー1 paramsの取得について

まずは、下の部分で、「クエリストリング」キーと値のペアのオブジェクトを取得しました。

実際に見た方がイメージが湧くかもしれません。

下のような感じです。

chatGPT

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 のインスタンスを作成しています。

SubjectObservableObserverの両方の特徴を持ちます。

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」関数を見てみましょう。

まずは、引数として、次を取得しています。

・「setupNetwork」関数の戻り値の「worldContract」「waitForTransaction」関数
「createClientComponent」関数の返り値の「Counter」

そして、こちらの「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

下のように、バージョンが表示されましたら、成功です。

サポートをしていただけたらすごく嬉しいです😄 いただけたサポートを励みに、これからもコツコツ頑張っていきます😊