diff --git a/templates/phaser/packages/client/src/mud/createClientComponents.ts b/templates/phaser/packages/client/src/mud/createClientComponents.ts index 5058cc380b..c46d8201d2 100644 --- a/templates/phaser/packages/client/src/mud/createClientComponents.ts +++ b/templates/phaser/packages/client/src/mud/createClientComponents.ts @@ -1,3 +1,14 @@ +/* + * Creates components for use by the client. + * + * By default it returns the components from setupNetwork.ts, those which are + * automatically inferred from the mud.config.ts table definitions. + * + * However, you can add or override components here as needed. This + * lets you add user defined components, which may or may not have + * an onchain component. + */ + import { SetupNetworkResult } from "./setupNetwork"; export type ClientComponents = ReturnType; diff --git a/templates/phaser/packages/client/src/mud/createSystemCalls.ts b/templates/phaser/packages/client/src/mud/createSystemCalls.ts index 306d43cb73..9a1c478041 100644 --- a/templates/phaser/packages/client/src/mud/createSystemCalls.ts +++ b/templates/phaser/packages/client/src/mud/createSystemCalls.ts @@ -1,3 +1,14 @@ +/* + * Creates components for use by the client. + * + * By default it returns the components from setupNetwork.ts, those which are + * automatically inferred from the mud.config.ts table definitions. + * + * However, you can add or override components here as needed. This + * lets you add user defined components, which may or may not have + * an onchain component. + */ + import { getComponentValue } from "@latticexyz/recs"; import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; @@ -6,10 +17,33 @@ import { singletonEntity } from "@latticexyz/store-sync/recs"; export type SystemCalls = ReturnType; export function createSystemCalls( + /* + * The parameter list informs TypeScript that: + * + * - The first parameter is expected to be a + * SetupNetworkResult, as defined in setupNetwork.ts + * + * - Out of this parameter, we only care about two fields: + * - worldContract (which comes from createContract, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/phaser/packages/client/src/mud/setupNetwork.ts#L31). + * - waitForTransaction (which comes from syncToRecs, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/phaser/packages/client/src/mud/setupNetwork.ts#L39). + * + * - From the second parameter, which is a ClientComponent, + * we only care about Counter. This parameter comes to use + * through createClientComponents.ts, but it originates in + * syncToRecs (https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/phaser/packages/client/src/mud/setupNetwork.ts#L39). + */ { worldContract, waitForTransaction }: SetupNetworkResult, { Counter }: ClientComponents ) { const increment = async () => { + /* + * Because IncrementSystem + * (https://mud.dev/tutorials/walkthrough/minimal-onchain#incrementsystemsol) + * is in the root namespace, `.increment` can be called directly + * on the World contract. + */ const tx = await worldContract.write.increment(); await waitForTransaction(tx); return getComponentValue(Counter, singletonEntity); diff --git a/templates/phaser/packages/client/src/mud/getNetworkConfig.ts b/templates/phaser/packages/client/src/mud/getNetworkConfig.ts index 47d5880408..90d5ff42f5 100644 --- a/templates/phaser/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/phaser/packages/client/src/mud/getNetworkConfig.ts @@ -1,22 +1,80 @@ +/* + * Network specific configuration for the client. + * By default connect to the anvil test network. + */ + +/* + * By default the template just creates a temporary wallet + * (called a burner wallet) and uses a faucet (on our test net) + * to get ETH for it. + * + * See https://mud.dev/tutorials/minimal/deploy#wallet-managed-address + * for how to use the user's own address instead. + */ import { getBurnerPrivateKey } from "@latticexyz/common"; + +/* + * Import the addresses of the World, possibly on multiple chains, + * from packages/contracts/worlds.json. When the contracts package + * deploys a new `World`, it updates this file. + */ import worlds from "contracts/worlds.json"; + +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ import { supportedChains } from "./supportedChains"; export async function getNetworkConfig() { const params = new URLSearchParams(window.location.search); + + /* + * The chain ID is the first item available from this list: + * 1. chainId query parameter + * 2. chainid query parameter + * 3. The VITE_CHAIN_ID environment variable set when the + * vite dev server was started or client was built + * 4. The default, 31337 (anvil) + */ const chainId = Number(params.get("chainId") || params.get("chainid") || import.meta.env.VITE_CHAIN_ID || 31337); + + /* + * Find the chain (unless it isn't in the list of supported chains). + */ const chainIndex = supportedChains.findIndex((c) => c.id === chainId); const chain = supportedChains[chainIndex]; if (!chain) { throw new Error(`Chain ${chainId} not found`); } + /* + * Get the address of the World. If you want to use a + * different address than the one in worlds.json, + * provide it as worldAddress in the query string. + */ const world = worlds[chain.id.toString()]; const worldAddress = params.get("worldAddress") || world?.address; if (!worldAddress) { throw new Error(`No world address found for chain ${chainId}. Did you run \`mud deploy\`?`); } + /* + * MUD clients use events to synchronize the database, meaning + * they need to look as far back as when the World was started. + * The block number for the World start can be specified either + * on the URL (as initialBlockNumber) or in the worlds.json + * file. If neither has it, it starts at the first block, zero. + */ const initialBlockNumber = params.has("initialBlockNumber") ? Number(params.get("initialBlockNumber")) : world?.blockNumber ?? 0n; diff --git a/templates/phaser/packages/client/src/mud/setupNetwork.ts b/templates/phaser/packages/client/src/mud/setupNetwork.ts index 548bef1ab3..d84f17cc41 100644 --- a/templates/phaser/packages/client/src/mud/setupNetwork.ts +++ b/templates/phaser/packages/client/src/mud/setupNetwork.ts @@ -1,3 +1,8 @@ +/* + * The MUD client code is built on top of viem + * (https://viem.sh/docs/getting-started.html). + * This line imports the functions we need from it. + */ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; @@ -6,6 +11,15 @@ import { world } from "./world"; import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json"; import { createBurnerAccount, createContract, transportObserver, ContractWrite } from "@latticexyz/common"; import { Subject, share } from "rxjs"; + +/* + * Import our MUD config, which includes strong types for + * our tables and other config options. We use this to generate + * things like RECS components and get back strong types for them. + * + * See https://mud.dev/tutorials/walkthrough/minimal-onchain#mudconfigts + * for the source of this information. + */ import mudConfig from "contracts/mud.config"; export type SetupNetworkResult = Awaited>; @@ -13,6 +27,10 @@ export type SetupNetworkResult = Awaited>; export async function setupNetwork() { const networkConfig = await getNetworkConfig(); + /* + * Create a viem public (read only) client + * (https://viem.sh/docs/clients/public.html) + */ const clientOptions = { chain: networkConfig.chain, transport: transportObserver(fallback([webSocket(), http()])), @@ -21,13 +39,25 @@ export async function setupNetwork() { const publicClient = createPublicClient(clientOptions); + /* + * Create a temporary wallet and a viem client for it + * (see https://viem.sh/docs/clients/wallet.html). + */ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); const burnerWalletClient = createWalletClient({ ...clientOptions, account: burnerAccount, }); + /* + * Create an observable for contract writes that we can + * pass into MUD dev tools for transaction observability. + */ const write$ = new Subject(); + + /* + * Create an object for communicating with the deployed World. + */ const worldContract = createContract({ address: networkConfig.worldAddress as Hex, abi: IWorldAbi, @@ -36,6 +66,12 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); + /* + * Sync on-chain state into RECS and keeps our client in sync. + * Uses the MUD indexer if available, otherwise falls back + * to the viem publicClient to make RPC calls to fetch MUD + * events from the chain. + */ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, @@ -44,7 +80,11 @@ export async function setupNetwork() { startBlock: BigInt(networkConfig.initialBlockNumber), }); - // Request drip from faucet + /* + * If there is a faucet, request (test) ETH if you have + * less than 1 ETH. Repeat every 20 seconds to ensure you don't + * run out. + */ if (networkConfig.faucetServiceUrl) { const address = burnerAccount.address; console.info("[Dev Faucet]: Player address -> ", address); diff --git a/templates/phaser/packages/client/src/mud/supportedChains.ts b/templates/phaser/packages/client/src/mud/supportedChains.ts index 4b5bc43250..abc9c557eb 100644 --- a/templates/phaser/packages/client/src/mud/supportedChains.ts +++ b/templates/phaser/packages/client/src/mud/supportedChains.ts @@ -1,4 +1,19 @@ +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + */ + import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains"; -// If you are deploying to chains other than anvil or Lattice testnet, add them here +/* + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet]; diff --git a/templates/react/packages/client/src/mud/createClientComponents.ts b/templates/react/packages/client/src/mud/createClientComponents.ts index 5058cc380b..c46d8201d2 100644 --- a/templates/react/packages/client/src/mud/createClientComponents.ts +++ b/templates/react/packages/client/src/mud/createClientComponents.ts @@ -1,3 +1,14 @@ +/* + * Creates components for use by the client. + * + * By default it returns the components from setupNetwork.ts, those which are + * automatically inferred from the mud.config.ts table definitions. + * + * However, you can add or override components here as needed. This + * lets you add user defined components, which may or may not have + * an onchain component. + */ + import { SetupNetworkResult } from "./setupNetwork"; export type ClientComponents = ReturnType; diff --git a/templates/react/packages/client/src/mud/createSystemCalls.ts b/templates/react/packages/client/src/mud/createSystemCalls.ts index 306d43cb73..9858910ed2 100644 --- a/templates/react/packages/client/src/mud/createSystemCalls.ts +++ b/templates/react/packages/client/src/mud/createSystemCalls.ts @@ -1,3 +1,8 @@ +/* + * Create the system calls that the client can use to ask + * for changes in the World state (using the System contracts). + */ + import { getComponentValue } from "@latticexyz/recs"; import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; @@ -6,10 +11,33 @@ import { singletonEntity } from "@latticexyz/store-sync/recs"; export type SystemCalls = ReturnType; export function createSystemCalls( + /* + * The parameter list informs TypeScript that: + * + * - The first parameter is expected to be a + * SetupNetworkResult, as defined in setupNetwork.ts + * + * - Out of this parameter, we only care about two fields: + * - worldContract (which comes from createContract, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/react/packages/client/src/mud/setupNetwork.ts#L31). + * - waitForTransaction (which comes from syncToRecs, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/react/packages/client/src/mud/setupNetwork.ts#L39). + * + * - From the second parameter, which is a ClientComponent, + * we only care about Counter. This parameter comes to use + * through createClientComponents.ts, but it originates in + * syncToRecs (https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/react/packages/client/src/mud/setupNetwork.ts#L39). + */ { worldContract, waitForTransaction }: SetupNetworkResult, { Counter }: ClientComponents ) { const increment = async () => { + /* + * Because IncrementSystem + * (https://mud.dev/tutorials/walkthrough/minimal-onchain#incrementsystemsol) + * is in the root namespace, `.increment` can be called directly + * on the World contract. + */ const tx = await worldContract.write.increment(); await waitForTransaction(tx); return getComponentValue(Counter, singletonEntity); diff --git a/templates/react/packages/client/src/mud/getNetworkConfig.ts b/templates/react/packages/client/src/mud/getNetworkConfig.ts index 47d5880408..61ad962f25 100644 --- a/templates/react/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/react/packages/client/src/mud/getNetworkConfig.ts @@ -1,22 +1,81 @@ +/* + * Network specific configuration for the client. + * By default connect to the anvil test network. + * + */ + +/* + * By default the template just creates a temporary wallet + * (called a burner wallet) and uses a faucet (on our test net) + * to get ETH for it. + * + * See https://mud.dev/tutorials/minimal/deploy#wallet-managed-address + * for how to use the user's own address instead. + */ import { getBurnerPrivateKey } from "@latticexyz/common"; + +/* + * Import the addresses of the World, possibly on multiple chains, + * from packages/contracts/worlds.json. When the contracts package + * deploys a new `World`, it updates this file. + */ import worlds from "contracts/worlds.json"; + +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ import { supportedChains } from "./supportedChains"; export async function getNetworkConfig() { const params = new URLSearchParams(window.location.search); + + /* + * The chain ID is the first item available from this list: + * 1. chainId query parameter + * 2. chainid query parameter + * 3. The VITE_CHAIN_ID environment variable set when the + * vite dev server was started or client was built + * 4. The default, 31337 (anvil) + */ const chainId = Number(params.get("chainId") || params.get("chainid") || import.meta.env.VITE_CHAIN_ID || 31337); + + /* + * Find the chain (unless it isn't in the list of supported chains). + */ const chainIndex = supportedChains.findIndex((c) => c.id === chainId); const chain = supportedChains[chainIndex]; if (!chain) { throw new Error(`Chain ${chainId} not found`); } + /* + * Get the address of the World. If you want to use a + * different address than the one in worlds.json, + * provide it as worldAddress in the query string. + */ const world = worlds[chain.id.toString()]; const worldAddress = params.get("worldAddress") || world?.address; if (!worldAddress) { throw new Error(`No world address found for chain ${chainId}. Did you run \`mud deploy\`?`); } + /* + * MUD clients use events to synchronize the database, meaning + * they need to look as far back as when the World was started. + * The block number for the World start can be specified either + * on the URL (as initialBlockNumber) or in the worlds.json + * file. If neither has it, it starts at the first block, zero. + */ const initialBlockNumber = params.has("initialBlockNumber") ? Number(params.get("initialBlockNumber")) : world?.blockNumber ?? 0n; diff --git a/templates/react/packages/client/src/mud/setup.ts b/templates/react/packages/client/src/mud/setup.ts index 4f79edd8f3..8f9fdbab34 100644 --- a/templates/react/packages/client/src/mud/setup.ts +++ b/templates/react/packages/client/src/mud/setup.ts @@ -1,3 +1,7 @@ +/* + * This file sets up all the definitions required for a MUD client. + */ + import { createClientComponents } from "./createClientComponents"; import { createSystemCalls } from "./createSystemCalls"; import { setupNetwork } from "./setupNetwork"; @@ -8,6 +12,7 @@ export async function setup() { const network = await setupNetwork(); const components = createClientComponents(network); const systemCalls = createSystemCalls(network, components); + return { network, components, diff --git a/templates/react/packages/client/src/mud/setupNetwork.ts b/templates/react/packages/client/src/mud/setupNetwork.ts index 548bef1ab3..ddc720a4d7 100644 --- a/templates/react/packages/client/src/mud/setupNetwork.ts +++ b/templates/react/packages/client/src/mud/setupNetwork.ts @@ -1,11 +1,27 @@ +/* + * The MUD client code is built on top of viem + * (https://viem.sh/docs/getting-started.html). + * This line imports the functions we need from it. + */ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; + import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json"; import { createBurnerAccount, createContract, transportObserver, ContractWrite } from "@latticexyz/common"; + import { Subject, share } from "rxjs"; + +/* + * Import our MUD config, which includes strong types for + * our tables and other config options. We use this to generate + * things like RECS components and get back strong types for them. + * + * See https://mud.dev/tutorials/walkthrough/minimal-onchain#mudconfigts + * for the source of this information. + */ import mudConfig from "contracts/mud.config"; export type SetupNetworkResult = Awaited>; @@ -13,6 +29,10 @@ export type SetupNetworkResult = Awaited>; export async function setupNetwork() { const networkConfig = await getNetworkConfig(); + /* + * Create a viem public (read only) client + * (https://viem.sh/docs/clients/public.html) + */ const clientOptions = { chain: networkConfig.chain, transport: transportObserver(fallback([webSocket(), http()])), @@ -21,13 +41,25 @@ export async function setupNetwork() { const publicClient = createPublicClient(clientOptions); + /* + * Create a temporary wallet and a viem client for it + * (see https://viem.sh/docs/clients/wallet.html). + */ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); const burnerWalletClient = createWalletClient({ ...clientOptions, account: burnerAccount, }); + /* + * Create an observable for contract writes that we can + * pass into MUD dev tools for transaction observability. + */ const write$ = new Subject(); + + /* + * Create an object for communicating with the deployed World. + */ const worldContract = createContract({ address: networkConfig.worldAddress as Hex, abi: IWorldAbi, @@ -36,6 +68,12 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); + /* + * Sync on-chain state into RECS and keeps our client in sync. + * Uses the MUD indexer if available, otherwise falls back + * to the viem publicClient to make RPC calls to fetch MUD + * events from the chain. + */ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, @@ -44,7 +82,11 @@ export async function setupNetwork() { startBlock: BigInt(networkConfig.initialBlockNumber), }); - // Request drip from faucet + /* + * If there is a faucet, request (test) ETH if you have + * less than 1 ETH. Repeat every 20 seconds to ensure you don't + * run out. + */ if (networkConfig.faucetServiceUrl) { const address = burnerAccount.address; console.info("[Dev Faucet]: Player address -> ", address); diff --git a/templates/react/packages/client/src/mud/supportedChains.ts b/templates/react/packages/client/src/mud/supportedChains.ts index 4b5bc43250..614412500e 100644 --- a/templates/react/packages/client/src/mud/supportedChains.ts +++ b/templates/react/packages/client/src/mud/supportedChains.ts @@ -1,4 +1,20 @@ +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + + */ + import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains"; -// If you are deploying to chains other than anvil or Lattice testnet, add them here +/* + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet]; diff --git a/templates/threejs/packages/client/src/mud/createClientComponents.ts b/templates/threejs/packages/client/src/mud/createClientComponents.ts index 5058cc380b..c46d8201d2 100644 --- a/templates/threejs/packages/client/src/mud/createClientComponents.ts +++ b/templates/threejs/packages/client/src/mud/createClientComponents.ts @@ -1,3 +1,14 @@ +/* + * Creates components for use by the client. + * + * By default it returns the components from setupNetwork.ts, those which are + * automatically inferred from the mud.config.ts table definitions. + * + * However, you can add or override components here as needed. This + * lets you add user defined components, which may or may not have + * an onchain component. + */ + import { SetupNetworkResult } from "./setupNetwork"; export type ClientComponents = ReturnType; diff --git a/templates/threejs/packages/client/src/mud/createSystemCalls.ts b/templates/threejs/packages/client/src/mud/createSystemCalls.ts index fa0e994860..b87d86253d 100644 --- a/templates/threejs/packages/client/src/mud/createSystemCalls.ts +++ b/templates/threejs/packages/client/src/mud/createSystemCalls.ts @@ -1,3 +1,8 @@ +/* + * Create the system calls that the client can use to ask + * for changes in the World state (using the System contracts). + */ + import { getComponentValue } from "@latticexyz/recs"; import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; @@ -5,10 +10,31 @@ import { SetupNetworkResult } from "./setupNetwork"; export type SystemCalls = ReturnType; export function createSystemCalls( + /* + * The parameter list informs TypeScript that: + * + * - The first parameter is expected to be a + * SetupNetworkResult, as defined in setupNetwork.ts + * + * - Out of this parameter, we only care about two fields: + * - worldContract (which comes from createContract, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/threejs/packages/client/src/mud/setupNetwork.ts#L31). + * - waitForTransaction (which comes from syncToRecs, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/threejs/packages/client/src/mud/setupNetwork.ts#L39). + * + * - From the second parameter, which is a ClientComponent, + * we only care about Position. This parameter comes to use + * through createClientComponents.ts, but it originates in + * syncToRecs (https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/threejs/packages/client/src/mud/setupNetwork.ts#L39). + */ { worldContract, waitForTransaction, playerEntity }: SetupNetworkResult, { Position }: ClientComponents ) { const moveTo = async (x: number, y: number, z: number) => { + /* + * Because MoveSystem is in the root namespace, .move can be called directly + * on the World contract. + */ const tx = await worldContract.write.move([x, y, z]); await waitForTransaction(tx); }; diff --git a/templates/threejs/packages/client/src/mud/getNetworkConfig.ts b/templates/threejs/packages/client/src/mud/getNetworkConfig.ts index 47d5880408..e0bebfe426 100644 --- a/templates/threejs/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/threejs/packages/client/src/mud/getNetworkConfig.ts @@ -1,22 +1,84 @@ +/* + * Network specific configuration for the client. + * By default connect to the anvil test network. + * + */ + +/* + * By default the template just creates a temporary wallet + * (called a burner wallet) and uses a faucet (on our test net) + * to get ETH for it. + * + * See https://mud.dev/tutorials/minimal/deploy#wallet-managed-address + * for how to use the user's own address instead. + */ import { getBurnerPrivateKey } from "@latticexyz/common"; + +/* + * Import the addresses of the World, possibly on multiple chains, + * from packages/contracts/worlds.json. When the contracts package + * deploys a new `World`, it updates this file. + */ import worlds from "contracts/worlds.json"; + +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ import { supportedChains } from "./supportedChains"; +/* + * This is the function that does the actual work. + */ export async function getNetworkConfig() { const params = new URLSearchParams(window.location.search); + + /* + * The chain ID is the first item available from this list: + * 1. chainId query parameter + * 2. chainid query parameter + * 3. The VITE_CHAIN_ID environment variable set when the + * vite dev server was started or client was built + * 4. The default, 31337 (anvil) + */ const chainId = Number(params.get("chainId") || params.get("chainid") || import.meta.env.VITE_CHAIN_ID || 31337); + + /* + * Find the chain (unless it isn't in the list of supported chains). + */ const chainIndex = supportedChains.findIndex((c) => c.id === chainId); const chain = supportedChains[chainIndex]; if (!chain) { throw new Error(`Chain ${chainId} not found`); } + /* + * Get the address of the World. If you want to use a + * different address than the one in worlds.json, + * provide it as worldAddress in the query string. + */ const world = worlds[chain.id.toString()]; const worldAddress = params.get("worldAddress") || world?.address; if (!worldAddress) { throw new Error(`No world address found for chain ${chainId}. Did you run \`mud deploy\`?`); } + /* + * MUD clients use events to synchronize the database, meaning + * they need to look as far back as when the World was started. + * The block number for the World start can be specified either + * on the URL (as initialBlockNumber) or in the worlds.json + * file. If neither has it, it starts at the first block, zero. + */ const initialBlockNumber = params.has("initialBlockNumber") ? Number(params.get("initialBlockNumber")) : world?.blockNumber ?? 0n; diff --git a/templates/threejs/packages/client/src/mud/setup.ts b/templates/threejs/packages/client/src/mud/setup.ts index 4f79edd8f3..9e8e7c8c42 100644 --- a/templates/threejs/packages/client/src/mud/setup.ts +++ b/templates/threejs/packages/client/src/mud/setup.ts @@ -1,3 +1,6 @@ +/* + * This file sets up all the definitions required for a MUD client. + */ import { createClientComponents } from "./createClientComponents"; import { createSystemCalls } from "./createSystemCalls"; import { setupNetwork } from "./setupNetwork"; diff --git a/templates/threejs/packages/client/src/mud/setupNetwork.ts b/templates/threejs/packages/client/src/mud/setupNetwork.ts index 548bef1ab3..a72db6288f 100644 --- a/templates/threejs/packages/client/src/mud/setupNetwork.ts +++ b/templates/threejs/packages/client/src/mud/setupNetwork.ts @@ -1,3 +1,8 @@ +/* + * The MUD client code is built on top of viem + * (https://viem.sh/docs/getting-started.html). + * This line imports the functions we need from it. + */ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; @@ -6,6 +11,15 @@ import { world } from "./world"; import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json"; import { createBurnerAccount, createContract, transportObserver, ContractWrite } from "@latticexyz/common"; import { Subject, share } from "rxjs"; + +/* + * Import our MUD config, which includes strong types for + * our tables and other config options. We use this to generate + * things like RECS components and get back strong types for them. + * + * See https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/threejs/packages/contracts/mud.config.ts + * for the source of this information. + */ import mudConfig from "contracts/mud.config"; export type SetupNetworkResult = Awaited>; @@ -13,6 +27,10 @@ export type SetupNetworkResult = Awaited>; export async function setupNetwork() { const networkConfig = await getNetworkConfig(); + /* + * Create a viem public (read only) client + * (https://viem.sh/docs/clients/public.html) + */ const clientOptions = { chain: networkConfig.chain, transport: transportObserver(fallback([webSocket(), http()])), @@ -21,13 +39,25 @@ export async function setupNetwork() { const publicClient = createPublicClient(clientOptions); + /* + * Create a temporary wallet and a viem client for it + * (see https://viem.sh/docs/clients/wallet.html). + */ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); const burnerWalletClient = createWalletClient({ ...clientOptions, account: burnerAccount, }); + /* + * Create an observable for contract writes that we can + * pass into MUD dev tools for transaction observability. + */ const write$ = new Subject(); + + /* + * Create an object for communicating with the deployed World. + */ const worldContract = createContract({ address: networkConfig.worldAddress as Hex, abi: IWorldAbi, @@ -36,6 +66,12 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); + /* + * Sync on-chain state into RECS and keeps our client in sync. + * Uses the MUD indexer if available, otherwise falls back + * to the viem publicClient to make RPC calls to fetch MUD + * events from the chain. + */ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, @@ -44,7 +80,11 @@ export async function setupNetwork() { startBlock: BigInt(networkConfig.initialBlockNumber), }); - // Request drip from faucet + /* + * If there is a faucet, request (test) ETH if you have + * less than 1 ETH. Repeat every 20 seconds to ensure you don't + * run out. + */ if (networkConfig.faucetServiceUrl) { const address = burnerAccount.address; console.info("[Dev Faucet]: Player address -> ", address); diff --git a/templates/threejs/packages/client/src/mud/supportedChains.ts b/templates/threejs/packages/client/src/mud/supportedChains.ts index 4b5bc43250..499c8bb3a6 100644 --- a/templates/threejs/packages/client/src/mud/supportedChains.ts +++ b/templates/threejs/packages/client/src/mud/supportedChains.ts @@ -1,4 +1,18 @@ +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + */ import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains"; -// If you are deploying to chains other than anvil or Lattice testnet, add them here +/* + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet]; diff --git a/templates/vanilla/packages/client/src/mud/createClientComponents.ts b/templates/vanilla/packages/client/src/mud/createClientComponents.ts index 5058cc380b..c46d8201d2 100644 --- a/templates/vanilla/packages/client/src/mud/createClientComponents.ts +++ b/templates/vanilla/packages/client/src/mud/createClientComponents.ts @@ -1,3 +1,14 @@ +/* + * Creates components for use by the client. + * + * By default it returns the components from setupNetwork.ts, those which are + * automatically inferred from the mud.config.ts table definitions. + * + * However, you can add or override components here as needed. This + * lets you add user defined components, which may or may not have + * an onchain component. + */ + import { SetupNetworkResult } from "./setupNetwork"; export type ClientComponents = ReturnType; diff --git a/templates/vanilla/packages/client/src/mud/createSystemCalls.ts b/templates/vanilla/packages/client/src/mud/createSystemCalls.ts index 306d43cb73..9a364b4920 100644 --- a/templates/vanilla/packages/client/src/mud/createSystemCalls.ts +++ b/templates/vanilla/packages/client/src/mud/createSystemCalls.ts @@ -1,3 +1,8 @@ +/* + * Create the system calls that the client can use to ask + * for changes in the World state (using the System contracts). + */ + import { getComponentValue } from "@latticexyz/recs"; import { ClientComponents } from "./createClientComponents"; import { SetupNetworkResult } from "./setupNetwork"; @@ -6,10 +11,34 @@ import { singletonEntity } from "@latticexyz/store-sync/recs"; export type SystemCalls = ReturnType; export function createSystemCalls( + /* + * The parameter list informs TypeScript that: + * + * - The first parameter is expected to be a + * SetupNetworkResult, as defined in setupNetwork.ts + * + * - Out of this parameter, we only care about two fields: + * - worldContract (which comes from createContract, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L31). + * - waitForTransaction (which comes from syncToRecs, see + * https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L39). + * + * - From the second parameter, which is a ClientComponent, + * we only care about Counter. This parameter comes to use + * through createClientComponents.ts, but it originates in + * syncToRecs +(https://github.com/latticexyz/mud/blob/26dabb34321eedff7a43f3fcb46da4f3f5ba3708/templates/vanilla/packages/client/src/mud/setupNetwork.ts#L39). + */ { worldContract, waitForTransaction }: SetupNetworkResult, { Counter }: ClientComponents ) { const increment = async () => { + /* + * Because IncrementSystem + * (https://mud.dev/tutorials/walkthrough/minimal-onchain#incrementsystemsol) + * is in the root namespace, `.increment` can be called directly + * on the World contract. + */ const tx = await worldContract.write.increment(); await waitForTransaction(tx); return getComponentValue(Counter, singletonEntity); diff --git a/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts b/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts index 47d5880408..d2a724be41 100644 --- a/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts @@ -1,22 +1,82 @@ +/* + * Network specific configuration for the client. + * By default connect to the anvil test network. + * + */ + +/* + * By default the template just creates a temporary wallet + * (called a burner wallet) and uses a faucet (on our test net) + * to get ETH for it. + * + * See https://mud.dev/tutorials/minimal/deploy#wallet-managed-address + * for how to use the user's own address instead. + */ import { getBurnerPrivateKey } from "@latticexyz/common"; + +/* + * Import the addresses of the World, possibly on multiple chains, + * from packages/contracts/worlds.json. When the contracts package + * deploys a new `World`, it updates this file. + */ import worlds from "contracts/worlds.json"; + +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ + import { supportedChains } from "./supportedChains"; export async function getNetworkConfig() { const params = new URLSearchParams(window.location.search); + + /* + * The chain ID is the first item available from this list: + * 1. chainId query parameter + * 2. chainid query parameter + * 3. The VITE_CHAIN_ID environment variable set when the + * vite dev server was started or client was built + * 4. The default, 31337 (anvil) + */ const chainId = Number(params.get("chainId") || params.get("chainid") || import.meta.env.VITE_CHAIN_ID || 31337); + + /* + * Find the chain (unless it isn't in the list of supported chains). + */ const chainIndex = supportedChains.findIndex((c) => c.id === chainId); const chain = supportedChains[chainIndex]; if (!chain) { throw new Error(`Chain ${chainId} not found`); } + /* + * Get the address of the World. If you want to use a + * different address than the one in worlds.json, + * provide it as worldAddress in the query string. + */ const world = worlds[chain.id.toString()]; const worldAddress = params.get("worldAddress") || world?.address; if (!worldAddress) { throw new Error(`No world address found for chain ${chainId}. Did you run \`mud deploy\`?`); } + /* + * MUD clients use events to synchronize the database, meaning + * they need to look as far back as when the World was started. + * The block number for the World start can be specified either + * on the URL (as initialBlockNumber) or in the worlds.json + * file. If neither has it, it starts at the first block, zero. + */ const initialBlockNumber = params.has("initialBlockNumber") ? Number(params.get("initialBlockNumber")) : world?.blockNumber ?? 0n; diff --git a/templates/vanilla/packages/client/src/mud/setup.ts b/templates/vanilla/packages/client/src/mud/setup.ts index 4f79edd8f3..8f9fdbab34 100644 --- a/templates/vanilla/packages/client/src/mud/setup.ts +++ b/templates/vanilla/packages/client/src/mud/setup.ts @@ -1,3 +1,7 @@ +/* + * This file sets up all the definitions required for a MUD client. + */ + import { createClientComponents } from "./createClientComponents"; import { createSystemCalls } from "./createSystemCalls"; import { setupNetwork } from "./setupNetwork"; @@ -8,6 +12,7 @@ export async function setup() { const network = await setupNetwork(); const components = createClientComponents(network); const systemCalls = createSystemCalls(network, components); + return { network, components, diff --git a/templates/vanilla/packages/client/src/mud/setupNetwork.ts b/templates/vanilla/packages/client/src/mud/setupNetwork.ts index 548bef1ab3..ddc720a4d7 100644 --- a/templates/vanilla/packages/client/src/mud/setupNetwork.ts +++ b/templates/vanilla/packages/client/src/mud/setupNetwork.ts @@ -1,11 +1,27 @@ +/* + * The MUD client code is built on top of viem + * (https://viem.sh/docs/getting-started.html). + * This line imports the functions we need from it. + */ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; + import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json"; import { createBurnerAccount, createContract, transportObserver, ContractWrite } from "@latticexyz/common"; + import { Subject, share } from "rxjs"; + +/* + * Import our MUD config, which includes strong types for + * our tables and other config options. We use this to generate + * things like RECS components and get back strong types for them. + * + * See https://mud.dev/tutorials/walkthrough/minimal-onchain#mudconfigts + * for the source of this information. + */ import mudConfig from "contracts/mud.config"; export type SetupNetworkResult = Awaited>; @@ -13,6 +29,10 @@ export type SetupNetworkResult = Awaited>; export async function setupNetwork() { const networkConfig = await getNetworkConfig(); + /* + * Create a viem public (read only) client + * (https://viem.sh/docs/clients/public.html) + */ const clientOptions = { chain: networkConfig.chain, transport: transportObserver(fallback([webSocket(), http()])), @@ -21,13 +41,25 @@ export async function setupNetwork() { const publicClient = createPublicClient(clientOptions); + /* + * Create a temporary wallet and a viem client for it + * (see https://viem.sh/docs/clients/wallet.html). + */ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); const burnerWalletClient = createWalletClient({ ...clientOptions, account: burnerAccount, }); + /* + * Create an observable for contract writes that we can + * pass into MUD dev tools for transaction observability. + */ const write$ = new Subject(); + + /* + * Create an object for communicating with the deployed World. + */ const worldContract = createContract({ address: networkConfig.worldAddress as Hex, abi: IWorldAbi, @@ -36,6 +68,12 @@ export async function setupNetwork() { onWrite: (write) => write$.next(write), }); + /* + * Sync on-chain state into RECS and keeps our client in sync. + * Uses the MUD indexer if available, otherwise falls back + * to the viem publicClient to make RPC calls to fetch MUD + * events from the chain. + */ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ world, config: mudConfig, @@ -44,7 +82,11 @@ export async function setupNetwork() { startBlock: BigInt(networkConfig.initialBlockNumber), }); - // Request drip from faucet + /* + * If there is a faucet, request (test) ETH if you have + * less than 1 ETH. Repeat every 20 seconds to ensure you don't + * run out. + */ if (networkConfig.faucetServiceUrl) { const address = burnerAccount.address; console.info("[Dev Faucet]: Player address -> ", address); diff --git a/templates/vanilla/packages/client/src/mud/supportedChains.ts b/templates/vanilla/packages/client/src/mud/supportedChains.ts index 4b5bc43250..abc9c557eb 100644 --- a/templates/vanilla/packages/client/src/mud/supportedChains.ts +++ b/templates/vanilla/packages/client/src/mud/supportedChains.ts @@ -1,4 +1,19 @@ +/* + * The supported chains. + * By default, there are only two chains here: + * + * - mudFoundry, the chain running on anvil that pnpm dev + * starts by default. It is similar to the viem anvil chain + * (see https://viem.sh/docs/clients/test.html), but with the + * basefee set to zero to avoid transaction fees. + * - latticeTestnet, our public test network. + * + */ + import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains"; -// If you are deploying to chains other than anvil or Lattice testnet, add them here +/* + * See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface + * for instructions on how to add networks. + */ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet];