Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
tash-2s committed Mar 11, 2024
1 parent 30d7768 commit af147bb
Show file tree
Hide file tree
Showing 31 changed files with 401 additions and 273 deletions.
19 changes: 6 additions & 13 deletions templates/react/packages/client/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
import ReactDOM from "react-dom/client";
import { App } from "./App";
import { setup } from "./mud/setup";
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NetworkProvider } from "./mud/NetworkContext";
import { StoreSync } from "./mud/StoreSync";
import { WalletAdapter } from "./mud/wallet/WalletAdapter";
import { DevTools } from "./mud/DevTools";
import { App } from "./App";
import { MUD } from "./mud/MUD";

const rootElement = document.getElementById("react-root");
if (!rootElement) throw new Error("React root not found");
const root = ReactDOM.createRoot(rootElement);

// Wagmi depends on TanStack Query.
const queryClient = new QueryClient();

// TODO: figure out if we actually want this to be async or if we should render something else in the meantime
setup().then(({ network, wagmiConfig }) => {
root.render(
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<NetworkProvider network={network}>
<StoreSync>
<WalletAdapter>
<App />
{import.meta.env.DEV && <DevTools />}
</WalletAdapter>
</StoreSync>
</NetworkProvider>
<MUD network={network}>
<App />
</MUD>
</QueryClientProvider>
</WagmiProvider>,
);
Expand Down
12 changes: 4 additions & 8 deletions templates/react/packages/client/src/mud/DevTools.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { useEffect } from "react";
import { useMUD } from "./useMUD";
import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json";
import mudConfig from "contracts/mud.config";

// Displays dev-tools for the burner wallet
// Displays dev-tools connected to the burner wallet
export function DevTools() {
const { network, burner } = useMUD();

useEffect(() => {
if (!burner) return;

// TODO: Handle unmount properly by updating the dev-tools implementation.
let unmount: (() => void) | null = null;
let unmount: (() => void) | undefined;

import("@latticexyz/dev-tools")
.then(({ mount }) =>
Expand All @@ -22,15 +20,13 @@ export function DevTools() {
latestBlock$: network.latestBlock$,
storedBlockLogs$: network.storedBlockLogs$,
worldAddress: network.worldAddress,
worldAbi: IWorldAbi,
worldAbi: burner.worldContract.abi,
write$: burner.write$,
useStore: network.useStore,
}),
)
.then((result) => {
if (result) {
unmount = result;
}
unmount = result;
});

return () => {
Expand Down
26 changes: 26 additions & 0 deletions templates/react/packages/client/src/mud/MUD.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type ReactNode } from "react";
import { type Network } from "./setupNetwork";
import { NetworkProvider } from "./NetworkContext";
import { StoreSync } from "./StoreSync";
import { WalletManager } from "./wallet/WalletManager";
import { DevTools } from "./DevTools";

type Props = {
network: Network;
children: ReactNode;
};

// A React component that encapsulates MUD-related components.
export function MUD({ network, children }: Props) {
return (
<NetworkProvider network={network}>
<StoreSync>
<WalletManager>
{children}
{/* Mounts dev-tools when in development mode. https://vitejs.dev/guide/env-and-mode.html */}
{import.meta.env.DEV && <DevTools />}
</WalletManager>
</StoreSync>
</NetworkProvider>
);
}
6 changes: 4 additions & 2 deletions templates/react/packages/client/src/mud/NetworkContext.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { createContext, useContext, type ReactNode } from "react";
import { type Network } from "./setupNetwork";

// A React context that holds the result of `setupNetwork()` (i.e., World's address, Public Client, client store).
// Once set, these values do not change.
const NetworkContext = createContext<Network | null>(null);

type Props = {
children: ReactNode;
network: Network;
children: ReactNode;
};

export function NetworkProvider({ children, network }: Props) {
export function NetworkProvider({ network, children }: Props) {
if (useContext(NetworkContext)) throw new Error("NetworkProvider can only be used once");
return <NetworkContext.Provider value={network}>{children}</NetworkContext.Provider>;
}
Expand Down
13 changes: 9 additions & 4 deletions templates/react/packages/client/src/mud/StoreSync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { type ReactNode } from "react";
import { SyncStep } from "@latticexyz/store-sync";
import { useNetwork } from "./NetworkContext";

export function StoreSync(props: { children: ReactNode }) {
type Props = {
children: ReactNode;
};

// A React component that checks the client store's synchronization status.
// It does not render its children until synchronization is complete.
// Until then, it displays a loading message.
export function StoreSync({ children }: Props) {
const { useStore } = useNetwork();

const { step, message, percentage } = useStore((state) => state.syncProgress);

if (step === SyncStep.LIVE) {
return props.children;
}
if (step === SyncStep.LIVE) return children;

return (
<div>
Expand Down
13 changes: 11 additions & 2 deletions templates/react/packages/client/src/mud/createSystemCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@

import { type Hex } from "viem";
import { type Network } from "./setupNetwork";
import { type WorldContract } from "./wallet/burner";
import { type WorldContract } from "./wallet/createBurner";

export function createSystemCalls({ tables, useStore, waitForTransaction }: Network, worldContract: WorldContract) {
export type SystemCalls = ReturnType<typeof createSystemCalls>;

export function createSystemCalls(
/*
* `tables`, `useStore`, and `waitForTransaction` are from `syncToZustand()`.
* `worldContract` is from `getContract()`.
*/
{ tables, useStore, waitForTransaction }: Network,
worldContract: WorldContract,
) {
const addTask = async (label: string) => {
const tx = await worldContract.write.addTask([label]);
await waitForTransaction(tx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { fallback, webSocket, http, type ClientConfig } from "viem";
import { transportObserver } from "@latticexyz/common";
import { type MUDChain } from "@latticexyz/common/chains";

export function createClientConfig(chain: MUDChain) {
// https://viem.sh/docs/clients/public#parameters
export function createViemClientConfig(chain: MUDChain) {
return {
chain,
transport: transportObserver(fallback([webSocket(), http()])),
Expand Down
7 changes: 5 additions & 2 deletions templates/react/packages/client/src/mud/getNetworkConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import worlds from "contracts/worlds.json";
*/
import { supportedChains } from "./supportedChains";

import { isHex } from "viem";
import { isAddress } from "viem";

export async function getNetworkConfig() {
const params = new URLSearchParams(window.location.search);
Expand Down Expand Up @@ -57,9 +57,12 @@ export async function getNetworkConfig() {
*/
const world = worlds[chain.id.toString()];
const worldAddress = params.get("worldAddress") || world?.address;
if (!isHex(worldAddress)) {
if (!worldAddress) {
throw new Error(`No world address found for chain ${chainId}. Did you run \`mud deploy\`?`);
}
if (!isAddress(worldAddress)) {
throw new Error(`The world address is not valid: ${worldAddress}`);
}

/*
* MUD clients use events to synchronize the database, meaning
Expand Down
13 changes: 12 additions & 1 deletion templates/react/packages/client/src/mud/setup.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
/*
* This file sets up all the definitions required for a MUD client.
*/

import { createConfig } from "wagmi";
import { setupNetwork } from "./setupNetwork";

export type SetupResult = Awaited<ReturnType<typeof setup>>;

export async function setup() {
const network = await setupNetwork();

// Create a Wagmi config for an external wallet connection.
// https://wagmi.sh/react/api/createConfig
const wagmiConfig = createConfig({
chains: [network.publicClient.chain],
client: () => network.publicClient,
});

return { network, wagmiConfig };
return {
network,
wagmiConfig,
};
}
11 changes: 5 additions & 6 deletions templates/react/packages/client/src/mud/setupNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import { createPublicClient } from "viem";
import { syncToZustand } from "@latticexyz/store-sync/zustand";
import { getNetworkConfig } from "./getNetworkConfig";
import { createClientConfig } from "./createClientConfig";
import { createViemClientConfig } from "./createViemClientConfig";

/*
* 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.
* our tables and other config options. We use this for `syncToZustand()`.
*
* See https://mud.dev/templates/typescript/contracts#mudconfigts
* for the source of this information.
Expand All @@ -27,10 +26,10 @@ export async function setupNetwork() {
* Create a viem public (read only) client
* (https://viem.sh/docs/clients/public.html)
*/
const publicClient = createPublicClient(createClientConfig(networkConfig.chain));
const publicClient = createPublicClient(createViemClientConfig(networkConfig.chain));

/*
* Sync on-chain state into zustand and keeps our client in sync.
* Sync on-chain state into Zustand 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.
Expand All @@ -43,8 +42,8 @@ export async function setupNetwork() {
});

return {
worldAddress: networkConfig.worldAddress,
publicClient,
worldAddress: networkConfig.worldAddress,
...syncResult,
};
}
5 changes: 3 additions & 2 deletions templates/react/packages/client/src/mud/supportedChains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import { type MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains";
import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains";

/*
* See https://mud.dev/tutorials/minimal/deploy#run-the-user-interface
Expand All @@ -20,7 +20,8 @@ import { type MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/ch
export const supportedChains: MUDChain[] = [
{
...mudFoundry,
// See https://github.com/latticexyz/mud/pull/2330
// Uses a placeholder explorer URL as a workaround for a Wagmi issue.
// Details at: https://github.com/latticexyz/mud/pull/2330
blockExplorers: { default: { name: "", url: "https://example.com" } },
},
latticeTestnet,
Expand Down
14 changes: 10 additions & 4 deletions templates/react/packages/client/src/mud/useMUD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import { useNetwork } from "./NetworkContext";
import { useBurner } from "./wallet/BurnerContext";
import { createSystemCalls } from "./createSystemCalls";

// A React hook that provides the network settings and `burner`.
//
// `burner` is available under either of two conditions:
//
// 1. An external wallet (e.g., MetaMask) is connected and has delegated authority to the burner account (i.e., a temporary account).
// 2. A burner account without an external wallet is used.
//
// `burner` includes a `systemCalls` property to facilitate calling Systems.
export function useMUD() {
const network = useNetwork();
const burner = useBurner();

if (burner) {
return { network, burner: { ...burner, systemCalls: createSystemCalls(network, burner.worldContract) } };
}
if (burner) return { network, burner: { ...burner, systemCalls: createSystemCalls(network, burner.worldContract) } };

return { network, burner };
return { network, burner: undefined };
}

This file was deleted.

13 changes: 10 additions & 3 deletions templates/react/packages/client/src/mud/wallet/BurnerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { createContext, useContext, type ReactNode } from "react";
import { type Burner } from "./burner";
import { type Burner } from "./createBurner";

// A React context that holds the result of `createBurner()` (e.g., Wallet Client, World contract).
const BurnerContext = createContext<Burner | null>(null);

export function BurnerProvider(props: { burner: Burner | null; children: ReactNode }) {
type Props = {
burner: Burner | null;
children: ReactNode;
};

export function BurnerProvider({ burner, children }: Props) {
if (useContext(BurnerContext)) throw new Error("BurnerProvider can only be used once");
return <BurnerContext.Provider value={props.burner}>{props.children}</BurnerContext.Provider>;
return <BurnerContext.Provider value={burner}>{children}</BurnerContext.Provider>;
}

export function useBurner() {
// This can be null.
return useContext(BurnerContext);
}
Loading

0 comments on commit af147bb

Please sign in to comment.