-
Notifications
You must be signed in to change notification settings - Fork 200
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
feat(create-mud): use new sync packages #1214
Changes from all commits
6bc4dcd
960300d
9858d12
97f8a3c
8d7b706
35c175c
5eb8bf1
b29a6fc
c6761eb
fffdb10
d79c9d7
272cd6a
6cf4c1b
9f055af
07e9780
a5428f7
68f18ec
9d9e5b4
3bf4eb8
0cb6cd9
7a150f6
0c34c1e
447c805
89a711e
0db5373
6bb33d8
212c78c
a0235f1
08023c9
e46787c
917c6d9
490700f
f05eb13
18b1476
c8b7cbd
2b2a474
8f5eff7
ba6f360
f90031f
15c7ec3
8a9a760
910eb24
a1d300f
6840edb
3dceee3
eecfbad
00b9b6c
cb70bb8
d463df2
eeb0a17
de05378
39d160e
08cbfb9
2a44ceb
81cb59e
f4defdb
7c1d06d
1b1e190
05dc486
0666626
8d907e2
063ea59
2014146
9bb6c27
e89b317
891549a
8a989d1
cf7474c
397b8c7
945d75c
4f192b1
51a8017
48ddd5a
8b9ede7
cf8aef2
0a810f5
090b008
051c510
2e270d1
f13d6d6
afe9613
575748e
d4984cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
--- | ||
"@latticexyz/cli": patch | ||
"@latticexyz/common": major | ||
"@latticexyz/create-mud": major | ||
"@latticexyz/recs": patch | ||
"@latticexyz/store-indexer": patch | ||
--- | ||
|
||
Templates and examples now use MUD's new sync packages, all built on top of [viem](https://viem.sh/). This greatly speeds up and stabilizes our networking code and improves types throughout. | ||
|
||
These new sync packages come with support for our `recs` package, including `encodeEntity` and `decodeEntity` utilities for composite keys. | ||
|
||
If you're using `store-cache` and `useRow`/`useRows`, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a [sql.js](https://github.com/sql-js/sql.js/)-powered sync module that will replace `store-cache`. | ||
|
||
**Migrate existing RECS apps to new sync packages** | ||
|
||
As you migrate, you may find some features replaced, removed, or not included by default. Please [open an issue](https://github.com/latticexyz/mud/issues/new) and let us know if we missed anything. | ||
|
||
1. Add `@latticexyz/store-sync` package to your app's `client` package and make sure `viem` is pinned to version `1.3.1` (otherwise you may get type errors) | ||
|
||
2. In your `supportedChains.ts`, replace `foundry` chain with our new `mudFoundry` chain. | ||
|
||
```diff | ||
- import { foundry } from "viem/chains"; | ||
- import { MUDChain, latticeTestnet } from "@latticexyz/common/chains"; | ||
+ import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains"; | ||
|
||
- export const supportedChains: MUDChain[] = [foundry, latticeTestnet]; | ||
+ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet]; | ||
``` | ||
Comment on lines
+21
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this part still necessary? or do we want to keep it so we can add stuff to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's worth keeping for now since we've discovered a couple reasons to hook into viem internals There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a reason to keep it: wevm/viem#1006 |
||
|
||
3. In `getNetworkConfig.ts`, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viem `chain` object. | ||
|
||
```diff | ||
- export async function getNetworkConfig(): Promise<NetworkConfig> { | ||
+ export async function getNetworkConfig() { | ||
``` | ||
|
||
```diff | ||
const initialBlockNumber = params.has("initialBlockNumber") | ||
? Number(params.get("initialBlockNumber")) | ||
- : world?.blockNumber ?? -1; // -1 will attempt to find the block number from RPC | ||
+ : world?.blockNumber ?? 0n; | ||
Comment on lines
+42
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we currently have some mechanism to look for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am imagining in the future |
||
``` | ||
|
||
```diff | ||
+ return { | ||
+ privateKey: getBurnerWallet().value, | ||
+ chain, | ||
+ worldAddress, | ||
+ initialBlockNumber, | ||
+ faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, | ||
+ }; | ||
``` | ||
|
||
4. In `setupNetwork.ts`, replace `setupMUDV2Network` with `syncToRecs`. | ||
|
||
```diff | ||
- import { setupMUDV2Network } from "@latticexyz/std-client"; | ||
- import { createFastTxExecutor, createFaucetService, getSnapSyncRecords } from "@latticexyz/network"; | ||
+ import { createFaucetService } from "@latticexyz/network"; | ||
+ import { createPublicClient, fallback, webSocket, http, createWalletClient, getContract, Hex, parseEther, ClientConfig } from "viem"; | ||
+ import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; | ||
+ import { createBurnerAccount, createContract, transportObserver } from "@latticexyz/common"; | ||
``` | ||
|
||
```diff | ||
- const result = await setupMUDV2Network({ | ||
- ... | ||
- }); | ||
|
||
+ const clientOptions = { | ||
+ chain: networkConfig.chain, | ||
+ transport: transportObserver(fallback([webSocket(), http()])), | ||
+ pollingInterval: 1000, | ||
+ } as const satisfies ClientConfig; | ||
|
||
+ const publicClient = createPublicClient(clientOptions); | ||
|
||
+ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex); | ||
+ const burnerWalletClient = createWalletClient({ | ||
+ ...clientOptions, | ||
+ account: burnerAccount, | ||
+ }); | ||
|
||
+ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ | ||
+ world, | ||
+ config: storeConfig, | ||
+ address: networkConfig.worldAddress as Hex, | ||
+ publicClient, | ||
+ components: contractComponents, | ||
+ startBlock: BigInt(networkConfig.initialBlockNumber), | ||
+ indexerUrl: networkConfig.indexerUrl ?? undefined, | ||
+ }); | ||
|
||
+ const worldContract = createContract({ | ||
+ address: networkConfig.worldAddress as Hex, | ||
+ abi: IWorld__factory.abi, | ||
+ publicClient, | ||
+ walletClient: burnerWalletClient, | ||
+ }); | ||
``` | ||
|
||
```diff | ||
// Request drip from faucet | ||
- const signer = result.network.signer.get(); | ||
- if (networkConfig.faucetServiceUrl && signer) { | ||
- const address = await signer.getAddress(); | ||
+ if (networkConfig.faucetServiceUrl) { | ||
+ const address = burnerAccount.address; | ||
``` | ||
|
||
```diff | ||
const requestDrip = async () => { | ||
- const balance = await signer.getBalance(); | ||
+ const balance = await publicClient.getBalance({ address }); | ||
console.info(`[Dev Faucet]: Player balance -> ${balance}`); | ||
- const lowBalance = balance?.lte(utils.parseEther("1")); | ||
+ const lowBalance = balance < parseEther("1"); | ||
``` | ||
|
||
You can remove the previous ethers `worldContract`, snap sync code, and fast transaction executor. | ||
|
||
The return of `setupNetwork` is a bit different than before, so you may have to do corresponding app changes. | ||
|
||
```diff | ||
+ return { | ||
+ world, | ||
+ components, | ||
+ playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }), | ||
+ publicClient, | ||
+ walletClient: burnerWalletClient, | ||
+ latestBlock$, | ||
+ blockStorageOperations$, | ||
+ waitForTransaction, | ||
+ worldContract, | ||
+ }; | ||
``` | ||
|
||
5. Update `createSystemCalls` with the new return type of `setupNetwork`. | ||
|
||
```diff | ||
export function createSystemCalls( | ||
- { worldSend, txReduced$, singletonEntity }: SetupNetworkResult, | ||
+ { worldContract, waitForTransaction }: SetupNetworkResult, | ||
{ Counter }: ClientComponents | ||
) { | ||
const increment = async () => { | ||
- const tx = await worldSend("increment", []); | ||
- await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash); | ||
+ const tx = await worldContract.write.increment(); | ||
+ await waitForTransaction(tx); | ||
return getComponentValue(Counter, singletonEntity); | ||
}; | ||
``` | ||
|
||
6. (optional) If you still need a clock, you can create it with: | ||
|
||
```ts | ||
import { map, filter } from "rxjs"; | ||
import { createClock } from "@latticexyz/network"; | ||
|
||
const clock = createClock({ | ||
period: 1000, | ||
initialTime: 0, | ||
syncInterval: 5000, | ||
}); | ||
|
||
world.registerDisposer(() => clock.dispose()); | ||
|
||
latestBlock$ | ||
.pipe( | ||
map((block) => Number(block.timestamp) * 1000), // Map to timestamp in ms | ||
filter((blockTimestamp) => blockTimestamp !== clock.lastUpdateTime), // Ignore if the clock was already refreshed with this block | ||
filter((blockTimestamp) => blockTimestamp !== clock.currentTime) // Ignore if the current local timestamp is correct | ||
) | ||
.subscribe(clock.update); // Update the local clock | ||
``` | ||
|
||
If you're using the previous `LoadingState` component, you'll want to migrate to the new `SyncProgress`: | ||
|
||
```ts | ||
import { SyncStep, singletonEntity } from "@latticexyz/store-sync/recs"; | ||
|
||
const syncProgress = useComponentValue(SyncProgress, singletonEntity, { | ||
message: "Connecting", | ||
percentage: 0, | ||
step: SyncStep.INITIALIZE, | ||
}); | ||
|
||
if (syncProgress.step === SyncStep.LIVE) { | ||
// we're live! | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
node_modules | ||
node_modules | ||
*.db | ||
*.db-journal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,26 @@ | ||
import { Component, Entity, getComponentValue } from "@latticexyz/recs"; | ||
import { setup } from "./mud/setup"; | ||
|
||
const { | ||
network: { | ||
storeCache, | ||
network: { blockNumber$ }, | ||
worldSend, | ||
worldContract, | ||
components: { LoadingState }, | ||
}, | ||
network: { components, latestBlock$, worldContract, waitForTransaction }, | ||
} = await setup(); | ||
|
||
const _window = window as any; | ||
_window.storeCache = storeCache; | ||
_window.worldContract = worldContract; | ||
_window.worldSend = worldSend; | ||
_window.waitForTransaction = waitForTransaction; | ||
|
||
_window.getComponentValue = (componentName: keyof typeof components, entity: Entity) => | ||
getComponentValue(components[componentName] as Component, entity); | ||
|
||
// Update block number in the UI | ||
blockNumber$.subscribe((blockNumber) => { | ||
latestBlock$.subscribe((block) => { | ||
const element = document.querySelector("#block"); | ||
if (element) element.innerHTML = String(blockNumber); | ||
if (element) element.innerHTML = String(block.number); | ||
}); | ||
|
||
// Update initial sync status in the UI | ||
LoadingState.update$.subscribe((value) => { | ||
const syncState = value.value[0]?.state; | ||
const element = document.querySelector("#sync-state"); | ||
if (element) element.innerHTML = String(syncState); | ||
components.SyncProgress.update$.subscribe(({ value }) => { | ||
const syncStep = value[0]?.step; | ||
const element = document.querySelector("#sync-step"); | ||
if (element) element.innerHTML = String(syncStep); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,10 @@ | ||
import { getComponentValue } from "@latticexyz/recs"; | ||
import { awaitStreamValue } from "@latticexyz/utils"; | ||
import { ClientComponents } from "./createClientComponents"; | ||
import { SetupNetworkResult } from "./setupNetwork"; | ||
|
||
export type SystemCalls = ReturnType<typeof createSystemCalls>; | ||
|
||
export function createSystemCalls(network: SetupNetworkResult, components: ClientComponents) { | ||
// const increment = async () => { | ||
// const tx = await worldSend("increment", []); | ||
// await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash); | ||
// return getComponentValue(Counter, singletonEntity); | ||
// }; | ||
|
||
return { | ||
// increment, | ||
// TODO | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
big props on this changeset, this is by far the best changeset we have so far