MUD is a framework for building ambitious onchain applications
MIT License
Bot releases are visible (Hide)
Published by github-actions[bot] 7 months ago
mud-gas-report
binary to gas-report
, since it's no longer MUD specific.66cc35a8c: Create gas-report package, move gas-report cli command and GasReporter contract to it
4385c5a4c: Allow the gas-report
CLI to parse logs via stdin
, so it can be used with custom test commands (e.g. mud test
).
Usage:
# replace `forge test -vvv` with the custom test command
GAS_REPORTER_ENABLED=true forge test -vvv | pnpm gas-report --stdin
90d0d79c: Now uses --isolate
flag in forge test
for more accurate gas measurement.
stdin
logs in gas-report
. Since the script piping in logs to gas-report
can be long-running, it is useful to see its logs to know if it's stalling..d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.Published by github-actions[bot] 7 months ago
9940fdb3e: New package to run your own faucet service. We'll use this soon for our testnet in place of @latticexyz/services
.
To run the faucet server:
pnpm add @latticexyz/faucet
.env
file that has a RPC_HTTP_URL
and FAUCET_PRIVATE_KEY
(or pass the environment variables into the next command)pnpm faucet-server
to start the serverYou can also adjust the server's HOST
(defaults to 0.0.0.0
) and PORT
(defaults to 3002
). The tRPC routes are accessible under /trpc
.
To connect a tRPC client, add the package with pnpm add @latticexyz/faucet
and then use createClient
:
import { createClient } from "@latticexyz/faucet";
const faucet = createClient({ url: "http://localhost:3002/trpc" });
await faucet.mutate.drip({ address: burnerAccount.address });
1d0f7e22b: Added /healthz
and /readyz
healthcheck endpoints for Kubernetes
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.sendTransaction
, which does a better of managing nonces for higher volumes of transactions.Published by github-actions[bot] 7 months ago
331dbfdcb: We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
As such, we've replaced blockStorageOperations# @latticexyz/dev-tools with
storedBlockLogs# @latticexyz/dev-tools, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.
939916bcd: MUD dev tools is updated to latest sync stack. You must now pass in all of its data requirements rather than relying on magic globals.
import { mount as mountDevTools } from "@latticexyz/dev-tools";
- mountDevTools();
+ mountDevTools({
+ config,
+ publicClient,
+ walletClient,
+ latestBlock$,
+ blockStorageOperations$,
+ worldAddress,
+ worldAbi,
+ write$,
+ // if you're using recs
+ recsWorld,
+ });
It's also advised to wrap dev tools so that it is only mounted during development mode. Here's how you do this with Vite:
// https://vitejs.dev/guide/env-and-mode.html
if (import.meta.env.DEV) {
mountDevTools({ ... });
}
252a1852: Migrated to new config format.
24a0dd444: Improved rendering of transactions that make calls via World's call
and callFrom
methods
1faf7f697: Added Zustand support to Dev Tools:
const { syncToZustand } from "@latticexyz/store-sync";
const { mount as mountDevTools } from "@latticexyz/dev-tools";
const { useStore } = syncToZustand({ ... });
mountDevTools({
...
useStore,
});
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
d5c0682fb: Updated all human-readable resource IDs to use {namespace}__{name}
for consistency with world function signatures.
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
b8a6158d6: bump viem to 1.6.0
e0193e573: Updates store event key
reference to keyTuple
e0377761c: Updates table
reference to tableId
bfcb293d1: What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
753bdce41: Store sync logic is now consolidated into a createStoreSync
function exported from @latticexyz/store-sync
. This simplifies each storage sync strategy to just a simple wrapper around the storage adapter. You can now sync to RECS with syncToRecs
or SQLite with syncToSqlite
and PostgreSQL support coming soon.
There are no breaking changes if you were just using syncToRecs
from @latticexyz/store-sync
or running the sqlite-indexer
binary from @latticexyz/store-indexer
.
5294a7d59: Improves support for internal/client-only RECS components
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
af639a264: Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
6c6733256: Add tableIdToHex
and hexToTableId
pure functions and move/deprecate TableId
.
cea754dde: - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
d2f8e9400: Moved to new resource ID utils.
Updated dependencies [7ce82b6fc]
Updated dependencies [5df1f31bc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [77dce993a]
Updated dependencies [ce97426c0]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [3622e39dd]
Updated dependencies [c963b46c7]
Updated dependencies [08d7c471f]
Updated dependencies [05b3e8882]
Updated dependencies [52182f70d]
Updated dependencies [0f27afddb]
Updated dependencies [748f4588a]
Updated dependencies [865253dba]
Updated dependencies [8f49c277d]
Updated dependencies [7fa2ca183]
Updated dependencies [ce7125a1b]
Updated dependencies [745485cda]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [c14f8bf1e]
Updated dependencies [c07fa0215]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [6ca1874e0]
Updated dependencies [331dbfdcb]
Updated dependencies [504e25dc8]
Updated dependencies [e86fbc126]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [430e6b29a]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [de47d698f]
Updated dependencies [e48fb3b03]
Updated dependencies [2c920de7]
Updated dependencies [0a3b9b1c9]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [6c615b608]
Updated dependencies [3fb9ce283]
Updated dependencies [c207d35e8]
Updated dependencies [db7798be2]
Updated dependencies [bb6ada740]
Updated dependencies [85b94614b]
Updated dependencies [35c9f33df]
Updated dependencies [3be4deecf]
Updated dependencies [a25881160]
Updated dependencies [a4aff73c5]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [5debcca8]
Updated dependencies [c4d5eb4e4]
Updated dependencies [f8dab7334]
Updated dependencies [1a0fa7974]
Updated dependencies [57a526083]
Updated dependencies [f62c767e7]
Updated dependencies [d00c4a9af]
Updated dependencies [9e5baf4ff]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [c32a9269a]
Updated dependencies [eb384bb0e]
Updated dependencies [37c228c63]
Updated dependencies [618dd0e89]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [1faf7f697]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [e5d208e40]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [131c63e53]
Updated dependencies [1f80a0b52]
Updated dependencies [712866f5f]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e5a962bc3]
Updated dependencies [331f0d636]
Updated dependencies [f6f402896]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [08b422171]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [4e445a1ab]
Updated dependencies [37c228c63]
Updated dependencies [37c228c63]
Updated dependencies [433078c54]
Updated dependencies [669fa43e5]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [4c7fd3eb2]
Updated dependencies [a0341daf9]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [582388ba5]
Updated dependencies [6573e38e9]
Updated dependencies [51914d656]
Updated dependencies [eeb15cc06]
Updated dependencies [063daf80e]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [997286bac]
Updated dependencies [2bfee9217]
Updated dependencies [1ca35e9a1]
Updated dependencies [4081493b8]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [582388ba5]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [a735e14b4]
Updated dependencies [88b1a5a19]
Updated dependencies [1e2ad78e2]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [3e024fcf3]
Updated dependencies [b02f9d0e4]
Updated dependencies [2ca75f9b9]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1a82c278]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [f1cd43bf9]
Updated dependencies [9d0f492a9]
Updated dependencies [55a05fd7a]
Updated dependencies [7e6e5157b]
Updated dependencies [f03531d97]
Updated dependencies [c583f3cd0]
Updated dependencies [31ffc9d5d]
Updated dependencies [5e723b90e]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [a7b30c79b]
Updated dependencies [6470fe1fd]
Updated dependencies [86766ce1]
Updated dependencies [92de59982]
Updated dependencies [5741d53d0]
Updated dependencies [aee8020a6]
Updated dependencies [22ee44700]
Updated dependencies [e2d089c6d]
Updated dependencies [1327ea8c8]
Updated dependencies [ad4ac4459]
Updated dependencies [f6d214e3d]
Updated dependencies [3f5d33af]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [331dbfdcb]
Updated dependencies [93390d89]
Updated dependencies [57d8965df]
Updated dependencies [18d3aea55]
Updated dependencies [fa7763583]
Updated dependencies [7987c94d6]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [e48171741]
Updated dependencies [e4a6189df]
Updated dependencies [753bdce41]
Updated dependencies [5294a7d59]
Updated dependencies [69a96f109]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [e3de1a338]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [b8a6158d6]
Updated dependencies [4e4a34150]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [be18b75b]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [9ef3f9a7c]
Updated dependencies [34203e4ed]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [17f987209]
Updated dependencies [25086be5f]
Updated dependencies [37c228c63]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [c642ff3a0]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [c049c23f4]
Updated dependencies [5e71e1cb5]
Updated dependencies [7eabd06f7]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [939916bcd]
Updated dependencies [251170e1e]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [95f64c85]
Updated dependencies [afdba793f]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [29c3f5087]
Updated dependencies [cea754dde]
Updated dependencies [331f0d636]
Updated dependencies [1b5eb0d07]
Updated dependencies [95c59b203]
Updated dependencies [d2f8e9400]
Updated dependencies [4c1dcd81e]
Updated dependencies [adc68225]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [7b73f44d9]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
78949f2c9: Replaced the react
template with a basic task list app using the new Zustand storage adapter and sync method. This new template better demonstrates the different ways of building with MUD and has fewer concepts to learn (i.e. just tables and records, no more ECS).
For ECS-based React apps, you can use react-ecs
template for the previous RECS storage adapter.
48c51b52a: RECS components are now dynamically created and inferred from your MUD config when using syncToRecs
.
To migrate existing projects after upgrading to this MUD version:
Remove contractComponents.ts
from client/src/mud
Remove components
argument from syncToRecs
Update build:mud
and dev
scripts in contracts/package.json
to remove tsgen
- "build:mud": "mud tablegen && mud worldgen && mud tsgen --configPath mud.config.ts --out ../client/src/mud",
+ "build:mud": "mud tablegen && mud worldgen",
- "dev": "pnpm mud dev-contracts --tsgenOutput ../client/src/mud",
+ "dev": "pnpm mud dev-contracts",
939916bcd: MUD dev tools is updated to latest sync stack. You must now pass in all of its data requirements rather than relying on magic globals.
import { mount as mountDevTools } from "@latticexyz/dev-tools";
- mountDevTools();
+ mountDevTools({
+ config,
+ publicClient,
+ walletClient,
+ latestBlock$,
+ blockStorageOperations$,
+ worldAddress,
+ worldAbi,
+ write$,
+ // if you're using recs
+ recsWorld,
+ });
It's also advised to wrap dev tools so that it is only mounted during development mode. Here's how you do this with Vite:
// https://vitejs.dev/guide/env-and-mode.html
if (import.meta.env.DEV) {
mountDevTools({ ... });
}
60cfd089f: 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, 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-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 and let us know if we missed anything.
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)
In your supportedChains.ts
, replace foundry
chain with our new mudFoundry
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 viem chain
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
, replace setupMUDV2Network
with syncToRecs
.
- 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 of setupNetwork
.
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 new SyncProgress
:
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!
}
252a1852: Migrated to new config format.
6288f9033: Updated templates to use mprocs instead of concurrently for running dev scripts.
07dd6f32c: Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
aabd30767: Bumped Solidity version to 0.8.24.
f6133591a: Replaced usage of window
global in vanilla JS template with an event listener on the button.
b68e1699b: Enabled MUD CLI debug logs for all templates.
83583a505: Templates now use out
for their forge build
artifacts, including ABIs. If you have a project created from a previous template, you can update your packages/contracts/package.json
with:
- "build:abi": "rimraf abi && forge build --extra-output-files abi --out abi --skip test script MudTest.sol",
- "build:abi-ts": "mud abi-ts --input 'abi/IWorld.sol/IWorld.abi.json' && prettier --write '**/*.abi.json.d.ts'",
+ "build:abi": "forge clean && forge build --skip test script",
+ "build:abi-ts": "mud abi-ts && prettier --write '**/*.abi.json.d.ts'",
And your packages/client/src/mud/setupNetwork
with:
- import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json";
+ import IWorldAbi from "contracts/out/IWorld.sol/IWorld.abi.json";
59267655: Added viem custom client actions that work the same as MUD's now-deprecated getContract
, writeContract
, and sendTransaction
wrappers. Templates have been updated to reflect the new patterns.
You can migrate your own code like this:
-import { createWalletClient } from "viem";
-import { getContract, writeContract, sendTransaction } from "@latticexyz/common";
+import { createWalletClient, getContract } from "viem";
+import { transactionQueue, writeObserver } from "@latticexyz/common/actions";
-const walletClient = createWalletClient(...);
+const walletClient = createWalletClient(...)
+ .extend(transactionQueue())
+ .extend(writeObserver({ onWrite });
const worldContract = getContract({
client: { publicClient, walletClient },
- onWrite,
});
1faf7f697: Added Zustand support to Dev Tools:
const { syncToZustand } from "@latticexyz/store-sync";
const { mount as mountDevTools } from "@latticexyz/dev-tools";
const { useStore } = syncToZustand({ ... });
mountDevTools({
...
useStore,
});
92de59982: Bump Solidity version to 0.8.21
331dbfdcb: We've updated Store events to be "schemaless", meaning there is enough information in each event to only need to operate on the bytes of each record to make an update to that record without having to first decode the record by its schema. This enables new kinds of indexers and sync strategies.
As such, we've replaced blockStorageOperations# Change Log with
storedBlockLogs# Change Log, a stream of simplified Store event logs after they've been synced to the configured storage adapter. These logs may not reflect exactly the events that are on chain when e.g. hydrating from an indexer, but they will still allow the client to "catch up" to the on-chain state of your tables.
55377ffe6: We now use @latticexyz/abi-ts
to generate TS type declaration files (.d.ts
) for each ABI JSON file. This replaces our usage TypeChain everywhere.
If you have a MUD project created from an older template, you can replace TypeChain with abi-ts
by first updating your contracts' package.json
:
-"build": "pnpm run build:mud && pnpm run build:abi && pnpm run build:typechain",
+"build": "pnpm run build:mud && pnpm run build:abi && pnpm run build:abi-ts",
-"build:abi": "forge clean && forge build",
+"build:abi": "rimraf abi && forge build --extra-output-files abi --out abi --skip test script MudTest.sol",
+"build:abi-ts": "mud abi-ts --input 'abi/IWorld.sol/IWorld.abi.json' && prettier --write '**/*.abi.json.d.ts'",
"build:mud": "mud tablegen && mud worldgen",
-"build:typechain": "rimraf types && typechain --target=ethers-v5 out/IWorld.sol/IWorld.json",
And update your client's setupNetwork.ts
with:
-import { IWorld__factory } from "contracts/types/ethers-contracts/factories/IWorld__factory";
+import IWorldAbi from "contracts/abi/IWorld.sol/IWorld.abi.json";
const worldContract = createContract({
address: networkConfig.worldAddress as Hex,
- abi: IWorld__factory.abi,
+ abi: IWorldAbi,
bfcb293d1: What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
7ce82b6fc: Store config now defaults storeArgument: false
for all tables. This means that table libraries, by default, will no longer include the extra functions with the _store
argument. This default was changed to clear up the confusion around using table libraries in tests, PostDeploy
scripts, etc.
If you are sure you need to manually specify a store when interacting with tables, you can still manually toggle it back on with storeArgument: true
in the table settings of your MUD config.
If you want to use table libraries in PostDeploy.s.sol
, you can add the following lines:
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
+ StoreSwitch.setStoreAddress(worldAddress);
+
+ SomeTable.get(someKey);
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
952cd5344: All Store
methods now require the table's value schema to be passed in as an argument instead of loading it from storage.
This decreases gas cost and removes circular dependencies of the Schema table (where it was not possible to write to the Schema table before the Schema table was registered).
function setRecord(
bytes32 table,
bytes32[] calldata key,
bytes calldata data,
+ Schema valueSchema
) external;
The same diff applies to getRecord
, getField
, setField
, pushToField
, popFromField
, updateInField
, and deleteRecord
.
This change only requires changes in downstream projects if the Store
methods were accessed directly. In most cases it is fully abstracted in the generated table libraries,
so downstream projects only need to regenerate their table libraries after updating MUD.
c5148da76: Updated templates' PostDeploy script to set store address so that tables can be used directly inside PostDeploy.
257a0afc: Bumped typescript
to 5.4.2
, eslint
to 8.57.0
, and both @typescript-eslint/eslint-plugin
and @typescript-eslint/parser
to 7.1.1
.
aacffcb59: Pinned prettier-plugin-solidity version to 1.1.3
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
6963a9e85: Templates now correctly include their respective .gitignore
files
afaf2f5ff: - Store
's internal schema table is now a normal table instead of using special code paths. It is renamed to Tables, and the table ID changed from mudstore:schema
to mudstore:Tables
Store
's registerSchema
and setMetadata
are combined into a single registerTable
method. This means metadata (key names, field names) is immutable and indexers can create tables with this metadata when a new table is registered on-chain.
- function registerSchema(bytes32 table, Schema schema, Schema keySchema) external;
-
- function setMetadata(bytes32 table, string calldata tableName, string[] calldata fieldNames) external;
+ function registerTable(
+ bytes32 table,
+ Schema keySchema,
+ Schema valueSchema,
+ string[] calldata keyNames,
+ string[] calldata fieldNames
+ ) external;
World
's registerTable
method is updated to match the Store
interface, setMetadata
is removed
The getSchema
method is renamed to getValueSchema
on all interfaces
- function getSchema(bytes32 table) external view returns (Schema schema);
+ function getValueSchema(bytes32 table) external view returns (Schema valueSchema);
The store-sync
and cli
packages are updated to integrate the breaking protocol changes. Downstream projects only need to manually integrate these changes if they access low level Store
or World
functions. Otherwise, a fresh deploy with the latest MUD will get you these changes.
48909d151: bump forge-std and ds-test dependencies
1b33a915c: Fixed an issue when creating a new project from the react
app, where React's expressions were overlapping with Handlebars expressions (used by our template command).
b8a6158d6: bump viem to 1.6.0
5237e320: Added dbaeumer.vscode-eslint
and esbenp.prettier-vscode
to recommended VSCode extensions.
331f0d636: Move createFaucetService
from @latticexyz/network
to @latticexyz/services/faucet
.
- import { createFaucetService } from "@latticexyz/network";
+ import { createFaucetService } from "@latticexyz/services/faucet";
ac508bf18: Renamed the default filename of generated user types from Types.sol
to common.sol
and the default filename of the generated table index file from Tables.sol
to index.sol
.
Both can be overridden via the MUD config:
export default mudConfig({
/** Filename where common user types will be generated and imported from. */
userTypesFilename: "common.sol",
/** Filename where codegen index will be generated. */
codegenIndexFilename: "index.sol",
});
Note: userTypesFilename
was renamed from userTypesPath
and .sol
is not appended automatically anymore but needs to be part of the provided filename.
To update your existing project, update all imports from Tables.sol
to index.sol
and all imports from Types.sol
to common.sol
, or override the defaults in your MUD config to the previous values.
- import { Counter } from "../src/codegen/Tables.sol";
+ import { Counter } from "../src/codegen/index.sol";
- import { ExampleEnum } from "../src/codegen/Types.sol";
+ import { ExampleEnum } from "../src/codegen/common.sol";
d844cd441: Sped up builds by using more of forge's cache.
Previously we'd build only what we needed because we would check in ABIs and other build artifacts into git, but that meant that we'd get a lot of forge cache misses. Now that we no longer need these files visible, we can take advantage of forge's caching and greatly speed up builds, especially incremental ones.
4e4a34150: bump to latest TS version (5.1.6)
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
cea754dde: - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
331f0d636: Deprecate @latticexyz/std-client
and remove v1 network dependencies.
getBurnerWallet
is replaced by getBurnerPrivateKey
from @latticexyz/common
. It now returns a Hex
string instead of an rxjs
BehaviorSubject
.
- import { getBurnerWallet } from "@latticexyz/std-client";
+ import { getBurnerPrivateKey } from "@latticexyz/common";
- const privateKey = getBurnerWallet().value;
- const privateKey = getBurnerPrivateKey();
All functions from std-client
that depended on v1 network code are removed (most notably setupMUDNetwork
and setupMUDV2Network
). Consumers should upgrade to v2 networking code from @latticexyz/store-sync
.
The following functions are removed from std-client
because they are very use-case specific and depend on deprecated code: getCurrentTurn
, getTurnAtTime
, getGameConfig
, isUntraversable
, getPlayerEntity
, resolveRelationshipChain
, findEntityWithComponentInRelationshipChain
, findInRelationshipChain
. Consumers should vendor these functions if they are still needed.
Remaining exports from std-client
are moved to /deprecated
. The package will be removed in a future release (once there are replacements for the deprecated exports).
- import { ... } from "@latticexyz/std-client";
+ import { ... } from "@latticexyz/std-client/deprecated";
Published by github-actions[bot] 7 months ago
5e723b90e: - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
.d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.Published by github-actions[bot] 7 months ago
65c9546c4: - Add renderWithFieldSuffix
helper method to always render a field function with a suffix, and optionally render the same function without a suffix.
methodNameSuffix
from RenderField
interface, because the suffix is now computed as part of renderWithFieldSuffix
.44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
bfcb293d1: What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
5e723b90e: - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
60cfd089f: 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, 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-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 and let us know if we missed anything.
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)
In your supportedChains.ts
, replace foundry
chain with our new mudFoundry
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 viem chain
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
, replace setupMUDV2Network
with syncToRecs
.
- 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 of setupNetwork
.
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 new SyncProgress
:
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!
}
6c6733256: Add tableIdToHex
and hexToTableId
pure functions and move/deprecate TableId
.
cd5abcc3b: Add utils for using viem with MUD
createContract
is a wrapper around viem's getContract
but with better nonce handling for faster executing of transactions. It has the same arguments and return type as getContract
.createNonceManager
helps track local nonces, used by createContract
.Also renames mudTransportObserver
to transportObserver
.
aabd30767: Bumped Solidity version to 0.8.24.
331dbfdcb: readHex
was moved from @latticexyz/protocol-parser
to @latticexyz/common
066056154: - Added a sendTransaction
helper to mirror viem's sendTransaction
, but with our nonce manager
sendTransaction
and writeContract
for better nonce handlingpending
for transaction simulation and transaction count (when initializing the nonce manager)3fb9ce283: Add utils for using viem with MUD
mudFoundry
chain with a transaction request formatter that temporarily removes max fees to work better with anvil --base-fee 0
createBurnerAccount
that also temporarily removes max fees during transaction signing to work better with anvil --base-fee 0
mudTransportObserver
that will soon let MUD Dev Tools observe transactionsYou can use them like:
import { createBurnerAccount, mudTransportObserver } from "@latticexyz/common";
import { mudFoundry } from "@latticexyz/common/chains";
createWalletClient({
account: createBurnerAccount(privateKey),
chain: mudFoundry,
transport: mudTransportObserver(http()),
pollingInterval: 1000,
});
939916bcd: createContract
now has an onWrite
callback so you can observe writes. This is useful for wiring up the transanction log in MUD dev tools.
import { createContract, ContractWrite } from "@latticexyz/common";
import { Subject } from "rxjs";
const write$ = new Subject<ContractWrite>();
creactContract({
...
onWrite: (write) => write$.next(write),
});
b8a6158d6: - adds defaultPriorityFee
to mudFoundry
for better support with MUD's default anvil config and removes workaround in createContract
59267655: Added viem custom client actions that work the same as MUD's now-deprecated getContract
, writeContract
, and sendTransaction
wrappers. Templates have been updated to reflect the new patterns.
You can migrate your own code like this:
-import { createWalletClient } from "viem";
-import { getContract, writeContract, sendTransaction } from "@latticexyz/common";
+import { createWalletClient, getContract } from "viem";
+import { transactionQueue, writeObserver } from "@latticexyz/common/actions";
-const walletClient = createWalletClient(...);
+const walletClient = createWalletClient(...)
+ .extend(transactionQueue())
+ .extend(writeObserver({ onWrite });
const worldContract = getContract({
client: { publicClient, walletClient },
- onWrite,
});
1b5eb0d07: Added unique
and groupBy
array helpers to @latticexyz/common/utils
.
import { unique } from "@latticexyz/common/utils";
unique([1, 2, 1, 4, 3, 2]);
// [1, 2, 4, 3]
import { groupBy } from "@latticexyz/common/utils";
const records = [
{ type: "cat", name: "Bob" },
{ type: "cat", name: "Spot" },
{ type: "dog", name: "Rover" },
];
Object.fromEntries(groupBy(records, (record) => record.type));
// {
// "cat": [{ type: "cat", name: "Bob" }, { type: "cat", name: "Spot" }],
// "dog: [{ type: "dog", name: "Rover" }]
// }
44a5432ac: - Add getRemappings
to get foundry remappings as an array of [to, from]
tuples.
extractUserTypes
solidity parser utility to extract user-defined types.loadAndExtractUserTypes
helper to load and parse a solidity file, extracting user-defined types.d075f82f3: - Moves contract write logic out of createContract
into its own writeContract
method so that it can be used outside of the contract instance, and for consistency with viem.
createContract
in favor of getContract
for consistency with viem.createNonceManager
's BroadcastChannel
setup and moves out the notion of a "nonce manager ID" to getNonceManagerId
so we can create an internal cache with getNonceManager
for use in writeContract
.If you were using the createNonceManager
before, you'll just need to rename publicClient
argument to client
:
const publicClient = createPublicClient({ ... });
- const nonceManager = createNonceManager({ publicClient, ... });
+ const nonceManager = createNonceManager({ client: publicClient, ... });
331dbfdcb: spliceHex
was added, which has a similar API as JavaScript's Array.prototype.splice
, but for Hex
strings.
spliceHex("0x123456", 1, 1, "0x0000"); // "0x12000056"
92de59982: Bump Solidity version to 0.8.21
0c4f9fea9: TableId.toHex()
now truncates name/namespace to 16 bytes each, to properly fit into a bytes32
hex string.
Also adds a few utils we'll need in the indexer:
bigIntMin
is similar to Math.min
but for bigint
sbigIntMax
is similar to Math.max
but for bigint
sbigIntSort
for sorting an array of bigint
schunk
to split an array into chunkswait
returns a Promise
that resolves after specified number of milliseconds708b49c50: Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
d2f8e9400: Renames resourceIdToHex
to resourceToHex
and hexToResourceId
to hexToResource
, to better distinguish between a resource ID (hex value) and a resource reference (type, namespace, name).
- resourceIdToHex({ type: 'table', namespace: '', name: 'Position' });
+ resourceToHex({ type: 'table', namespace: '', name: 'Position' });
- hexToResourceId('0x...');
+ hexToResource('0x...');
Previous methods still exist but are now deprecated to ease migration and reduce breaking changes. These will be removed in a future version.
Also removes the previously deprecated and unused table ID utils (replaced by these resource ID utils).
b1d41727d: Added a mapObject
helper to map the value of each property of an object to a new value.
4c1dcd81e: - Added a Result<Ok, Err>
type for more explicit and typesafe error handling (inspired by Rust).
includes
util as typesafe alternative to Array.prototype.includes()
.6071163f7: - Moves zero gas fee override to createContract
until https://github.com/wagmi-dev/viem/pull/963 or similar feature lands
gas
is providedd7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
5df1f31bc: Updated chunk
types to use readonly arrays
cea754dde: - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
331f0d636: Deprecate @latticexyz/std-client
and remove v1 network dependencies.
getBurnerWallet
is replaced by getBurnerPrivateKey
from @latticexyz/common
. It now returns a Hex
string instead of an rxjs
BehaviorSubject
.
- import { getBurnerWallet } from "@latticexyz/std-client";
+ import { getBurnerPrivateKey } from "@latticexyz/common";
- const privateKey = getBurnerWallet().value;
- const privateKey = getBurnerPrivateKey();
All functions from std-client
that depended on v1 network code are removed (most notably setupMUDNetwork
and setupMUDV2Network
). Consumers should upgrade to v2 networking code from @latticexyz/store-sync
.
The following functions are removed from std-client
because they are very use-case specific and depend on deprecated code: getCurrentTurn
, getTurnAtTime
, getGameConfig
, isUntraversable
, getPlayerEntity
, resolveRelationshipChain
, findEntityWithComponentInRelationshipChain
, findInRelationshipChain
. Consumers should vendor these functions if they are still needed.
Remaining exports from std-client
are moved to /deprecated
. The package will be removed in a future release (once there are replacements for the deprecated exports).
- import { ... } from "@latticexyz/std-client";
+ import { ... } from "@latticexyz/std-client/deprecated";
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
16b13ea8f: Adds viem workaround for zero base fee used by MUD's anvil config
82693072: waitForIdle
now falls back to setTimeout
for environments without requestIdleCallback
.
d5c0682fb: Updated all human-readable resource IDs to use {namespace}__{name}
for consistency with world function signatures.
01e46d99: Removed some unused files, namely curry
in @latticexyz/common
and useDeprecatedComputedValue
from @latticexyz/react
.
bb6ada740: Initial sync from indexer no longer blocks the promise returning from createStoreSync
, syncToRecs
, and syncToSqlite
. This should help with rendering loading screens using the SyncProgress
RECS component and avoid the long flashes of no content in templates.
By default, syncToRecs
and syncToSqlite
will start syncing (via observable subscription) immediately after called.
If your app needs to control when syncing starts, you can use the startSync: false
option and then blockStoreOperations$.subscribe()
to start the sync yourself. Just be sure to unsubscribe to avoid memory leaks.
const { blockStorageOperations$ } = syncToRecs({
...
startSync: false,
});
// start sync manually by subscribing to `blockStorageOperation# Change Log
const subcription = blockStorageOperation$.subscribe();
// clean up subscription
subscription.unsubscribe();
35c9f33df: - Remove need for tx queue in createContract
0b8ce3f2c: Minor fix to resolving user types: solc
doesn't like relative imports without ./
, but is fine with relative imports from ./../
, so we always append ./
to the relative path.
933b54b5f: The benchmark util now logs to stdout
instead of stderr
.
307abab3: resourceToLabel
now correctly returns just the resource name if its in the root namespace.
aacffcb59: Pinned prettier-plugin-solidity version to 1.1.3
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
e34d1170: Moved the transaction simulation step to just before sending the transaction in our transaction queue actions (sendTransaction
and writeContract
).
This helps avoid cascading transaction failures for deep queues or when a transaction succeeding depends on the value of the previous.
db314a74: Upgraded prettier version to 3.2.5 and prettier-plugin-solidity version to 1.3.1.
8d51a0348: Clean up Memory.sol, make mcopy pure
c162ad5a5: Prevented errors not included in the contract (but present in the file) from being included in the interface by contractToInterface
f62c767e7: Moved some codegen to use fs/promises
for better parallelism.
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
b8a6158d6: bump viem to 1.6.0
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
3e057061d: Removed chalk usage from modules imported in client fix downstream client builds (vite in particular).
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
24a6cd536: Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
25086be5f: Replaced temporary .mudtest
file in favor of WORLD_ADDRESS
environment variable when running tests with MudTest
contract
c4f49240d: Table libraries now correctly handle uninitialized fixed length arrays.
cc2c8da00: - Refactor tightcoder to use typescript functions instead of ejs
TightCoder
libraryisLeftAligned
and getLeftPaddingBits
common codegen helpersUpdated dependencies [aabd30767]
Updated dependencies [b38c096d]
Updated dependencies [f99e88987]
Updated dependencies [48909d151]
Updated dependencies [b02f9d0e4]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [f03531d97]
Updated dependencies [b8a6158d6]
Updated dependencies [92de59982]
Updated dependencies [535229984]
Updated dependencies [d7b1c588a]
Published by github-actions[bot] 7 months ago
07dd6f32c: Renamed all occurrences of schema
where it is used as "value schema" to valueSchema
to clearly distinguish it from "key schema".
The only breaking change for users is the change from schema
to valueSchema
in mud.config.ts
.
// mud.config.ts
export default mudConfig({
tables: {
CounterTable: {
keySchema: {},
- schema: {
+ valueSchema: {
value: "uint32",
},
},
}
}
44236041f: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
952cd5344: All Store
methods now require the table's value schema to be passed in as an argument instead of loading it from storage.
This decreases gas cost and removes circular dependencies of the Schema table (where it was not possible to write to the Schema table before the Schema table was registered).
function setRecord(
bytes32 table,
bytes32[] calldata key,
bytes calldata data,
+ Schema valueSchema
) external;
The same diff applies to getRecord
, getField
, setField
, pushToField
, popFromField
, updateInField
, and deleteRecord
.
This change only requires changes in downstream projects if the Store
methods were accessed directly. In most cases it is fully abstracted in the generated table libraries,
so downstream projects only need to regenerate their table libraries after updating MUD.
de151fec0: - Add FieldLayout
, which is a bytes32
user-type similar to Schema
.
Both FieldLayout
and Schema
have the same kind of data in the first 4 bytes.
But whereas Schema
has SchemaType
enum in each of the other 28 bytes, FieldLayout
has static byte lengths in each of the other 28 bytes.
Replace Schema valueSchema
with FieldLayout fieldLayout
in Store and World contracts.
FieldLayout
is more gas-efficient because it already has lengths, and Schema
has types which need to be converted to lengths.
Add getFieldLayout
to IStore
interface.
There is no FieldLayout
for keys, only for values, because key byte lengths aren't usually relevant on-chain. You can still use getKeySchema
if you need key types.
Add fieldLayoutToHex
utility to protocol-parser
package.
Add constants.sol
for constants shared between FieldLayout
, Schema
and PackedCounter
.
c32a9269a: - All World
function selectors that previously had bytes16 namespace, bytes16 name
arguments now use bytes32 resourceSelector
instead.
This includes setRecord
, setField
, pushToField
, popFromField
, updateInField
, deleteRecord
, call
, grantAccess
, revokeAccess
, registerTable
,
registerStoreHook
, registerSystemHook
, registerFunctionSelector
, registerSystem
and registerRootFunctionSelector
.
This change aligns the World
function selectors with the Store
function selectors, reduces clutter, reduces gas cost and reduces the World
's contract size.
World
's registerHook
function is removed. Use registerStoreHook
or registerSystemHook
instead.deploy
script is updated to integrate the World interface changese5d208e40: The registerRootFunctionSelector
function's signature was changed to accept a string functionSignature
parameter instead of a bytes4 functionSelector
parameter.
This change enables the World
to store the function signatures of all registered functions in a FunctionSignatures
offchain table, which will allow for the automatic generation of interfaces for a given World
address in the future.
IBaseWorld {
function registerRootFunctionSelector(
ResourceId systemId,
- bytes4 worldFunctionSelector,
+ string memory worldFunctionSignature,
bytes4 systemFunctionSelector
) external returns (bytes4 worldFunctionSelector);
}
3d0b3edb4: Removes .mudbackup
file handling and --backup
, --restore
, and --force
options from mud set-version
command.
To revert to a previous MUD version, use git diff
to find the version that you changed from and want to revert to and run pnpm mud set-version <prior-version>
again.
afaf2f5ff: - Store
's internal schema table is now a normal table instead of using special code paths. It is renamed to Tables, and the table ID changed from mudstore:schema
to mudstore:Tables
Store
's registerSchema
and setMetadata
are combined into a single registerTable
method. This means metadata (key names, field names) is immutable and indexers can create tables with this metadata when a new table is registered on-chain.
- function registerSchema(bytes32 table, Schema schema, Schema keySchema) external;
-
- function setMetadata(bytes32 table, string calldata tableName, string[] calldata fieldNames) external;
+ function registerTable(
+ bytes32 table,
+ Schema keySchema,
+ Schema valueSchema,
+ string[] calldata keyNames,
+ string[] calldata fieldNames
+ ) external;
World
's registerTable
method is updated to match the Store
interface, setMetadata
is removed
The getSchema
method is renamed to getValueSchema
on all interfaces
- function getSchema(bytes32 table) external view returns (Schema schema);
+ function getValueSchema(bytes32 table) external view returns (Schema valueSchema);
The store-sync
and cli
packages are updated to integrate the breaking protocol changes. Downstream projects only need to manually integrate these changes if they access low level Store
or World
functions. Otherwise, a fresh deploy with the latest MUD will get you these changes.
29c3f5087: deploy
, test
, dev-contracts
were overhauled using a declarative deployment approach under the hood. Deploys are now idempotent and re-running them will introspect the world and figure out the minimal changes necessary to bring the world into alignment with its config: adding tables, adding/upgrading systems, changing access control, etc.
The following CLI arguments are now removed from these commands:
--debug
(you can now adjust CLI output with DEBUG
environment variable, e.g. DEBUG=mud:*
)--priorityFeeMultiplier
(now calculated automatically)--disableTxWait
(everything is now parallelized with smarter nonce management)--pollInterval
(we now lean on viem defaults and we don't wait/poll until the very end of the deploy)Most deployment-in-progress logs are now behind a debug flag, which you can enable with a DEBUG=mud:*
environment variable.
48c51b52a: RECS components are now dynamically created and inferred from your MUD config when using syncToRecs
.
To migrate existing projects after upgrading to this MUD version:
Remove contractComponents.ts
from client/src/mud
Remove components
argument from syncToRecs
Update build:mud
and dev
scripts in contracts/package.json
to remove tsgen
- "build:mud": "mud tablegen && mud worldgen && mud tsgen --configPath mud.config.ts --out ../client/src/mud",
+ "build:mud": "mud tablegen && mud worldgen",
- "dev": "pnpm mud dev-contracts --tsgenOutput ../client/src/mud",
+ "dev": "pnpm mud dev-contracts",
57d8965df: Separated core systems deployment from CoreModule
, and added the systems as arguments to CoreModule
31ffc9d5d: The registerFunctionSelector
function now accepts a single functionSignature
string paramemer instead of separating function name and function arguments into separate parameters.
IBaseWorld {
function registerFunctionSelector(
ResourceId systemId,
- string memory systemFunctionName,
- string memory systemFunctionArguments
+ string memory systemFunctionSignature
) external returns (bytes4 worldFunctionSelector);
}
This is a breaking change if you were manually registering function selectors, e.g. in a PostDeploy.s.sol
script or a module.
To upgrade, simply replace the separate systemFunctionName
and systemFunctionArguments
parameters with a single systemFunctionSignature
parameter.
world.registerFunctionSelector(
systemId,
- systemFunctionName,
- systemFunctionArguments,
+ string(abi.encodePacked(systemFunctionName, systemFunctionArguments))
);
ac508bf18: Renamed the default filename of generated user types from Types.sol
to common.sol
and the default filename of the generated table index file from Tables.sol
to index.sol
.
Both can be overridden via the MUD config:
export default mudConfig({
/** Filename where common user types will be generated and imported from. */
userTypesFilename: "common.sol",
/** Filename where codegen index will be generated. */
codegenIndexFilename: "index.sol",
});
Note: userTypesFilename
was renamed from userTypesPath
and .sol
is not appended automatically anymore but needs to be part of the provided filename.
To update your existing project, update all imports from Tables.sol
to index.sol
and all imports from Types.sol
to common.sol
, or override the defaults in your MUD config to the previous values.
- import { Counter } from "../src/codegen/Tables.sol";
+ import { Counter } from "../src/codegen/index.sol";
- import { ExampleEnum } from "../src/codegen/Types.sol";
+ import { ExampleEnum } from "../src/codegen/common.sol";
5e723b90e: - ResourceSelector
is replaced with ResourceId
, ResourceIdLib
, ResourceIdInstance
, WorldResourceIdLib
and WorldResourceIdInstance
.
Previously a "resource selector" was a bytes32
value with the first 16 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Now a "resource ID" is a bytes32
value with the first 2 bytes reserved for the resource type, the next 14 bytes reserved for the resource's namespace, and the last 16 bytes reserved for the resource's name.
Previously ResouceSelector
was a library and the resource selector type was a plain bytes32
.
Now ResourceId
is a user type, and the functionality is implemented in the ResourceIdInstance
(for type) and WorldResourceIdInstance
(for namespace and name) libraries.
We split the logic into two libraries, because Store
now also uses ResourceId
and needs to be aware of resource types, but not of namespaces/names.
- import { ResourceSelector } from "@latticexyz/world/src/ResourceSelector.sol";
+ import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol";
+ import { WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+ import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
- bytes32 systemId = ResourceSelector.from("namespace", "name");
+ ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, "namespace", "name");
- using ResourceSelector for bytes32;
+ using WorldResourceIdInstance for ResourceId;
+ using ResourceIdInstance for ResourceId;
systemId.getName();
systemId.getNamespace();
+ systemId.getType();
All Store
and World
methods now use the ResourceId
type for tableId
, systemId
, moduleId
and namespaceId
.
All mentions of resourceSelector
were renamed to resourceId
or the more specific type (e.g. tableId
, systemId
)
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IStore {
function setRecord(
- bytes32 tableId,
+ ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
FieldLayout fieldLayout
) external;
// Same for all other methods
}
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
IBaseWorld {
function callFrom(
address delegator,
- bytes32 resourceSelector,
+ ResourceId systemId,
bytes memory callData
) external payable returns (bytes memory);
// Same for all other methods
}
252a1852: Migrated to new config format.
645736df: Added an --rpcBatch
option to mud deploy
command to batch RPC calls for rate limited RPCs.
bdb46fe3a: Deploys now validate contract size before deploying and warns when a contract is over or close to the size limit (24kb). This should help identify the most common cause of "evm revert" errors during system and module contract deploys.
aabd30767: Bumped Solidity version to 0.8.24.
618dd0e89: WorldFactory
now expects a user-provided salt
when calling deployWorld(...)
(instead of the previous globally incrementing counter). This enables deterministic world addresses across different chains.
When using mud deploy
, you can provide a bytes32
hex-encoded salt using the --salt
option, otherwise it defaults to a random hex value.
ccc21e913: Added a --alwaysRunPostDeploy
flag to deploys (deploy
, test
, dev-contracts
commands) to always run PostDeploy.s.sol
script after each deploy. By default, PostDeploy.s.sol
is only run once after a new world is deployed.
This is helpful if you want to continue a deploy that may not have finished (due to an error or otherwise) or to run deploys with an idempotent PostDeploy.s.sol
script.
59d78c93b: Added a mud build
command that generates table libraries, system interfaces, and typed ABIs.
66cc35a8c: Create gas-report package, move gas-report cli command and GasReporter contract to it
92de59982: Bump Solidity version to 0.8.21
8025c3505: Added a new @latticexyz/abi-ts
package to generate TS type declaration files (.d.ts
) for each ABI JSON file.
This allows you to import your JSON ABI and use it directly with libraries like viem and abitype.
pnpm add @latticexyz/abi-ts
pnpm abi-ts
By default, abi-ts
looks for files with the glob **/*.abi.json
, but you can customize this glob with the --input
argument, e.g.
pnpm abi-ts --input 'abi/IWorld.sol/IWorld.abi.json'
e667ee808: CLI deploy
, test
, dev-contracts
no longer run forge clean
before each deploy. We previously cleaned to ensure no outdated artifacts were checked into git (ABIs, typechain types, etc.). Now that all artifacts are gitignored, we can let forge use its cache again.
5554b197: mud deploy
now supports public/linked libraries.
This helps with cases where system contracts would exceed the EVM bytecode size limit and logic would need to be split into many smaller systems.
Instead of the overhead and complexity of system-to-system calls, this logic can now be moved into public libraries that will be deployed alongside your systems and automatically delegatecall
ed.
c36ffd13c: - update the set-version
cli command to work with the new release process by adding two new options:
--tag
: install the latest version of the given tag. For snapshot releases tags correspond to the branch name, commits to main
result in an automatic snapshot release, so --tag main
is equivalent to what used to be -v canary
--commit
: install a version based on a given commit hash. Since commits from main
result in an automatic snapshot release it works for all commits on main, and it works for manual snapshot releases from branches other than mainset-version
now updates all package.json
nested below the current working directory (expect node_modules
), so no need for running it each workspace of a monorepo separately.Example:
pnpm mud set-version --tag main && pnpm install
pnpm mud set-version --commit db19ea39 && pnpm install
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
e1dc88ebe: Transactions sent via deploy will now be retried a few times before giving up. This hopefully helps with large deploys on some chains.
168a4cb43: Add support for legacy transactions in deploy script by falling back to gasPrice
if lastBaseFeePerGas
is not available
7ce82b6fc: Store config now defaults storeArgument: false
for all tables. This means that table libraries, by default, will no longer include the extra functions with the _store
argument. This default was changed to clear up the confusion around using table libraries in tests, PostDeploy
scripts, etc.
If you are sure you need to manually specify a store when interacting with tables, you can still manually toggle it back on with storeArgument: true
in the table settings of your MUD config.
If you want to use table libraries in PostDeploy.s.sol
, you can add the following lines:
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
+ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
+ StoreSwitch.setStoreAddress(worldAddress);
+
+ SomeTable.get(someKey);
a35c05ea9: Table libraries now hardcode the bytes32
table ID value rather than computing it in Solidity. This saves a bit of gas across all storage operations.
3bfee32cf: dev-contracts
will no longer bail when there was an issue with deploying (e.g. typo in contracts) and instead wait for file changes before retrying.
c32c8e8f2: Removes std-contracts
package. These were v1 contracts, now entirely replaced by our v2 tooling. See the MUD docs for building with v2 or create a new project from our v2 templates with pnpm create mud@next your-app-name
.
8f49c277d: Attempting to deploy multiple systems where there are overlapping system IDs now throws an error.
ce7125a1b: Removes solecs
package. These were v1 contracts, now entirely replaced by our v2 tooling. See the MUD docs for building with v2 or create a new project from our v2 templates with pnpm create mud@next your-app-name
.
854de0761: Using mud set-version --link
will no longer attempt to fetch the latest version from npm.
aea67c580: Include bytecode for World
and Store
in npm packages.
c07fa0215: Tables and interfaces in the world
package are now generated to the codegen
folder.
This is only a breaking change if you imported tables or codegenerated interfaces from @latticexyz/world
directly.
If you're using the MUD CLI, the changed import paths are already integrated and no further changes are necessary.
- import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol";
+ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
87235a21b: Fix table IDs for module install step of deploy
d5c0682fb: Updated all human-readable resource IDs to use {namespace}__{name}
for consistency with world function signatures.
257a0afc: Bumped typescript
to 5.4.2
, eslint
to 8.57.0
, and both @typescript-eslint/eslint-plugin
and @typescript-eslint/parser
to 7.1.1
.
4e2a170f9: Deploys now continue if they detect a Module_AlreadyInstalled
revert error.
211be2a1e: The FieldLayout
in table libraries is now generated at compile time instead of dynamically in a table library function.
This significantly reduces gas cost in all table library functions.
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
1feecf495: Added --worldAddress
argument to dev-contracts
CLI command so that you can develop against an existing world.
433078c54: Reverse PackedCounter encoding, to optimize gas for bitshifts.
Ints are right-aligned, shifting using an index is straightforward if they are indexed right-to-left.
61c6ab705: Changed deploy order so that system/module contracts are fully deployed before registering/installing them on the world.
e259ef79f: Generated contractComponents
now properly import World
as type
78a837167: Fixed registration of world signatures/selectors for namespaced systems. We changed these signatures in #2160, but missed updating part of the deploy step.
063daf80e: Previously registerSystem
and registerTable
had a side effect of registering namespaces if the system or table's namespace didn't exist yet.
This caused a possible frontrunning issue, where an attacker could detect a registerSystem
/registerTable
transaction in the mempool,
insert a registerNamespace
transaction before it, grant themselves access to the namespace, transfer ownership of the namespace to the victim,
so that the registerSystem
/registerTable
transactions still went through successfully.
To mitigate this issue, the side effect of registering a namespace in registerSystem
and registerTable
has been removed.
Calls to these functions now expect the respective namespace to exist and the caller to own the namespace, otherwise they revert.
Changes in consuming projects are only necessary if tables or systems are registered manually.
If only the MUD deployer is used to register tables and systems, no changes are necessary, as the MUD deployer has been updated accordingly.
+ world.registerNamespace(namespaceId);
world.registerSystem(systemId, system, true);
+ world.registerNamespace(namespaceId);
MyTable.register();
69d55ce32: Deploy commands (deploy
, dev-contracts
, test
) now correctly run worldgen
to generate system interfaces before deploying.
bd9cc8ec2: Refactor deploy
command to break up logic into modules
8d51a0348: Clean up Memory.sol, make mcopy pure
2699630c0: Deploys will now always rebuild IWorld.sol
interface (a workaround for https://github.com/foundry-rs/foundry/issues/6241)
48909d151: bump forge-std and ds-test dependencies
1d4039622: We fixed a bug in the deploy script that would cause the deployment to fail if a non-root namespace was used in the config.
4fe079309: Fixed a few issues with deploys:
21a626ae9: Changed mud
CLI import order so that environment variables from the .env
file are loaded before other imports.
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
db7798be2: Updated deployer with world's new InitModule
naming.
d844cd441: Sped up builds by using more of forge's cache.
Previously we'd build only what we needed because we would check in ABIs and other build artifacts into git, but that meant that we'd get a lot of forge cache misses. Now that we no longer need these files visible, we can take advantage of forge's caching and greatly speed up builds, especially incremental ones.
bfcb293d1: What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
55ab88a60: StoreCore
and IStore
now expose specific functions for getStaticField
and getDynamicField
in addition to the general getField
.
Using the specific functions reduces gas overhead because more optimized logic can be executed.
interface IStore {
/**
* Get a single static field from the given tableId and key tuple, with the given value field layout.
* Note: the field value is left-aligned in the returned bytes32, the rest of the word is not zeroed out.
* Consumers are expected to truncate the returned value as needed.
*/
function getStaticField(
bytes32 tableId,
bytes32[] calldata keyTuple,
uint8 fieldIndex,
FieldLayout fieldLayout
) external view returns (bytes32);
/**
* Get a single dynamic field from the given tableId and key tuple at the given dynamic field index.
* (Dynamic field index = field index - number of static fields)
*/
function getDynamicField(
bytes32 tableId,
bytes32[] memory keyTuple,
uint8 dynamicFieldIndex
) external view returns (bytes memory);
}
4e4a34150: bump to latest TS version (5.1.6)
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
83583a505: deploy
and dev-contracts
CLI commands now use forge build --skip test script
before deploying and run mud abi-ts
to generate strong types for ABIs.
60cfd089f: 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, 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-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 and let us know if we missed anything.
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)
In your supportedChains.ts
, replace foundry
chain with our new mudFoundry
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 viem chain
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
, replace setupMUDV2Network
with syncToRecs
.
- 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 of setupNetwork
.
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 new SyncProgress
:
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!
}
24a6cd536: Changed the userTypes
property to accept { filePath: string, internalType: SchemaAbiType }
to enable strong type inference from the config.
708b49c50: Generated table libraries now have a set of functions prefixed with _
that always use their own storage for read/write.
This saves gas for use cases where the functionality to dynamically determine which Store
to use for read/write is not needed, e.g. root systems in a World
, or when using Store
without World
.
We decided to continue to always generate a set of functions that dynamically decide which Store
to use, so that the generated table libraries can still be imported by non-root systems.
library Counter {
// Dynamically determine which store to write to based on the context
function set(uint32 value) internal;
// Always write to own storage
function _set(uint32 value) internal;
// ... equivalent functions for all other Store methods
}
25086be5f: Replaced temporary .mudtest
file in favor of WORLD_ADDRESS
environment variable when running tests with MudTest
contract
9c83adc01: Added a non-deterministic fallback for deploying to chains that have replay protection on and do not support pre-EIP-155 transactions (no chain ID).
If you're using mud deploy
and there's already a deterministic deployer on your target chain, you can provide the address with --deployerAddress 0x...
to still get some determinism.
c049c23f4: - The World
contract now has an initialize
function, which can be called once by the creator of the World to install the core module.
This change allows the registration of all core tables to happen in the CoreModule
, so no table metadata has to be included in the World
's bytecode.
interface IBaseWorld {
function initialize(IModule coreModule) public;
}
The World
contract now stores the original creator of the World
in an immutable state variable.
It is used internally to only allow the original creator to initialize the World
in a separate transaction.
interface IBaseWorld {
function creator() external view returns (address);
}
The deploy script is updated to use the World
's initialize
function to install the CoreModule
instead of registerRootModule
as before.
6c6733256: Add tableIdToHex
and hexToTableId
pure functions and move/deprecate TableId
.
251170e1e: All optional modules have been moved from @latticexyz/world
to @latticexyz/world-modules
.
If you're using the MUD CLI, the import is already updated and no changes are necessary.
c4f49240d: Table libraries now correctly handle uninitialized fixed length arrays.
afdba793f: 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
, or component.metadata.tableId
:
component.id
is now the on-chain bytes32
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 types3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
cea754dde: - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
d2f8e9400: Moved to new resource ID utils.
dc258e686: The mud test
cli now exits with code 1 on test failure. It used to exit with code 0, which meant that CIs didn't notice test failures.
Updated dependencies [7ce82b6fc]
Updated dependencies [d8c8f66bf]
Updated dependencies [c6c13f2ea]
Updated dependencies [77dce993a]
Updated dependencies [ce97426c0]
Updated dependencies [3236f799e]
Updated dependencies [1b86eac05]
Updated dependencies [a35c05ea9]
Updated dependencies [c9ee5e4a]
Updated dependencies [c963b46c7]
Updated dependencies [05b3e8882]
Updated dependencies [eaa766ef7]
Updated dependencies [52182f70d]
Updated dependencies [0f27afddb]
Updated dependencies [748f4588a]
Updated dependencies [865253dba]
Updated dependencies [8f49c277d]
Updated dependencies [7fa2ca183]
Updated dependencies [745485cda]
Updated dependencies [16b13ea8f]
Updated dependencies [aea67c580]
Updated dependencies [33f50f8a4]
Updated dependencies [82693072]
Updated dependencies [07dd6f32c]
Updated dependencies [c07fa0215]
Updated dependencies [90e4161bb]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [6ca1874e0]
Updated dependencies [331dbfdcb]
Updated dependencies [d5c0682fb]
Updated dependencies [1d60930d6]
Updated dependencies [01e46d99]
Updated dependencies [4be22ba4]
Updated dependencies [430e6b29a]
Updated dependencies [f9f9609ef]
Updated dependencies [904fd7d4e]
Updated dependencies [e6c03a87a]
Updated dependencies [1077c7f53]
Updated dependencies [2c920de7]
Updated dependencies [b98e51808]
Updated dependencies [b9e562d8f]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [759514d8b]
Updated dependencies [952cd5344]
Updated dependencies [d5094a242]
Updated dependencies [3fb9ce283]
Updated dependencies [c207d35e8]
Updated dependencies [db7798be2]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [3be4deecf]
Updated dependencies [a25881160]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [5debcca8]
Updated dependencies [80a26419f]
Updated dependencies [c4d5eb4e4]
Updated dependencies [f8dab7334]
Updated dependencies [1a0fa7974]
Updated dependencies [f62c767e7]
Updated dependencies [d00c4a9af]
Updated dependencies [d7325e517]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [de151fec0]
Updated dependencies [c32a9269a]
Updated dependencies [eb384bb0e]
Updated dependencies [37c228c63]
Updated dependencies [35348f831]
Updated dependencies [618dd0e89]
Updated dependencies [aacffcb59]
Updated dependencies [c991c71a]
Updated dependencies [ae340b2bf]
Updated dependencies [1bf2e9087]
Updated dependencies [e5d208e40]
Updated dependencies [b38c096d]
Updated dependencies [211be2a1e]
Updated dependencies [0f3e2e02b]
Updated dependencies [4bb7e8cbf]
Updated dependencies [1f80a0b52]
Updated dependencies [d08789282]
Updated dependencies [5c965a919]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e5a962bc3]
Updated dependencies [331f0d636]
Updated dependencies [f6f402896]
Updated dependencies [d5b73b126]
Updated dependencies [e34d1170]
Updated dependencies [08b422171]
Updated dependencies [b8a6158d6]
Updated dependencies [190fdd11]
Updated dependencies [c4fc85041]
Updated dependencies [37c228c63]
Updated dependencies [37c228c63]
Updated dependencies [433078c54]
Updated dependencies [2459e15fc]
Updated dependencies [db314a74]
Updated dependencies [b2d2aa715]
Updated dependencies [4c7fd3eb2]
Updated dependencies [a0341daf9]
Updated dependencies [ca50fef81]
Updated dependencies [83583a505]
Updated dependencies [5e723b90e]
Updated dependencies [6573e38e9]
Updated dependencies [51914d656]
Updated dependencies [063daf80e]
Updated dependencies [afaf2f5ff]
Updated dependencies [37c228c63]
Updated dependencies [9352648b1]
Updated dependencies [59267655]
Updated dependencies [37c228c63]
Updated dependencies [2bfee9217]
Updated dependencies [1ca35e9a1]
Updated dependencies [ca3291751]
Updated dependencies [ba17bdab5]
Updated dependencies [44a5432ac]
Updated dependencies [6e66c5b74]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [88b1a5a19]
Updated dependencies [65c9546c4]
Updated dependencies [48909d151]
Updated dependencies [7b28d32e5]
Updated dependencies [f8a01a047]
Updated dependencies [b02f9d0e4]
Updated dependencies [2ca75f9b9]
Updated dependencies [f62c767e7]
Updated dependencies [bb91edaa0]
Updated dependencies [590542030]
Updated dependencies [1a82c278]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [48c51b52a]
Updated dependencies [9f8b84e73]
Updated dependencies [a02da555b]
Updated dependencies [66cc35a8c]
Updated dependencies [672d05ca1]
Updated dependencies [f1cd43bf9]
Updated dependencies [9d0f492a9]
Updated dependencies [55a05fd7a]
Updated dependencies [f03531d97]
Updated dependencies [c583f3cd0]
Updated dependencies [31ffc9d5d]
Updated dependencies [5e723b90e]
Updated dependencies [63831a264]
Updated dependencies [b8a6158d6]
Updated dependencies [6db95ce15]
Updated dependencies [8193136a9]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [6ca1874e0]
Updated dependencies [a7b30c79b]
Updated dependencies [6470fe1fd]
Updated dependencies [86766ce1]
Updated dependencies [92de59982]
Updated dependencies [5741d53d0]
Updated dependencies [aee8020a6]
Updated dependencies [331f0d636]
Updated dependencies [22ee44700]
Updated dependencies [e2d089c6d]
Updated dependencies [ad4ac4459]
Updated dependencies [8025c3505]
Updated dependencies [be313068b]
Updated dependencies [ac508bf18]
Updated dependencies [836383734]
Updated dependencies [9ff4dd955]
Updated dependencies [93390d89]
Updated dependencies [4385c5a4c]
Updated dependencies [57d8965df]
Updated dependencies [18d3aea55]
Updated dependencies [7987c94d6]
Updated dependencies [bb91edaa0]
Updated dependencies [144c0d8d]
Updated dependencies [5ac4c97f4]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [1890f1a06]
Updated dependencies [90d0d79c]
Updated dependencies [e48171741]
Updated dependencies [e4a6189df]
Updated dependencies [9b43029c3]
Updated dependencies [37c228c63]
Updated dependencies [55ab88a60]
Updated dependencies [c58da9ad]
Updated dependencies [37c228c63]
Updated dependencies [747d8d1b8]
Updated dependencies [4e4a34150]
Updated dependencies [535229984]
Updated dependencies [af639a264]
Updated dependencies [5e723b90e]
Updated dependencies [99ab9cd6f]
Updated dependencies [086be4ef4]
Updated dependencies [be18b75b]
Updated dependencies [0c4f9fea9]
Updated dependencies [0d12db8c2]
Updated dependencies [c049c23f4]
Updated dependencies [80dd6992e]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [37c228c63]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [17f987209]
Updated dependencies [25086be5f]
Updated dependencies [37c228c63]
Updated dependencies [b1d41727d]
Updated dependencies [3ac68ade6]
Updated dependencies [c642ff3a0]
Updated dependencies [22ba7b675]
Updated dependencies [4c1dcd81e]
Updated dependencies [3042f86e]
Updated dependencies [c049c23f4]
Updated dependencies [9af542d3e]
Updated dependencies [5e71e1cb5]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [5c52bee09]
Updated dependencies [fdbba6d88]
Updated dependencies [251170e1e]
Updated dependencies [8025c3505]
Updated dependencies [c4f49240d]
Updated dependencies [745485cda]
Updated dependencies [95f64c85]
Updated dependencies [37c228c63]
Updated dependencies [3e7d83d0]
Updated dependencies [5df1f31bc]
Updated dependencies [a2f41ade9]
Updated dependencies [29c3f5087]
Updated dependencies [cea754dde]
Updated dependencies [5e71e1cb5]
Updated dependencies [331f0d636]
Updated dependencies [95c59b203]
Updated dependencies [cc2c8da00]
Updated dependencies [252a1852]
Updated dependencies [103f635eb]
Published by github-actions[bot] 7 months ago
getLogs
function now that viem's getLogs
supports using multiple events
per RPC call.
isNonPendingBlock
and isNonPendingLog
helpers now that viem narrows Block
and Log
types based on inputsgroupLogsByBlockNumber
types and testseeb15cc06: - Replace blockEventsToStorage
with blockLogsToStorage
that exposes a storeOperations
callback to perform database writes from store operations. This helps encapsulates database adapters into a single wrapper/instance of blockLogsToStorage
and allows for wrapping a block of store operations in a database transaction.
toBlock
option to groupLogsByBlockNumber
and remove blockHash
from results. This helps track the last block number for a given set of logs when used in the context of RxJS streams.72b806979: Add block logs stream package
import { filter, map, mergeMap } from "rxjs";
import { createPublicClient, parseAbi } from "viem";
import {
createBlockStream,
isNonPendingBlock,
groupLogsByBlockNumber,
blockRangeToLogs,
} from "@latticexyz/block-logs-stream";
const publicClient = createPublicClient({
// your viem public client config here
});
const latestBlock$ = await createBlockStream({
publicClient,
blockTag: "latest",
});
const latestBlockNumber$ = latestBlock$.pipe(
filter(isNonPendingBlock),
map((block) => block.number),
);
latestBlockNumber$
.pipe(
map((latestBlockNumber) => ({
startBlock: 0n,
endBlock: latestBlockNumber,
})),
blockRangeToLogs({
publicClient,
address,
events: parseAbi([
"event StoreDeleteRecord(bytes32 table, bytes32[] key)",
"event StoreSetField(bytes32 table, bytes32[] key, uint8 schemaIndex, bytes data)",
"event StoreSetRecord(bytes32 table, bytes32[] key, bytes data)",
"event StoreEphemeralRecord(bytes32 table, bytes32[] key, bytes data)",
]),
}),
mergeMap(({ logs }) => from(groupLogsByBlockNumber(logs))),
)
.subscribe((block) => {
console.log("got events for block", block);
});
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
904fd7d4e: Add store sync package
f99e88987: Bump viem to 1.14.0 and abitype to 0.9.8
6573e38e9: Renamed all occurrences of table
where it is used as "table ID" to tableId
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
event StoreSetField(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key
);
event StoreEphemeralRecord(
- bytes32 table,
+ bytes32 tableId,
bytes32[] key,
bytes data
);
6e66c5b74: Renamed all occurrences of key
where it is used as "key tuple" to keyTuple
.
This is only a breaking change for consumers who manually decode Store
events, but not for consumers who use the MUD libraries.
event StoreSetRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
event StoreSetField(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
uint8 fieldIndex,
bytes data
);
event StoreDeleteRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
);
event StoreEphemeralRecord(
bytes32 tableId,
- bytes32[] key,
+ bytes32[] keyTuple,
bytes data
);
590542030: TS packages now generate their respective .d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.
b8a6158d6: bump viem to 1.6.0
5d737cf2e: Updated the debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.
bfcb293d1: What used to be known as ephemeral
table is now called offchain
table.
The previous ephemeral
tables only supported an emitEphemeral
method, which emitted a StoreSetEphemeralRecord
event.
Now offchain
tables support all regular table methods, except partial operations on dynamic fields (push
, pop
, update
).
Unlike regular tables they don't store data on-chain but emit the same events as regular tables (StoreSetRecord
, StoreSpliceStaticData
, StoreDeleteRecord
), so their data can be indexed by offchain indexers/clients.
- EphemeralTable.emitEphemeral(value);
+ OffchainTable.set(value);
535229984: - bump to viem 1.3.0 and abitype 0.9.3
@wagmi/chains
imports to viem/chains
af639a264: Store
events have been renamed for consistency and readability.
If you're parsing Store
events manually, you need to update your ABI.
If you're using the MUD sync stack, the new events are already integrated and no further changes are necessary.
- event StoreSetRecord(
+ event Store_SetRecord(
ResourceId indexed tableId,
bytes32[] keyTuple,
bytes staticData,
bytes32 encodedLengths,
bytes dynamicData
);
- event StoreSpliceStaticData(
+ event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data
);
- event StoreSpliceDynamicData(
+ event Store_SpliceDynamicData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
uint40 deleteCount,
bytes data,
bytes32 encodedLengths
);
- event StoreDeleteRecord(
+ event Store_DeleteRecord(
ResourceId indexed tableId,
bytes32[] keyTuple
);
cea754dde: - The external setRecord
and deleteRecord
methods of IStore
no longer accept a FieldLayout
as input, but load it from storage instead.
This is to prevent invalid FieldLayout
values being passed, which could cause the onchain state to diverge from the indexer state.
However, the internal StoreCore
library still exposes a setRecord
and deleteRecord
method that allows a FieldLayout
to be passed.
This is because StoreCore
can only be used internally, so the FieldLayout
value can be trusted and we can save the gas for accessing storage.
interface IStore {
function setRecord(
ResourceId tableId,
bytes32[] calldata keyTuple,
bytes calldata staticData,
PackedCounter encodedLengths,
bytes calldata dynamicData,
- FieldLayout fieldLayout
) external;
function deleteRecord(
ResourceId tableId,
bytes32[] memory keyTuple,
- FieldLayout fieldLayout
) external;
}
The spliceStaticData
method and Store_SpliceStaticData
event of IStore
and StoreCore
no longer include deleteCount
in their signature.
This is because when splicing static data, the data after start
is always overwritten with data
instead of being shifted, so deleteCount
is always the length of the data to be written.
event Store_SpliceStaticData(
ResourceId indexed tableId,
bytes32[] keyTuple,
uint48 start,
- uint40 deleteCount,
bytes data
);
interface IStore {
function spliceStaticData(
ResourceId tableId,
bytes32[] calldata keyTuple,
uint48 start,
- uint40 deleteCount,
bytes calldata data
) external;
}
The updateInField
method has been removed from IStore
, as it's almost identical to the more general spliceDynamicData
.
If you're manually calling updateInField
, here is how to upgrade to spliceDynamicData
:
- store.updateInField(tableId, keyTuple, fieldIndex, startByteIndex, dataToSet, fieldLayout);
+ uint8 dynamicFieldIndex = fieldIndex - fieldLayout.numStaticFields();
+ store.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, uint40(startByteIndex), uint40(dataToSet.length), dataToSet);
All other methods that are only valid for dynamic fields (pushToField
, popFromField
, getFieldSlice
)
have been renamed to make this more explicit (pushToDynamicField
, popFromDynamicField
, getDynamicFieldSlice
).
Their fieldIndex
parameter has been replaced by a dynamicFieldIndex
parameter, which is the index relative to the first dynamic field (i.e. dynamicFieldIndex
= fieldIndex
- numStaticFields
).
The FieldLayout
parameter has been removed, as it was only used to calculate the dynamicFieldIndex
in the method.
interface IStore {
- function pushToField(
+ function pushToDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
bytes calldata dataToPush,
- FieldLayout fieldLayout
) external;
- function popFromField(
+ function popFromDynamicField(
ResourceId tableId,
bytes32[] calldata keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
uint256 byteLengthToPop,
- FieldLayout fieldLayout
) external;
- function getFieldSlice(
+ function getDynamicFieldSlice(
ResourceId tableId,
bytes32[] memory keyTuple,
- uint8 fieldIndex,
+ uint8 dynamicFieldIndex,
- FieldLayout fieldLayout,
uint256 start,
uint256 end
) external view returns (bytes memory data);
}
IStore
has a new getDynamicFieldLength
length method, which returns the byte length of the given dynamic field and doesn't require the FieldLayout
.
IStore {
+ function getDynamicFieldLength(
+ ResourceId tableId,
+ bytes32[] memory keyTuple,
+ uint8 dynamicFieldIndex
+ ) external view returns (uint256);
}
IStore
now has additional overloads for getRecord
, getField
, getFieldLength
and setField
that don't require a FieldLength
to be passed, but instead load it from storage.
IStore
now exposes setStaticField
and setDynamicField
to save gas by avoiding the dynamic inference of whether the field is static or dynamic.
The getDynamicFieldSlice
method no longer accepts reading outside the bounds of the dynamic field.
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero.
Updated dependencies [a35c05ea9]
Updated dependencies [16b13ea8f]
Updated dependencies [82693072]
Updated dependencies [aabd30767]
Updated dependencies [65c9546c4]
Updated dependencies [d5c0682fb]
Updated dependencies [01e46d99]
Updated dependencies [331dbfdcb]
Updated dependencies [44236041f]
Updated dependencies [066056154]
Updated dependencies [3fb9ce283]
Updated dependencies [bb6ada740]
Updated dependencies [35c9f33df]
Updated dependencies [0b8ce3f2c]
Updated dependencies [933b54b5f]
Updated dependencies [307abab3]
Updated dependencies [aacffcb59]
Updated dependencies [f99e88987]
Updated dependencies [939916bcd]
Updated dependencies [e34d1170]
Updated dependencies [b8a6158d6]
Updated dependencies [db314a74]
Updated dependencies [59267655]
Updated dependencies [8d51a0348]
Updated dependencies [c162ad5a5]
Updated dependencies [f62c767e7]
Updated dependencies [590542030]
Updated dependencies [1b5eb0d07]
Updated dependencies [44a5432ac]
Updated dependencies [b8a6158d6]
Updated dependencies [5d737cf2e]
Updated dependencies [d075f82f3]
Updated dependencies [331dbfdcb]
Updated dependencies [92de59982]
Updated dependencies [bfcb293d1]
Updated dependencies [3e057061d]
Updated dependencies [535229984]
Updated dependencies [5e723b90e]
Updated dependencies [0c4f9fea9]
Updated dependencies [60cfd089f]
Updated dependencies [24a6cd536]
Updated dependencies [708b49c50]
Updated dependencies [d2f8e9400]
Updated dependencies [25086be5f]
Updated dependencies [b1d41727d]
Updated dependencies [4c1dcd81e]
Updated dependencies [6071163f7]
Updated dependencies [6c6733256]
Updated dependencies [cd5abcc3b]
Updated dependencies [d7b1c588a]
Updated dependencies [c4f49240d]
Updated dependencies [5df1f31bc]
Updated dependencies [cea754dde]
Updated dependencies [331f0d636]
Updated dependencies [cc2c8da00]
Published by github-actions[bot] 7 months ago
ca3291751: Moves log output behind a debug flag. You can enable logging with DEBUG=abi-ts
environment variable.
8025c3505: Added a new @latticexyz/abi-ts
package to generate TS type declaration files (.d.ts
) for each ABI JSON file.
This allows you to import your JSON ABI and use it directly with libraries like viem and abitype.
pnpm add @latticexyz/abi-ts
pnpm abi-ts
By default, abi-ts
looks for files with the glob **/*.abi.json
, but you can customize this glob with the --input
argument, e.g.
pnpm abi-ts --input 'abi/IWorld.sol/IWorld.abi.json'
glob
handle resolving the glob against the current working directory..d.ts
type definition files for better compatibility when using MUD with moduleResolution
set to bundler
or node16
and fixes issues around missing type declarations for dependent packages.debug
util to pipe to stdout
and added an additional util to explicitly pipe to stderr
when needed.Published by github-actions[bot] 7 months ago
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
4be22ba4: ERC20 and ERC721 implementations now always register the token namespace, instead of checking if it has already been registered. This prevents issues with registering the namespace beforehand, namely that only the owner of a system can create a puppet for it.
44236041: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
Updated dependencies [c9ee5e4a]
Updated dependencies [8f49c277d]
Updated dependencies [82693072]
Updated dependencies [d5c0682fb]
Updated dependencies [01e46d99]
Updated dependencies [2c920de7]
Updated dependencies [44236041]
Updated dependencies [3be4deecf]
Updated dependencies [5debcca8]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [c991c71a]
Updated dependencies [b38c096d]
Updated dependencies [e34d1170]
Updated dependencies [190fdd11]
Updated dependencies [db314a74]
Updated dependencies [59267655]
Updated dependencies [1a82c278]
Updated dependencies [8193136a9]
Updated dependencies [86766ce1]
Updated dependencies [93390d89]
Updated dependencies [144c0d8d]
Updated dependencies [c58da9ad]
Updated dependencies [be18b75b]
Updated dependencies [3042f86e]
Updated dependencies [d7b1c588a]
Updated dependencies [95f64c85]
Updated dependencies [3e7d83d0]
Updated dependencies [252a1852]
Published by github-actions[bot] 7 months ago
c9ee5e4a: Store and World configs have been rebuilt with strong types. The shape of these configs have also changed slightly for clarity, the biggest change of which is merging of keySchema
and valueSchema
into a single schema
with a separate key
for a table's primary key.
To migrate, first update the imported config method:
-import { mudConfig } from "@latticexyz/world/register";
+import { defineWorld } from "@latticexyz/world";
-export default mudConfig({
+export default defineWorld({
Note that if you are only using Store, you will need to import defineStore
from @latticexyz/store
.
Then migrate the table key by renaming keySchema
to schema
and define the table key
with each field name from your key schema:
export default defineWorld({
tables: {
Position: {
- keySchema: {
+ schema: {
player: "address",
},
valueSchema: {
x: "int32",
y: "int32",
},
+ key: ['player'],
},
},
});
Now we can merge the valueSchema
into schema
.
export default defineWorld({
tables: {
Position: {
schema: {
player: "address",
- },
- valueSchema: {
x: "int32",
y: "int32",
},
key: ['player'],
},
},
});
If you previously used the table config shorthand without the full keySchema
and valueSchema
, some of the defaults have changed. Shorthands now use an id: "bytes32"
field by default rather than key: "bytes32"
and corresponding key: ["id"]
. To keep previous behavior, you may have to manually define your schema
with the previous key
and value
fields.
export default defineWorld({
tables: {
- OwnedBy: "address",
+ OwnedBy: {
+ schema: {
+ key: "bytes32",
+ value: "address",
+ },
+ key: ["key"],
+ },
},
});
Singleton tables are defined similarly, where an empty key
rather than keySchema
is provided:
-keySchema: {}
+key: []
Offchain tables are now defined as a table type
instead an offchainOnly
boolean:
-offchainOnly: true
+type: 'offchainTable'
All codegen options have moved under codegen
:
export default defineWorld({
- codegenDirectory: "…",
+ codegen: {
+ outputDirectory: "…",
+ },
tables: {
Position: {
schema: {
player: "address",
x: "int32",
y: "int32",
},
key: ['player'],
- directory: "…",
- dataStruct: false,
+ codegen: {
+ outputDirectory: "…",
+ dataStruct: false,
+ },
},
},
});
9aa5e786: Set the protocol version to 2.0.0
for each Store and World.
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
252a1852: Migrated to new config format.
5debcca8: registerRootFunctionSelector
now expects a systemFunctionSignature
instead of a systemFunctionSelector
. Internally, we compute the selector from the signature. This allows us to track system function signatures that are registered at the root so we can later generate ABIs for these systems.
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
8f49c277d: Attempting to deploy multiple systems where there are overlapping system IDs now throws an error.
44236041: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
3be4deecf: Added salt to the WorldDeployed
event.
1a82c278: Added system signatures to the FunctionSignatures
table, so they can be used to generate system ABIs and decode system calls made via the world.
86766ce1: Created an IWorldEvents
interface with HelloStore
, so all World events are defined in a single interface.
93390d89: Added an abstract
StoreKernel
contract, which includes all Store interfaces except for registration, and implements write methods, protocolVersion
and initializes StoreCore
. Store
extends StoreKernel
with the IStoreRegistration
interface. StoreData
is removed as a separate interface/contract. World
now extends StoreKernel
(since the registration methods are added via the InitModule
).
be18b75b: IWorldKernel
now inherits IModuleErrors
so it can render the correct errors if the World reverts when delegatecalled with Module code.
95f64c85: Renamed the functionSelector
key in the FunctionSelectors
table to worldFunctionSelector
. This clarifies that FunctionSelectors
is for world function selectors and can be used to generate the world ABI.
Updated dependencies [c9ee5e4a]
Updated dependencies [82693072]
Updated dependencies [d5c0682fb]
Updated dependencies [01e46d99]
Updated dependencies [2c920de7]
Updated dependencies [44236041]
Updated dependencies [9aa5e786]
Updated dependencies [307abab3]
Updated dependencies [c991c71a]
Updated dependencies [b38c096d]
Updated dependencies [e34d1170]
Updated dependencies [190fdd11]
Updated dependencies [db314a74]
Updated dependencies [59267655]
Updated dependencies [8193136a9]
Updated dependencies [93390d89]
Updated dependencies [144c0d8d]
Updated dependencies [c58da9ad]
Updated dependencies [3042f86e]
Updated dependencies [d7b1c588a]
Updated dependencies [3e7d83d0]
Updated dependencies [252a1852]
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
8193136a9: Added dynamicFieldIndex
to the Store_SpliceDynamicData
event. This enables indexers to store dynamic data as a blob per dynamic field without a schema lookup.
adc68225: PostgreSQL sync/indexer now uses {storeAddress}
for its database schema names and {namespace}__{tableName}
for its database table names (or just {tableName}
for root namespace), to be more consistent with the rest of the MUD codebase.
For namespaced tables:
- SELECT * FROM 0xfff__some_ns.some_table
+ SELECT * FROM 0xfff.some_ns__some_table
For root tables:
- SELECT * FROM 0xfff__.some_table
+ SELECT * FROM 0xfff.some_table
SQLite sync/indexer now uses snake case for its table names and column names for easier writing of queries and to better match PostgreSQL sync/indexer naming.
- SELECT * FROM 0xfFf__someNS__someTable
+ SELECT * FROM 0xfff__some_ns__some_table
252a1852: Migrated to new config format.
3622e39dd: Added a followBlockTag
option to configure which block number to follow when running createStoreSync
. It defaults to latest
(current behavior), which is recommended for individual clients so that you always have the latest chain state.
Indexers now default to safe
to avoid issues with reorgs and load-balanced RPCs being out of sync. This means indexers will be slightly behind the latest block number, but clients can quickly catch up. Indexers can override this setting using FOLLOW_BLOCK_TAG
environment variable.
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
{namespace}__{name}
for consistency with world function signatures.Published by github-actions[bot] 7 months ago
adc68225: PostgreSQL sync/indexer now uses {storeAddress}
for its database schema names and {namespace}__{tableName}
for its database table names (or just {tableName}
for root namespace), to be more consistent with the rest of the MUD codebase.
For namespaced tables:
- SELECT * FROM 0xfff__some_ns.some_table
+ SELECT * FROM 0xfff.some_ns__some_table
For root tables:
- SELECT * FROM 0xfff__.some_table
+ SELECT * FROM 0xfff.some_table
SQLite sync/indexer now uses snake case for its table names and column names for easier writing of queries and to better match PostgreSQL sync/indexer naming.
- SELECT * FROM 0xfFf__someNS__someTable
+ SELECT * FROM 0xfff__some_ns__some_table
3622e39dd: Added a followBlockTag
option to configure which block number to follow when running createStoreSync
. It defaults to latest
(current behavior), which is recommended for individual clients so that you always have the latest chain state.
Indexers now default to safe
to avoid issues with reorgs and load-balanced RPCs being out of sync. This means indexers will be slightly behind the latest block number, but clients can quickly catch up. Indexers can override this setting using FOLLOW_BLOCK_TAG
environment variable.
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
Published by github-actions[bot] 7 months ago
c9ee5e4a: Store and World configs have been rebuilt with strong types. The shape of these configs have also changed slightly for clarity, the biggest change of which is merging of keySchema
and valueSchema
into a single schema
with a separate key
for a table's primary key.
To migrate, first update the imported config method:
-import { mudConfig } from "@latticexyz/world/register";
+import { defineWorld } from "@latticexyz/world";
-export default mudConfig({
+export default defineWorld({
Note that if you are only using Store, you will need to import defineStore
from @latticexyz/store
.
Then migrate the table key by renaming keySchema
to schema
and define the table key
with each field name from your key schema:
export default defineWorld({
tables: {
Position: {
- keySchema: {
+ schema: {
player: "address",
},
valueSchema: {
x: "int32",
y: "int32",
},
+ key: ['player'],
},
},
});
Now we can merge the valueSchema
into schema
.
export default defineWorld({
tables: {
Position: {
schema: {
player: "address",
- },
- valueSchema: {
x: "int32",
y: "int32",
},
key: ['player'],
},
},
});
If you previously used the table config shorthand without the full keySchema
and valueSchema
, some of the defaults have changed. Shorthands now use an id: "bytes32"
field by default rather than key: "bytes32"
and corresponding key: ["id"]
. To keep previous behavior, you may have to manually define your schema
with the previous key
and value
fields.
export default defineWorld({
tables: {
- OwnedBy: "address",
+ OwnedBy: {
+ schema: {
+ key: "bytes32",
+ value: "address",
+ },
+ key: ["key"],
+ },
},
});
Singleton tables are defined similarly, where an empty key
rather than keySchema
is provided:
-keySchema: {}
+key: []
Offchain tables are now defined as a table type
instead an offchainOnly
boolean:
-offchainOnly: true
+type: 'offchainTable'
All codegen options have moved under codegen
:
export default defineWorld({
- codegenDirectory: "…",
+ codegen: {
+ outputDirectory: "…",
+ },
tables: {
Position: {
schema: {
player: "address",
x: "int32",
y: "int32",
},
key: ['player'],
- directory: "…",
- dataStruct: false,
+ codegen: {
+ outputDirectory: "…",
+ dataStruct: false,
+ },
},
},
});
9aa5e786: Set the protocol version to 2.0.0
for each Store and World.
8193136a9: Added dynamicFieldIndex
to the Store_SpliceDynamicData
event. This enables indexers to store dynamic data as a blob per dynamic field without a schema lookup.
3e7d83d0: Renamed PackedCounter
to EncodedLengths
for consistency.
252a1852: Migrated to new config format.
93390d89: Added an abstract
StoreKernel
contract, which includes all Store interfaces except for registration, and implements write methods, protocolVersion
and initializes StoreCore
. Store
extends StoreKernel
with the IStoreRegistration
interface. StoreData
is removed as a separate interface/contract. World
now extends StoreKernel
(since the registration methods are added via the InitModule
).
144c0d8d: Replaced the static array length getters in table libraries with constants.
3042f86e: Moved key schema and value schema methods to constants in code-generated table libraries for less bytecode and less gas in register/install methods.
-console.log(SomeTable.getKeySchema());
+console.log(SomeTable._keySchema);
-console.log(SomeTable.getValueSchema());
+console.log(SomeTable._valueSchema);
d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
2c920de7: Refactored StoreCore
to import IStoreEvents
instead of defining the events twice.
44236041: Moved table ID and field layout constants in code-generated table libraries from the file level into the library, for clearer access and cleaner imports.
-import { SomeTable, SomeTableTableId } from "./codegen/tables/SomeTable.sol";
+import { SomeTable } from "./codegen/tables/SomeTable.sol";
-console.log(SomeTableTableId);
+console.log(SomeTable._tableId);
-console.log(SomeTable.getFieldLayout());
+console.log(SomeTable._fieldLayout);
c991c71a: Added interfaces for all errors that are used by StoreCore
, which includes FieldLayout
, PackedCounter
, Schema
, and Slice
. This interfaces are inherited by IStore
, ensuring that all possible errors are included in the IStore
ABI for proper decoding in the frontend.
190fdd11: Restored Bytes.sliceN
helpers that were previously (mistakenly) removed and renamed them to Bytes.getBytesN
.
If you're upgrading an existing MUD project, you can rerun codegen with mud build
to update your table libraries to the new function names.
c58da9ad: Moved the HelloStore
to IStoreEvents
so all Store events are defined in the same interface.
Updated dependencies [82693072]
Updated dependencies [d5c0682fb]
Updated dependencies [01e46d99]
Updated dependencies [44236041]
Updated dependencies [307abab3]
Updated dependencies [b38c096d]
Updated dependencies [e34d1170]
Updated dependencies [db314a74]
Updated dependencies [59267655]
Updated dependencies [d7b1c588a]
Updated dependencies [3e7d83d0]
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
Published by github-actions[bot] 7 months ago
/internal
import path to indicate that these are now internal-only and deprecated. We'll be replacing these types and functions with new ones that are compatible with our new, strongly-typed config.d7b1c588a: Upgraded all packages and templates to viem v2.7.12 and abitype v1.0.0.
Some viem APIs have changed and we've updated getContract
to reflect those changes and keep it aligned with viem. It's one small code change:
const worldContract = getContract({
address: worldAddress,
abi: IWorldAbi,
- publicClient,
- walletClient,
+ client: { public: publicClient, wallet: walletClient },
});
Published by github-actions[bot] 7 months ago