Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(create-mud): explain boilerplate in templates #1427

Merged
merged 38 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
43ee665
Finished the "Deploying to a third party blockchain"
qbzzt Aug 30, 2023
e9ba623
docs: Add how to deploy to the lattice testnet
qbzzt Aug 30, 2023
fdca1e8
Merge branch 'main' into 230825-deploy2testnet
qbzzt Aug 30, 2023
2f029ac
docs: Ready for review
qbzzt Aug 31, 2023
1ed600b
Merge branch '230825-deploy2testnet' of https://github.com/latticexyz…
qbzzt Aug 31, 2023
75e1b67
Start to turn the client code walkthrough into comments.
qbzzt Sep 7, 2023
8903020
docs: moved the walkthrough to the comments
qbzzt Sep 8, 2023
70d5c81
Apply suggestions from code review
qbzzt Sep 8, 2023
a9fd518
Update createClientComponents.ts
qbzzt Sep 8, 2023
19784ec
Update createClientComponents.ts
qbzzt Sep 8, 2023
9a05139
Added most of the @holic comments
qbzzt Sep 9, 2023
ee90f1c
Merge branch 'main' into 230907-client-boilerplate-comments
qbzzt Sep 9, 2023
7114eea
Update getNetworkConfig.ts
qbzzt Sep 9, 2023
dd50c3b
Update setupNetwork.ts
qbzzt Sep 9, 2023
4bf8598
Update getNetworkConfig.ts
qbzzt Sep 9, 2023
1b78e03
Update setup.ts
qbzzt Sep 9, 2023
3096450
Update setupNetwork.ts
qbzzt Sep 9, 2023
00a1c4b
Merge branch '230907-client-boilerplate-comments' of https://github.c…
qbzzt Sep 11, 2023
50f9909
docs: add annotated mud code to react template
qbzzt Sep 11, 2023
cfff29f
Update the comments to refer to vanilla instead of react
qbzzt Sep 11, 2023
295d5fd
add comments to threejs template
qbzzt Sep 11, 2023
77265c4
tiny change in supportedChains.ts
qbzzt Sep 11, 2023
ac4063b
update the phaser template with comments
qbzzt Sep 11, 2023
fb575a4
Merge branch 'main' into 230907-client-boilerplate-comments
qbzzt Sep 11, 2023
008ece8
prettier
qbzzt Sep 11, 2023
8a4aa33
Apply suggestions from code review
qbzzt Sep 12, 2023
2e9cd87
Merge branch 'main' into 230907-client-boilerplate-comments
qbzzt Sep 12, 2023
1be016b
Update createSystemCalls.ts
qbzzt Sep 12, 2023
3391282
Update getNetworkConfig.ts
qbzzt Sep 12, 2023
ddf4177
Apply suggestions from code review
qbzzt Sep 12, 2023
845cf91
Update setupNetwork.ts
qbzzt Sep 12, 2023
659a116
Update setupNetwork.ts
qbzzt Sep 12, 2023
7e4d65e
Update setupNetwork.ts
qbzzt Sep 12, 2023
9cb5cbb
Freeze links to github
qbzzt Sep 12, 2023
2f14622
Comment formatting
qbzzt Sep 12, 2023
4c93099
Fix more comments
qbzzt Sep 12, 2023
6078400
Final prettier commit (I hope)
qbzzt Sep 12, 2023
51243a1
Merge branch 'main' into 230907-client-boilerplate-comments
qbzzt Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<typeof createClientComponents>;
Expand Down
34 changes: 34 additions & 0 deletions templates/phaser/packages/client/src/mud/createSystemCalls.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -6,10 +17,33 @@ import { singletonEntity } from "@latticexyz/store-sync/recs";
export type SystemCalls = ReturnType<typeof createSystemCalls>;

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/main/templates/phaser/packages/client/src/mud/setupNetwork.ts#L31).
* - waitForTransaction (which comes from syncToRecs, see
* https://github.com/latticexyz/mud/blob/main/templates/phaser/packages/client/src/mud/setupNetwork.ts#L39).
qbzzt marked this conversation as resolved.
Show resolved Hide resolved
*
* - 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/main/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);
Expand Down
56 changes: 56 additions & 0 deletions templates/phaser/packages/client/src/mud/getNetworkConfig.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
/*
* 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;
Expand Down
40 changes: 39 additions & 1 deletion templates/phaser/packages/client/src/mud/setupNetwork.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -6,13 +11,26 @@ 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<ReturnType<typeof setupNetwork>>;

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()])),
Expand All @@ -21,13 +39,23 @@ export async function setupNetwork() {

const publicClient = createPublicClient(clientOptions);

/*
* Create A temporary wallet and a viem client for it
qbzzt marked this conversation as resolved.
Show resolved Hide resolved
* (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<ContractWrite>();

/* Create an object for communicating with the deployed World. */
const worldContract = createContract({
address: networkConfig.worldAddress as Hex,
abi: IWorldAbi,
Expand All @@ -36,6 +64,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,
Expand All @@ -44,7 +78,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);
Expand Down
17 changes: 16 additions & 1 deletion templates/phaser/packages/client/src/mud/supportedChains.ts
Original file line number Diff line number Diff line change
@@ -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];
Original file line number Diff line number Diff line change
@@ -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<typeof createClientComponents>;
Expand Down
28 changes: 28 additions & 0 deletions templates/react/packages/client/src/mud/createSystemCalls.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -6,10 +11,33 @@ import { singletonEntity } from "@latticexyz/store-sync/recs";
export type SystemCalls = ReturnType<typeof createSystemCalls>;

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/main/templates/react/packages/client/src/mud/setupNetwork.ts#L31).
* - waitForTransaction (which comes from syncToRecs, see
* https://github.com/latticexyz/mud/blob/main/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/main/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);
Expand Down
56 changes: 56 additions & 0 deletions templates/react/packages/client/src/mud/getNetworkConfig.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
/* 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;
Expand Down
Loading