Releases: latticexyz/mud
[email protected]
Major Changes
-
#1214
60cfd089
Thanks @holic! - Templates and examples now use MUD's new sync packages, all built on top of viem. This greatly speeds up and stabilizes our networking code and improves types throughout.These new sync packages come with support for our
recs
package, includingencodeEntity
anddecodeEntity
utilities for composite keys.If you're using
store-cache
anduseRow
/useRows
, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a sql.js-powered sync module that will replacestore-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 and let us know if we missed anything.
-
Add
@latticexyz/store-sync
package to your app'sclient
package and make sureviem
is pinned to version1.3.1
(otherwise you may get type errors) -
In your
supportedChains.ts
, replacefoundry
chain with our newmudFoundry
chain.- 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];
-
In
getNetworkConfig.ts
, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viemchain
object.- export async function getNetworkConfig(): Promise<NetworkConfig> { + export async function getNetworkConfig() {
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;
+ return { + privateKey: getBurnerWallet().value, + chain, + worldAddress, + initialBlockNumber, + faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, + };
-
In
setupNetwork.ts
, replacesetupMUDV2Network
withsyncToRecs
.- 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";
- 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, + });
// 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;
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.+ return { + world, + components, + playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }), + publicClient, + walletClient: burnerWalletClient, + latestBlock$, + blockStorageOperations$, + waitForTransaction, + worldContract, + };
-
Update
createSystemCalls
with the new return type ofsetupNetwork
.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); };
-
(optional) If you still need a clock, you can create it with:
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 newSyncProgress
: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! }
-
@latticexyz/[email protected]
Patch Changes
- #1210
cc2c8da0
Thanks @dk1a! - - Refactor tightcoder to use typescript functions instead of ejs- Optimize
TightCoder
library - Add
isLeftAligned
andgetLeftPaddingBits
common codegen helpers
- Optimize
- Updated dependencies [
c963b46c
,3fb9ce28
,35c9f33d
,5c965a91
,b02f9d0e
,60cfd089
,6071163f
,6c673325
,cd5abcc3
,cc2c8da0
]:- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
@latticexyz/[email protected]
@latticexyz/[email protected]
@latticexyz/[email protected]
Patch Changes
-
#1237
5c965a91
Thanks @alvrs! - Align Store events parameter naming between IStoreWrite and StoreCore -
#1210
cc2c8da0
Thanks @dk1a! - - Refactor tightcoder to use typescript functions instead of ejs- Optimize
TightCoder
library - Add
isLeftAligned
andgetLeftPaddingBits
common codegen helpers
- Optimize
-
Updated dependencies [
3fb9ce28
,35c9f33d
,b02f9d0e
,60cfd089
,6071163f
,6c673325
,cd5abcc3
,cc2c8da0
]:- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
@latticexyz/[email protected]
Major Changes
-
#1198
e86fbc12
Thanks @holic! - Adds store indexer service package with utils to query the indexer service.You can run the indexer locally by checking out the MUD monorepo, installing/building everything, and running
pnpm start:local
frompackages/store-indexer
.To query the indexer in the client, you can create a tRPC client with a URL pointing to the indexer service and call the available tRPC methods:
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer"; const indexer = createIndexerClient({ url: indexerUrl }); const result = await indexer.findAll.query({ chainId: publicClient.chain.id, address, });
If you're using
syncToRecs
, you can just pass in theindexerUrl
option as a shortcut to the above:import { syncToRecs } from "@latticexyz/store-sync/recs"; syncToRecs({ ... indexerUrl: "https://your.indexer.service", });
Minor Changes
-
#1234
131c63e5
Thanks @holic! - - Accept a plain viemPublicClient
(instead of requiring aChain
to be set) instore-sync
andstore-indexer
functions. These functions now fetch chain ID usingpublicClient.getChainId()
when nopublicClient.chain.id
is present.- Allow configuring
store-indexer
with a set of RPC URLs (RPC_HTTP_URL
andRPC_WS_URL
) instead ofCHAIN_ID
.
- Allow configuring
-
#1235
582388ba
Thanks @holic! - ExportsingletonEntity
as const rather than within thesyncToRecs
result.- const { singletonEntity, ... } = syncToRecs({ ... }); + import { singletonEntity, syncToRecs } from "@latticexyz/store-sync/recs"; + const { ... } = syncToRecs({ ... });
Patch Changes
-
#1228
57a52608
Thanks @holic! - AddslatestBlockNumber
andlastBlockNumberProcessed
to internalSyncProgress
component -
#1197
9e5baf4f
Thanks @holic! - Add RECS sync strategy and corresponding utilsimport { createPublicClient, http } from 'viem'; import { syncToRecs } from '@latticexyz/store-sync'; import storeConfig from 'contracts/mud.config'; import { defineContractComponents } from './defineContractComponents'; const publicClient = createPublicClient({ chain, transport: http(), pollingInterval: 1000, }); const { components, singletonEntity, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({ world, config: storeConfig, address: '0x...', publicClient, components: defineContractComponents(...), });
-
#1235
582388ba
Thanks @holic! - AddstartBlock
option tosyncToRecs
.import { syncToRecs } from "@latticexyz/store-sync/recs"; import worlds from "contracts/worlds.json"; syncToRecs({ startBlock: worlds['31337'].blockNumber, ... });
-
#1258
6c673325
Thanks @holic! - AddtableIdToHex
andhexToTableId
pure functions and move/deprecateTableId
. -
Updated dependencies [
c963b46c
,3fb9ce28
,35c9f33d
,5c965a91
,b02f9d0e
,60cfd089
,6071163f
,6c673325
,cd5abcc3
,afdba793
,cc2c8da0
]:- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
@latticexyz/[email protected]
Major Changes
-
#1198
e86fbc12
Thanks @holic! - Adds store indexer service package with utils to query the indexer service.You can run the indexer locally by checking out the MUD monorepo, installing/building everything, and running
pnpm start:local
frompackages/store-indexer
.To query the indexer in the client, you can create a tRPC client with a URL pointing to the indexer service and call the available tRPC methods:
import { createIndexerClient } from "@latticexyz/store-sync/trpc-indexer"; const indexer = createIndexerClient({ url: indexerUrl }); const result = await indexer.findAll.query({ chainId: publicClient.chain.id, address, });
If you're using
syncToRecs
, you can just pass in theindexerUrl
option as a shortcut to the above:import { syncToRecs } from "@latticexyz/store-sync/recs"; syncToRecs({ ... indexerUrl: "https://your.indexer.service", });
Minor Changes
- #1234
131c63e5
Thanks @holic! - - Accept a plain viemPublicClient
(instead of requiring aChain
to be set) instore-sync
andstore-indexer
functions. These functions now fetch chain ID usingpublicClient.getChainId()
when nopublicClient.chain.id
is present.- Allow configuring
store-indexer
with a set of RPC URLs (RPC_HTTP_URL
andRPC_WS_URL
) instead ofCHAIN_ID
.
- Allow configuring
Patch Changes
-
#1214
60cfd089
Thanks @holic! - Templates and examples now use MUD's new sync packages, all built on top of viem. This greatly speeds up and stabilizes our networking code and improves types throughout.These new sync packages come with support for our
recs
package, includingencodeEntity
anddecodeEntity
utilities for composite keys.If you're using
store-cache
anduseRow
/useRows
, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a sql.js-powered sync module that will replacestore-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 and let us know if we missed anything.
-
Add
@latticexyz/store-sync
package to your app'sclient
package and make sureviem
is pinned to version1.3.1
(otherwise you may get type errors) -
In your
supportedChains.ts
, replacefoundry
chain with our newmudFoundry
chain.- 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];
-
In
getNetworkConfig.ts
, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viemchain
object.- export async function getNetworkConfig(): Promise<NetworkConfig> { + export async function getNetworkConfig() {
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;
+ return { + privateKey: getBurnerWallet().value, + chain, + worldAddress, + initialBlockNumber, + faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, + };
-
In
setupNetwork.ts
, replacesetupMUDV2Network
withsyncToRecs
.- 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";
- 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, + });
// 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;
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.+ return { + world, + components, + playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }), + publicClient, + walletClient: burnerWalletClient, + latestBlock$, + blockStorageOperations$, + waitForTransaction, + worldContract, + };
-
Update
createSystemCalls
with the new return type ofsetupNetwork
.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); };
-
(optional) If you still need a clock, you can create it with:
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 newSyncProgress
: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! }
-
-
Updated dependencies [
c963b46c
,e86fbc12
, [3fb9ce28
](https://github.com/latticexyz/...
@latticexyz/[email protected]
Patch Changes
- Updated dependencies [
c963b46c
,3fb9ce28
,35c9f33d
,5c965a91
,b02f9d0e
,60cfd089
,6071163f
,6c673325
,cd5abcc3
,cc2c8da0
]:- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
@latticexyz/[email protected]
@latticexyz/[email protected]
@latticexyz/[email protected]
Patch Changes
-
#1206
e259ef79
Thanks @holic! - GeneratedcontractComponents
now properly importWorld
as type -
#1258
6c673325
Thanks @holic! - AddtableIdToHex
andhexToTableId
pure functions and move/deprecateTableId
. -
#1195
afdba793
Thanks @holic! - Update RECS components with v2 key/value schemas. This helps with encoding/decoding composite keys and strong types for keys/values.This may break if you were previously dependent on
component.id
,component.metadata.componentId
, orcomponent.metadata.tableId
:component.id
is now the on-chainbytes32
hex representation of the table IDcomponent.metadata.componentName
is the table name (e.g.Position
)component.metadata.tableName
is the namespaced table name (e.g.myworld:Position
)component.metadata.keySchema
is an object with key names and their corresponding ABI typescomponent.metadata.valueSchema
is an object with field names and their corresponding ABI types
-
Updated dependencies [
168a4cb4
,c963b46c
,3fb9ce28
,35c9f33d
,5c965a91
,e259ef79
,60cfd089
,6071163f
,6c673325
,cd5abcc3
,afdba793
,cc2c8da0
]:- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
- @latticexyz/[email protected]
@latticexyz/[email protected]
@latticexyz/[email protected]