From 331f0d636f6f327824307570a63fb301d9b897d1 Mon Sep 17 00:00:00 2001 From: alvarius Date: Wed, 16 Aug 2023 10:54:39 +0200 Subject: [PATCH] refactor: remove v1 network package, remove snap sync module, deprecate std-client (#1311) --- .changeset/late-geese-guess.md | 7 + .changeset/seven-flies-chew.md | 11 + .changeset/wild-gorillas-care.md | 28 + docs/pages/world/modules.mdx | 34 +- docs/pages/world/snap-sync.mdx | 144 ---- e2e/packages/client-vanilla/package.json | 2 - .../src/mud/getNetworkConfig.ts | 4 +- .../client-vanilla/src/mud/setupNetwork.ts | 2 +- e2e/packages/sync-test/package.json | 1 - e2e/pnpm-lock.yaml | 9 - .../packages/client-phaser/package.json | 2 - .../client-phaser/src/mud/getNetworkConfig.ts | 4 +- .../client-phaser/src/mud/setupNetwork.ts | 2 +- .../packages/client-react/package.json | 2 - .../client-react/src/mud/getNetworkConfig.ts | 4 +- .../client-react/src/mud/setupNetwork.ts | 2 +- .../packages/client-vanilla/package.json | 2 - .../src/mud/getNetworkConfig.ts | 4 +- .../client-vanilla/src/mud/setupNetwork.ts | 2 +- .../minimal/packages/contracts/mud.config.ts | 9 - examples/minimal/pnpm-lock.yaml | 18 - package.json | 2 +- packages/cli/src/utils/deploy.ts | 7 - packages/common/src/getBurnerPrivateKey.ts | 26 + packages/common/src/index.ts | 1 + packages/ecs-browser/src/ComponentEditor.tsx | 2 +- packages/ecs-browser/src/hooks.ts | 2 +- packages/network/.gitignore | 4 - packages/network/.hg | 2 - packages/network/.npmignore | 7 - packages/network/CHANGELOG.md | 669 ------------------ packages/network/README.md | 177 ----- packages/network/jest.config.js | 23 - packages/network/jest.setup.ts | 9 - packages/network/package.json | 68 -- .../network/src/createBlockNumberStream.ts | 66 -- packages/network/src/createClock.spec.ts | 70 -- packages/network/src/createClock.ts | 45 -- packages/network/src/createContracts.ts | 44 -- packages/network/src/createDecoder.spec.ts | 51 -- packages/network/src/createDecoder.ts | 98 --- packages/network/src/createEncoder.ts | 26 - packages/network/src/createFastTxExecutor.ts | 150 ---- packages/network/src/createNetwork.spec.ts | 101 --- packages/network/src/createNetwork.ts | 122 ---- packages/network/src/createProvider.ts | 143 ---- packages/network/src/createRelayStream.ts | 84 --- packages/network/src/createSigner.ts | 6 - packages/network/src/createSyncWorker.ts | 56 -- packages/network/src/createSystemExecutor.ts | 94 --- packages/network/src/createTopics.ts | 32 - packages/network/src/createTxQueue.ts | 358 ---------- packages/network/src/debug.ts | 3 - packages/network/src/dev/index.ts | 3 - packages/network/src/dev/observables.ts | 38 - packages/network/src/index.ts | 21 - packages/network/src/initCache.ts | 174 ----- packages/network/src/networkUtils.ts | 238 ------- packages/network/src/provider.ts | 28 - packages/network/src/types.ts | 297 -------- packages/network/src/utils.ts | 7 - packages/network/src/v2/README.md | 21 - packages/network/src/v2/common.ts | 27 - .../network/src/v2/decodeStoreSetField.ts | 82 --- .../network/src/v2/decodeStoreSetRecord.ts | 77 -- packages/network/src/v2/ecsEventFromLog.ts | 166 ----- packages/network/src/v2/fetchStoreEvents.ts | 48 -- .../network/src/v2/keyTupleToEntityID.spec.ts | 23 - packages/network/src/v2/keyTupleToEntityID.ts | 13 - .../network/src/v2/mode/createModeClient.ts | 11 - .../v2/mode/getBlockNumberFromModeTable.ts | 9 - .../network/src/v2/mode/getModeBlockNumber.ts | 18 - .../network/src/v2/mode/syncTablesFromMode.ts | 101 --- .../network/src/v2/schemas/decodeData.spec.ts | 20 - packages/network/src/v2/schemas/decodeData.ts | 79 --- .../src/v2/schemas/decodeDynamicField.ts | 37 - .../network/src/v2/schemas/decodeField.ts | 27 - .../network/src/v2/schemas/decodeKeyTuple.ts | 11 - .../network/src/v2/schemas/decodeSchema.ts | 45 -- .../src/v2/schemas/decodeStaticField.spec.ts | 83 --- .../src/v2/schemas/decodeStaticField.ts | 137 ---- .../network/src/v2/schemas/decodeValue.ts | 116 --- .../network/src/v2/schemas/tableMetadata.ts | 84 --- .../network/src/v2/schemas/tableSchemas.ts | 64 -- .../src/v2/snapSync/getSnapSyncRecords.ts | 48 -- packages/network/src/v2/snapSync/index.ts | 1 - .../src/v2/snapSync/snapSyncSystemAbi.ts | 69 -- packages/network/src/v2/syncUtils.ts | 76 -- .../src/v2/transformTableRecordsIntoEvents.ts | 44 -- .../network/src/workers/CacheStore.spec.ts | 386 ---------- packages/network/src/workers/CacheStore.ts | 210 ------ .../network/src/workers/Recover.worker.ts | 10 - packages/network/src/workers/Sync.worker.ts | 4 - .../network/src/workers/SyncWorker.spec.ts | 425 ----------- packages/network/src/workers/SyncWorker.ts | 304 -------- packages/network/src/workers/constants.ts | 13 - packages/network/src/workers/debug.ts | 3 - packages/network/src/workers/index.ts | 3 - packages/network/tsconfig.json | 17 - packages/network/tsup.config.ts | 11 - packages/react/package.json | 1 + packages/services/package.json | 3 +- .../ts/faucet}/createFaucetService.ts | 2 +- packages/services/ts/faucet/index.ts | 2 + packages/services/tsup.config.ts | 2 +- packages/std-client/package.json | 11 +- .../components/ActionComponent.ts | 0 .../components/BoolComponent.ts | 0 .../components/CoordComponent.ts | 0 .../components/DevHighlight.ts | 0 .../components/NumberComponent.ts | 0 .../components/StringComponent.ts | 0 .../components/VoxelCoordComponent.ts | 0 .../src/{ => deprecated}/components/index.ts | 0 .../src/{ => deprecated}/getBurnerWallet.ts | 3 + .../std-client/src/{ => deprecated}/hooks.ts | 0 .../std-client/src/{ => deprecated}/index.ts | 1 - .../systems/ActionSystem/constants.ts | 0 .../ActionSystem/createActionSystem.spec.ts | 0 .../ActionSystem/createActionSystem.ts | 0 .../systems/ActionSystem/index.ts | 0 .../systems/ActionSystem/types.ts | 0 .../systems/ActionSystem/utils/index.ts | 0 .../utils/waitForActionCompletion.ts | 0 .../src/{ => deprecated}/systems/index.ts | 0 packages/std-client/src/deprecated/utils.ts | 113 +++ packages/std-client/src/dev/index.ts | 1 - packages/std-client/src/dev/observables.ts | 4 - packages/std-client/src/setup/index.ts | 3 - .../std-client/src/setup/setupMUDV2Network.ts | 204 ------ packages/std-client/src/setup/types.ts | 43 -- packages/std-client/src/setup/utils.ts | 233 ------ packages/std-client/src/utils.ts | 256 ------- packages/std-client/tsup.config.ts | 2 +- .../SnapSyncModule.abi.json | 39 - .../SnapSyncSystem.abi.json | 104 --- packages/world/gas-report.json | 12 - packages/world/mud.config.ts | 2 - packages/world/package.json | 4 - .../src/modules/snapsync/SnapSyncModule.sol | 36 - .../src/modules/snapsync/SnapSyncSystem.sol | 51 -- .../world/src/modules/snapsync/SyncRecord.sol | 8 - .../world/src/modules/snapsync/constants.sol | 7 - packages/world/test/SnapSyncModule.t.sol | 222 ------ .../ts/plugins/snapsync/configExtensions.ts | 36 - packages/world/ts/plugins/snapsync/index.ts | 7 - packages/world/ts/plugins/snapsync/plugin.ts | 7 - .../ts/plugins/snapsync/typeExtensions.ts | 12 - packages/world/tsup.config.ts | 8 +- pnpm-lock.yaml | 379 ++-------- templates/phaser/packages/client/package.json | 3 +- .../client/src/mud/getNetworkConfig.ts | 4 +- .../packages/client/src/mud/setupNetwork.ts | 2 +- templates/react/packages/client/package.json | 3 +- .../client/src/mud/getNetworkConfig.ts | 4 +- .../packages/client/src/mud/setupNetwork.ts | 2 +- .../threejs/packages/client/package.json | 3 +- .../client/src/mud/getNetworkConfig.ts | 4 +- .../packages/client/src/mud/setupNetwork.ts | 2 +- .../vanilla/packages/client/package.json | 3 +- .../client/src/mud/getNetworkConfig.ts | 4 +- .../packages/client/src/mud/setupNetwork.ts | 2 +- 162 files changed, 300 insertions(+), 8124 deletions(-) create mode 100644 .changeset/late-geese-guess.md create mode 100644 .changeset/seven-flies-chew.md create mode 100644 .changeset/wild-gorillas-care.md delete mode 100644 docs/pages/world/snap-sync.mdx create mode 100644 packages/common/src/getBurnerPrivateKey.ts delete mode 100644 packages/network/.gitignore delete mode 100644 packages/network/.hg delete mode 100644 packages/network/.npmignore delete mode 100644 packages/network/CHANGELOG.md delete mode 100644 packages/network/README.md delete mode 100644 packages/network/jest.config.js delete mode 100644 packages/network/jest.setup.ts delete mode 100644 packages/network/package.json delete mode 100644 packages/network/src/createBlockNumberStream.ts delete mode 100644 packages/network/src/createClock.spec.ts delete mode 100644 packages/network/src/createClock.ts delete mode 100644 packages/network/src/createContracts.ts delete mode 100644 packages/network/src/createDecoder.spec.ts delete mode 100644 packages/network/src/createDecoder.ts delete mode 100644 packages/network/src/createEncoder.ts delete mode 100644 packages/network/src/createFastTxExecutor.ts delete mode 100644 packages/network/src/createNetwork.spec.ts delete mode 100644 packages/network/src/createNetwork.ts delete mode 100644 packages/network/src/createProvider.ts delete mode 100644 packages/network/src/createRelayStream.ts delete mode 100644 packages/network/src/createSigner.ts delete mode 100644 packages/network/src/createSyncWorker.ts delete mode 100644 packages/network/src/createSystemExecutor.ts delete mode 100644 packages/network/src/createTopics.ts delete mode 100644 packages/network/src/createTxQueue.ts delete mode 100644 packages/network/src/debug.ts delete mode 100644 packages/network/src/dev/index.ts delete mode 100644 packages/network/src/dev/observables.ts delete mode 100644 packages/network/src/index.ts delete mode 100644 packages/network/src/initCache.ts delete mode 100644 packages/network/src/networkUtils.ts delete mode 100644 packages/network/src/provider.ts delete mode 100644 packages/network/src/types.ts delete mode 100644 packages/network/src/utils.ts delete mode 100644 packages/network/src/v2/README.md delete mode 100644 packages/network/src/v2/common.ts delete mode 100644 packages/network/src/v2/decodeStoreSetField.ts delete mode 100644 packages/network/src/v2/decodeStoreSetRecord.ts delete mode 100644 packages/network/src/v2/ecsEventFromLog.ts delete mode 100644 packages/network/src/v2/fetchStoreEvents.ts delete mode 100644 packages/network/src/v2/keyTupleToEntityID.spec.ts delete mode 100644 packages/network/src/v2/keyTupleToEntityID.ts delete mode 100644 packages/network/src/v2/mode/createModeClient.ts delete mode 100644 packages/network/src/v2/mode/getBlockNumberFromModeTable.ts delete mode 100644 packages/network/src/v2/mode/getModeBlockNumber.ts delete mode 100644 packages/network/src/v2/mode/syncTablesFromMode.ts delete mode 100644 packages/network/src/v2/schemas/decodeData.spec.ts delete mode 100644 packages/network/src/v2/schemas/decodeData.ts delete mode 100644 packages/network/src/v2/schemas/decodeDynamicField.ts delete mode 100644 packages/network/src/v2/schemas/decodeField.ts delete mode 100644 packages/network/src/v2/schemas/decodeKeyTuple.ts delete mode 100644 packages/network/src/v2/schemas/decodeSchema.ts delete mode 100644 packages/network/src/v2/schemas/decodeStaticField.spec.ts delete mode 100644 packages/network/src/v2/schemas/decodeStaticField.ts delete mode 100644 packages/network/src/v2/schemas/decodeValue.ts delete mode 100644 packages/network/src/v2/schemas/tableMetadata.ts delete mode 100644 packages/network/src/v2/schemas/tableSchemas.ts delete mode 100644 packages/network/src/v2/snapSync/getSnapSyncRecords.ts delete mode 100644 packages/network/src/v2/snapSync/index.ts delete mode 100644 packages/network/src/v2/snapSync/snapSyncSystemAbi.ts delete mode 100644 packages/network/src/v2/syncUtils.ts delete mode 100644 packages/network/src/v2/transformTableRecordsIntoEvents.ts delete mode 100644 packages/network/src/workers/CacheStore.spec.ts delete mode 100644 packages/network/src/workers/CacheStore.ts delete mode 100644 packages/network/src/workers/Recover.worker.ts delete mode 100644 packages/network/src/workers/Sync.worker.ts delete mode 100644 packages/network/src/workers/SyncWorker.spec.ts delete mode 100644 packages/network/src/workers/SyncWorker.ts delete mode 100644 packages/network/src/workers/constants.ts delete mode 100644 packages/network/src/workers/debug.ts delete mode 100644 packages/network/src/workers/index.ts delete mode 100644 packages/network/tsconfig.json delete mode 100644 packages/network/tsup.config.ts rename packages/{network/src => services/ts/faucet}/createFaucetService.ts (86%) create mode 100644 packages/services/ts/faucet/index.ts rename packages/std-client/src/{ => deprecated}/components/ActionComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/BoolComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/CoordComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/DevHighlight.ts (100%) rename packages/std-client/src/{ => deprecated}/components/NumberComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/StringComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/VoxelCoordComponent.ts (100%) rename packages/std-client/src/{ => deprecated}/components/index.ts (100%) rename packages/std-client/src/{ => deprecated}/getBurnerWallet.ts (95%) rename packages/std-client/src/{ => deprecated}/hooks.ts (100%) rename packages/std-client/src/{ => deprecated}/index.ts (86%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/constants.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/createActionSystem.spec.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/createActionSystem.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/index.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/types.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/utils/index.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/ActionSystem/utils/waitForActionCompletion.ts (100%) rename packages/std-client/src/{ => deprecated}/systems/index.ts (100%) create mode 100644 packages/std-client/src/deprecated/utils.ts delete mode 100644 packages/std-client/src/dev/index.ts delete mode 100644 packages/std-client/src/dev/observables.ts delete mode 100644 packages/std-client/src/setup/index.ts delete mode 100644 packages/std-client/src/setup/setupMUDV2Network.ts delete mode 100644 packages/std-client/src/setup/types.ts delete mode 100644 packages/std-client/src/setup/utils.ts delete mode 100644 packages/std-client/src/utils.ts delete mode 100644 packages/world/abi/SnapSyncModule.sol/SnapSyncModule.abi.json delete mode 100644 packages/world/abi/SnapSyncSystem.sol/SnapSyncSystem.abi.json delete mode 100644 packages/world/src/modules/snapsync/SnapSyncModule.sol delete mode 100644 packages/world/src/modules/snapsync/SnapSyncSystem.sol delete mode 100644 packages/world/src/modules/snapsync/SyncRecord.sol delete mode 100644 packages/world/src/modules/snapsync/constants.sol delete mode 100644 packages/world/test/SnapSyncModule.t.sol delete mode 100644 packages/world/ts/plugins/snapsync/configExtensions.ts delete mode 100644 packages/world/ts/plugins/snapsync/index.ts delete mode 100644 packages/world/ts/plugins/snapsync/plugin.ts delete mode 100644 packages/world/ts/plugins/snapsync/typeExtensions.ts diff --git a/.changeset/late-geese-guess.md b/.changeset/late-geese-guess.md new file mode 100644 index 0000000000..2b75a94590 --- /dev/null +++ b/.changeset/late-geese-guess.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/world": major +--- + +The `SnapSyncModule` is removed. The recommended way of loading the initial state of a MUD app is via the new [`store-indexer`](https://mud.dev/indexer). Loading state via contract getter functions is not recommended, as it's computationally heavy on the RPC, can't be cached, and is an easy way to shoot yourself in the foot with exploding RPC costs. + +The `@latticexyz/network` package was deprecated and is now removed. All consumers should upgrade to the new sync stack from `@latticexyz/store-sync`. diff --git a/.changeset/seven-flies-chew.md b/.changeset/seven-flies-chew.md new file mode 100644 index 0000000000..ba2e7776cf --- /dev/null +++ b/.changeset/seven-flies-chew.md @@ -0,0 +1,11 @@ +--- +"@latticexyz/services": major +"create-mud": patch +--- + +Move `createFaucetService` from `@latticexyz/network` to `@latticexyz/services/faucet`. + +```diff +- import { createFaucetService } from "@latticexyz/network"; ++ import { createFaucetService } from "@latticexyz/services/faucet"; +``` diff --git a/.changeset/wild-gorillas-care.md b/.changeset/wild-gorillas-care.md new file mode 100644 index 0000000000..d14f2d542e --- /dev/null +++ b/.changeset/wild-gorillas-care.md @@ -0,0 +1,28 @@ +--- +"@latticexyz/std-client": major +"@latticexyz/common": minor +"create-mud": patch +--- + +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). + + ```diff + - import { ... } from "@latticexyz/std-client"; + + import { ... } from "@latticexyz/std-client/deprecated"; + ``` diff --git a/docs/pages/world/modules.mdx b/docs/pages/world/modules.mdx index cb08ce3126..f587a5aaff 100644 --- a/docs/pages/world/modules.mdx +++ b/docs/pages/world/modules.mdx @@ -41,7 +41,7 @@ function spawnSoldier() public { ### Querying modules -The `KeysInTableModule` and `KeysWithValueModule` modules index information about tables on-chain. Their functionality is leveraged in `SnapSyncModule` and `query` to allow on-chain querying. +The `KeysInTableModule` and `KeysWithValueModule` modules index information about tables on-chain. Their functionality is leveraged in `query` to allow on-chain querying. #### **`KeysInTableModule`** @@ -111,38 +111,6 @@ bytes32[] memory keysWithValue = getKeysWithValue(world, OwnersId, Owners.encode Internally, it works by installing a [hook](/store/advanced-features#storage-hooks) that maintains an array of all keys in the table. -#### **`SnapSyncModule`** - -The [`SnapSyncModule`](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/snapsync/SnapSyncModule.sol) installs a [System](/world/world-101#systems) that returns all records in a given table, with offset-based pagination. It requires the `KeysInTable` module to be installed on each table, but the [`SnapSync` plugin](/world/snap-sync) does this automatically. - -`SnapSyncSystem` exposes two public `view` functions: - -- `getRecords(bytes32 tableId, uint256 limit, uint256 offset)` returns all keys in a table -- `getNumKeysInTable(bytes32 tableId)` returns the number of keys in a table - -Clients can use snap-sync to get all records on the World, then begin syncing regularly from the current block: - -```tsx -import { getSnapSyncRecords } from "@latticexyz/network"; -import { getTableIds } from "@latticexyz/common/deprecated"; - -... - -if (networkConfig.snapSync) { - const currentBlockNumber = await provider.getBlockNumber(); - const tableRecords = await getSnapSyncRecords( - networkConfig.worldAddress, - getTableIds(storeConfig), - currentBlockNumber, - signerOrProvider - ); - - result.startSync(tableRecords, currentBlockNumber); -} else { - result.startSync(); -} -``` - #### **`query`** `query` provides a simple API to get a list of keys matching certain specified criteria. It is not a standalone module, but requires `KeysInTable` and `KeysWithValue` to be installed. diff --git a/docs/pages/world/snap-sync.mdx b/docs/pages/world/snap-sync.mdx deleted file mode 100644 index f03b2e8382..0000000000 --- a/docs/pages/world/snap-sync.mdx +++ /dev/null @@ -1,144 +0,0 @@ -## Snap-sync - -The [`SnapSync`](https://github.com/latticexyz/mud/tree/main/packages/world/ts/plugins/snapsync) plugin allows clients to sync with the state of a World by fetching it directly from the blockchain. - -Without this plugin or [an Indexer](/indexer), clients can be slow to sync as they have to process all events since the World was deployed. - -Let's walk through an example of using snap-sync. - -#### Creating tables - -First, we define some tables. - -```tsx -import { mudConfig } from "@latticexyz/world/register"; - -export default mudConfig({ - tables: { - Karma: "int32", - Reputation: { - schema: { - fame: "uint8", - infamy: "uint8", - }, - }, - }, -}); -``` - -#### Adding the snap-sync plugin - -Next, add the `SnapSync` plugin. This will automatically install the necessary [smart contract modules](/world/modules#snapsyncmodule). - -```tsx -import { mudConfig } from "@latticexyz/world/register"; -import { resolveTableId } from "@latticexyz/config"; -/** - * Importing this enables "snap sync mode". - * It allows clients to sync the latest state of the world using view functions. - */ -import "@latticexyz/world/snapsync"; - -export default mudConfig({ - snapSync: true, - tables: { - Karma: "int32", - Reputation: { - schema: { - fame: "uint8", - infamy: "uint8", - }, - }, - }, -}); -``` - -#### Using snap-sync in the client - -Now that the modules are installed on-chain, we need to enable snap-sync in the client. This depends on your exact client setup, but it comes by default with the MUD templates from [Quick start](/quick-start#project-setup). - -##### With a template - -In the standard templates, you can enable snap-sync on a given client by appending `?snapSync=true` to the url. Alternatively you can hardcode it to be enabled by default in `getNetworkConfig`: - -```tsx -export async function getNetworkConfig(): Promise { - - ... - - return { - ... - snapSync: params.get("snapSync") === "true", - }; -} -``` - -##### Manually - -When using [`std-client`](https://github.com/latticexyz/mud/tree/main/packages/std-client), we usually begin syncing by calling the `startSync()` function from `setupMUDV2Network`: - -```tsx -// Start syncing from the initial block set in the MUD config -result.startSync(); -``` - -Without any arguments, `startSync()` uses the initial block number in the MUD config, which is usually the block that the World was deployed. The means the initial sync is slow as it processes every event since deployment. - -However, `startSync()` takes two arguments: - -- `initialRecords` is an initial set of records for each table. -- `initialBlockNumber` is the block number to start syncing from. - -Using snap-sync, we can fetch the current state directly from the World contract, "skipping" the initial sync phase. The initial records are fetched at the current block number, for every table, using the snap-sync `view`. - -In the client, we fetch all the records at the current block number then begin syncing: - -```tsx -import { getSnapSyncRecords } from "@latticexyz/network"; -import { getTableIds } from "@latticexyz/common/deprecated"; - -... - -// Fetch the ID's of all tables in our config -const tableIds = getTableIds(storeConfig); - -// Fetch the current block number -const currentBlockNumber = await provider.getBlockNumber(); - -// Fetch all records at the current block for all tables -const tableRecords = await getSnapSyncRecords( - networkConfig.worldAddress, - tableIds, - currentBlockNumber, - signerOrProvider -); - -// Start syncing with these records at the current block -result.startSync(tableRecords, currentBlockNumber); -``` - -If both the Indexer and snap-sync are enabled, the arguments that are passed into `startSync()` take precedence. In this case the client would perform the initial "catch up" with snap-sync then stream from the Indexer. - -To allow users to toggle snap-sync, add a flag to the `NetworkConfig` type: - -```tsx -type NetworkConfig = SetupContractConfig & { - privateKey: string; - faucetServiceUrl?: string; - snapSync?: boolean; -}; -``` - -To set a default value for the flag, edit `getNetworkConfig()`: - -```tsx -export async function getNetworkConfig(): Promise { - - ... - - return { - ... - snapSync: params.get("snapSync") === "true", - }; -} -``` diff --git a/e2e/packages/client-vanilla/package.json b/e2e/packages/client-vanilla/package.json index 0dbfe0a0de..98f2a4aee0 100644 --- a/e2e/packages/client-vanilla/package.json +++ b/e2e/packages/client-vanilla/package.json @@ -14,11 +14,9 @@ "@improbable-eng/grpc-web": "^0.15.0", "@latticexyz/common": "link:../../../packages/common", "@latticexyz/dev-tools": "link:../../../packages/dev-tools", - "@latticexyz/network": "link:../../../packages/network", "@latticexyz/recs": "link:../../../packages/recs", "@latticexyz/schema-type": "link:../../../packages/schema-type", "@latticexyz/services": "link:../../../packages/services", - "@latticexyz/std-client": "link:../../../packages/std-client", "@latticexyz/store-sync": "link:../../../packages/store-sync", "@latticexyz/utils": "link:../../../packages/utils", "@latticexyz/world": "link:../../../packages/world", diff --git a/e2e/packages/client-vanilla/src/mud/getNetworkConfig.ts b/e2e/packages/client-vanilla/src/mud/getNetworkConfig.ts index 1296df65f4..2dd56bf204 100644 --- a/e2e/packages/client-vanilla/src/mud/getNetworkConfig.ts +++ b/e2e/packages/client-vanilla/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: params.get("privateKey") ?? getBurnerWallet().value, + privateKey: params.get("privateKey") ?? getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/e2e/packages/client-vanilla/src/mud/setupNetwork.ts b/e2e/packages/client-vanilla/src/mud/setupNetwork.ts index 341c3e303d..30926624d6 100644 --- a/e2e/packages/client-vanilla/src/mud/setupNetwork.ts +++ b/e2e/packages/client-vanilla/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/e2e/packages/sync-test/package.json b/e2e/packages/sync-test/package.json index aef3d3bb45..7963a954d2 100644 --- a/e2e/packages/sync-test/package.json +++ b/e2e/packages/sync-test/package.json @@ -8,7 +8,6 @@ }, "devDependencies": { "@latticexyz/cli": "link:../../../packages/cli", - "@latticexyz/network": "link:../../../packages/network", "@latticexyz/recs": "link:../../../packages/recs", "@latticexyz/schema-type": "link:../../../packages/schema-type", "@latticexyz/store": "link:../../../packages/store", diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index 50dc0ee03d..85f696816b 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: '@latticexyz/dev-tools': specifier: link:../../../packages/dev-tools version: link:../../../packages/dev-tools - '@latticexyz/network': - specifier: link:../../../packages/network - version: link:../../../packages/network '@latticexyz/recs': specifier: link:../../../packages/recs version: link:../../../packages/recs @@ -38,9 +35,6 @@ importers: '@latticexyz/services': specifier: link:../../../packages/services version: link:../../../packages/services - '@latticexyz/std-client': - specifier: link:../../../packages/std-client - version: link:../../../packages/std-client '@latticexyz/store-sync': specifier: link:../../../packages/store-sync version: link:../../../packages/store-sync @@ -150,9 +144,6 @@ importers: '@latticexyz/cli': specifier: link:../../../packages/cli version: link:../../../packages/cli - '@latticexyz/network': - specifier: link:../../../packages/network - version: link:../../../packages/network '@latticexyz/recs': specifier: link:../../../packages/recs version: link:../../../packages/recs diff --git a/examples/minimal/packages/client-phaser/package.json b/examples/minimal/packages/client-phaser/package.json index 449784bebd..b11e308b22 100644 --- a/examples/minimal/packages/client-phaser/package.json +++ b/examples/minimal/packages/client-phaser/package.json @@ -14,13 +14,11 @@ "@improbable-eng/grpc-web": "^0.15.0", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/phaserx": "link:../../../../packages/phaserx", "@latticexyz/react": "link:../../../../packages/react", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", "@latticexyz/services": "link:../../../../packages/services", - "@latticexyz/std-client": "link:../../../../packages/std-client", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/examples/minimal/packages/client-phaser/src/mud/getNetworkConfig.ts b/examples/minimal/packages/client-phaser/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/examples/minimal/packages/client-phaser/src/mud/getNetworkConfig.ts +++ b/examples/minimal/packages/client-phaser/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts b/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-phaser/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/examples/minimal/packages/client-react/package.json b/examples/minimal/packages/client-react/package.json index b96385353b..64c30cfcb5 100644 --- a/examples/minimal/packages/client-react/package.json +++ b/examples/minimal/packages/client-react/package.json @@ -14,12 +14,10 @@ "@improbable-eng/grpc-web": "^0.15.0", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/react": "link:../../../../packages/react", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", "@latticexyz/services": "link:../../../../packages/services", - "@latticexyz/std-client": "link:../../../../packages/std-client", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/examples/minimal/packages/client-react/src/mud/getNetworkConfig.ts b/examples/minimal/packages/client-react/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/examples/minimal/packages/client-react/src/mud/getNetworkConfig.ts +++ b/examples/minimal/packages/client-react/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/examples/minimal/packages/client-react/src/mud/setupNetwork.ts b/examples/minimal/packages/client-react/src/mud/setupNetwork.ts index 3ecb98b260..5f5736104a 100644 --- a/examples/minimal/packages/client-react/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-react/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/examples/minimal/packages/client-vanilla/package.json b/examples/minimal/packages/client-vanilla/package.json index 0d3e54c5b2..ffffbcaede 100644 --- a/examples/minimal/packages/client-vanilla/package.json +++ b/examples/minimal/packages/client-vanilla/package.json @@ -14,11 +14,9 @@ "@improbable-eng/grpc-web": "^0.15.0", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", "@latticexyz/services": "link:../../../../packages/services", - "@latticexyz/std-client": "link:../../../../packages/std-client", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/examples/minimal/packages/client-vanilla/src/mud/getNetworkConfig.ts b/examples/minimal/packages/client-vanilla/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/examples/minimal/packages/client-vanilla/src/mud/getNetworkConfig.ts +++ b/examples/minimal/packages/client-vanilla/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts b/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts +++ b/examples/minimal/packages/client-vanilla/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/examples/minimal/packages/contracts/mud.config.ts b/examples/minimal/packages/contracts/mud.config.ts index af345f9eb1..70d98bb5d6 100644 --- a/examples/minimal/packages/contracts/mud.config.ts +++ b/examples/minimal/packages/contracts/mud.config.ts @@ -1,17 +1,8 @@ import { mudConfig } from "@latticexyz/world/register"; -/** - * Importing this enables "snap sync mode". - * It allows clients to sync the latest state of the world using view functions. - * This is a simple way to quickly sync without the use of an external indexer. - * This could lead to expensive queries on live RPCs if the world is large, - * so we suggest using MODE for production deployments. - */ -import "@latticexyz/world/snapsync"; import { resolveTableId } from "@latticexyz/config"; export default mudConfig({ - snapSync: true, systems: { IncrementSystem: { name: "increment", diff --git a/examples/minimal/pnpm-lock.yaml b/examples/minimal/pnpm-lock.yaml index 0a493f2028..2f9214e3cf 100644 --- a/examples/minimal/pnpm-lock.yaml +++ b/examples/minimal/pnpm-lock.yaml @@ -38,9 +38,6 @@ importers: '@latticexyz/dev-tools': specifier: link:../../../../packages/dev-tools version: link:../../../../packages/dev-tools - '@latticexyz/network': - specifier: link:../../../../packages/network - version: link:../../../../packages/network '@latticexyz/phaserx': specifier: link:../../../../packages/phaserx version: link:../../../../packages/phaserx @@ -56,9 +53,6 @@ importers: '@latticexyz/services': specifier: link:../../../../packages/services version: link:../../../../packages/services - '@latticexyz/std-client': - specifier: link:../../../../packages/std-client - version: link:../../../../packages/std-client '@latticexyz/store-sync': specifier: link:../../../../packages/store-sync version: link:../../../../packages/store-sync @@ -162,9 +156,6 @@ importers: '@latticexyz/dev-tools': specifier: link:../../../../packages/dev-tools version: link:../../../../packages/dev-tools - '@latticexyz/network': - specifier: link:../../../../packages/network - version: link:../../../../packages/network '@latticexyz/react': specifier: link:../../../../packages/react version: link:../../../../packages/react @@ -177,9 +168,6 @@ importers: '@latticexyz/services': specifier: link:../../../../packages/services version: link:../../../../packages/services - '@latticexyz/std-client': - specifier: link:../../../../packages/std-client - version: link:../../../../packages/std-client '@latticexyz/store-sync': specifier: link:../../../../packages/store-sync version: link:../../../../packages/store-sync @@ -265,9 +253,6 @@ importers: '@latticexyz/dev-tools': specifier: link:../../../../packages/dev-tools version: link:../../../../packages/dev-tools - '@latticexyz/network': - specifier: link:../../../../packages/network - version: link:../../../../packages/network '@latticexyz/recs': specifier: link:../../../../packages/recs version: link:../../../../packages/recs @@ -277,9 +262,6 @@ importers: '@latticexyz/services': specifier: link:../../../../packages/services version: link:../../../../packages/services - '@latticexyz/std-client': - specifier: link:../../../../packages/std-client - version: link:../../../../packages/std-client '@latticexyz/store-sync': specifier: link:../../../../packages/store-sync version: link:../../../../packages/store-sync diff --git a/package.json b/package.json index 6e41351f1d..7d49efdd8e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "release:check": "changeset status --verbose --since=origin/main", "release:publish": "pnpm install && pnpm build && changeset publish", "release:version": "changeset version && pnpm install --lockfile-only && pnpx bun scripts/changelog.ts", - "sort-package-json": "npx sort-package-json package.json 'packages/*/package.json' 'templates/*/package.json' 'templates/*/packages/*/package.json' 'examples/*/package.json' 'examples/*/packages/*/package.json' 'integration/*/package.json' 'integration/*/packages/*/package.json' 'docs/package.json'", + "sort-package-json": "npx sort-package-json package.json 'packages/*/package.json' 'templates/*/package.json' 'templates/*/packages/*/package.json' 'examples/*/package.json' 'examples/*/packages/*/package.json' 'e2e/*/package.json' 'e2e/*/packages/*/package.json' 'docs/package.json'", "test": "pnpm recursive run test" }, "lint-staged": { diff --git a/packages/cli/src/utils/deploy.ts b/packages/cli/src/utils/deploy.ts index 345356bb67..f50a15dc30 100644 --- a/packages/cli/src/utils/deploy.ts +++ b/packages/cli/src/utils/deploy.ts @@ -18,7 +18,6 @@ import CoreModuleData from "@latticexyz/world/abi/CoreModule.sol/CoreModule.json import KeysWithValueModuleData from "@latticexyz/world/abi/KeysWithValueModule.sol/KeysWithValueModule.json" assert { type: "json" }; import KeysInTableModuleData from "@latticexyz/world/abi/KeysInTableModule.sol/KeysInTableModule.json" assert { type: "json" }; import UniqueEntityModuleData from "@latticexyz/world/abi/UniqueEntityModule.sol/UniqueEntityModule.json" assert { type: "json" }; -import SnapSyncModuleData from "@latticexyz/world/abi/SnapSyncModule.sol/SnapSyncModule.json" assert { type: "json" }; export interface DeployConfig { profile?: string; @@ -113,12 +112,6 @@ export async function deploy( disableTxWait, "UniqueEntityModule" ), - SnapSyncModule: deployContract( - SnapSyncModuleData.abi, - SnapSyncModuleData.bytecode, - disableTxWait, - "SnapSyncModule" - ), }; // Deploy user Modules diff --git a/packages/common/src/getBurnerPrivateKey.ts b/packages/common/src/getBurnerPrivateKey.ts new file mode 100644 index 0000000000..c11aebb49d --- /dev/null +++ b/packages/common/src/getBurnerPrivateKey.ts @@ -0,0 +1,26 @@ +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { isHex, Hex } from "viem"; + +function assertPrivateKey(privateKey: string, cacheKey: string): asserts privateKey is Hex { + if (!isHex(privateKey)) { + console.error("Private key found in cache is not valid hex", { privateKey, cacheKey }); + throw new Error(`Private key found in cache (${cacheKey}) is not valid hex`); + } + // ensure we can extract address from private key + // this should throw on bad private keys + privateKeyToAccount(privateKey); +} + +export function getBurnerPrivateKey(cacheKey = "mud:burnerWallet"): Hex { + const cachedPrivateKey = localStorage.getItem(cacheKey); + + if (cachedPrivateKey != null) { + assertPrivateKey(cachedPrivateKey, cacheKey); + return cachedPrivateKey; + } + + const privateKey = generatePrivateKey(); + console.log("New burner wallet created:", privateKeyToAccount(privateKey)); + localStorage.setItem(cacheKey, privateKey); + return privateKey; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 878de7788b..95489023b1 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,6 +1,7 @@ export * from "./createBurnerAccount"; export * from "./createContract"; export * from "./createNonceManager"; +export * from "./getBurnerPrivateKey"; export * from "./hexToTableId"; export * from "./tableIdToHex"; export * from "./transportObserver"; diff --git a/packages/ecs-browser/src/ComponentEditor.tsx b/packages/ecs-browser/src/ComponentEditor.tsx index ac764a5402..c852837aca 100644 --- a/packages/ecs-browser/src/ComponentEditor.tsx +++ b/packages/ecs-browser/src/ComponentEditor.tsx @@ -4,7 +4,7 @@ import { AnyComponent, Entity, Schema } from "@latticexyz/recs/src/types"; import { ComponentBrowserButton, ComponentEditorContainer, ComponentTitle } from "./StyledComponents"; import { ComponentValueEditor } from "./ComponentValueEditor"; import { hasContract, SetContractComponentFunction } from "./types"; -import { useComponentValueStream } from "@latticexyz/std-client"; +import { useComponentValueStream } from "@latticexyz/std-client/deprecated"; export const ComponentEditor = ({ entity, diff --git a/packages/ecs-browser/src/hooks.ts b/packages/ecs-browser/src/hooks.ts index bf527a85ee..64126a12c7 100644 --- a/packages/ecs-browser/src/hooks.ts +++ b/packages/ecs-browser/src/hooks.ts @@ -1,5 +1,5 @@ import { Component, Has, removeComponent } from "@latticexyz/recs"; -import { useQuery } from "@latticexyz/std-client"; +import { useQuery } from "@latticexyz/std-client/deprecated"; import { useCallback, useEffect, useState } from "react"; import type { Lang, IThemedToken } from "shiki"; diff --git a/packages/network/.gitignore b/packages/network/.gitignore deleted file mode 100644 index 8341911c48..0000000000 --- a/packages/network/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -docs -API \ No newline at end of file diff --git a/packages/network/.hg b/packages/network/.hg deleted file mode 100644 index cbbe5ab0ff..0000000000 --- a/packages/network/.hg +++ /dev/null @@ -1,2 +0,0 @@ -this file is here to tell parcel this is the project root -https://github.com/parcel-bundler/parcel/issues/7206#issuecomment-954127065% \ No newline at end of file diff --git a/packages/network/.npmignore b/packages/network/.npmignore deleted file mode 100644 index 1b9a49b3da..0000000000 --- a/packages/network/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -* - -!dist/** -!src/** -!package.json -!README.md -!CHANGELOG.md diff --git a/packages/network/CHANGELOG.md b/packages/network/CHANGELOG.md deleted file mode 100644 index 703efcdd65..0000000000 --- a/packages/network/CHANGELOG.md +++ /dev/null @@ -1,669 +0,0 @@ -# Change Log - -## 2.0.0-next.2 - -### Patch Changes - -- [#1308](https://github.com/latticexyz/mud/pull/1308) [`b8a6158d`](https://github.com/latticexyz/mud/commit/b8a6158d63738ebfc1e7eb221909436d050c7e39) Thanks [@holic](https://github.com/holic)! - bump viem to 1.6.0 - -- Updated dependencies [[`a2588116`](https://github.com/latticexyz/mud/commit/a25881160cb3283e11d218be7b8a9fe38ee83062), [`939916bc`](https://github.com/latticexyz/mud/commit/939916bcd5c9f3caf0399e9ab7689e77e6bef7ad), [`b8a6158d`](https://github.com/latticexyz/mud/commit/b8a6158d63738ebfc1e7eb221909436d050c7e39), [`48c51b52`](https://github.com/latticexyz/mud/commit/48c51b52acab147a2ed97903c43bafa9b6769473), [`b8a6158d`](https://github.com/latticexyz/mud/commit/b8a6158d63738ebfc1e7eb221909436d050c7e39)]: - - @latticexyz/store@2.0.0-next.2 - - @latticexyz/common@2.0.0-next.2 - - @latticexyz/world@2.0.0-next.2 - - @latticexyz/schema-type@2.0.0-next.2 - - @latticexyz/recs@2.0.0-next.2 - - @latticexyz/services@2.0.0-next.2 - - @latticexyz/solecs@2.0.0-next.2 - - @latticexyz/utils@2.0.0-next.2 - -## 2.0.0-next.1 - -### Patch Changes - -- [#1258](https://github.com/latticexyz/mud/pull/1258) [`6c673325`](https://github.com/latticexyz/mud/commit/6c6733256f91cddb0e913217cbd8e02e6bc484c7) Thanks [@holic](https://github.com/holic)! - Add `tableIdToHex` and `hexToTableId` pure functions and move/deprecate `TableId`. - -- Updated dependencies [[`3236f799`](https://github.com/latticexyz/mud/commit/3236f799e501be227da6e42e7b41a4928750115c), [`c963b46c`](https://github.com/latticexyz/mud/commit/c963b46c7eaceebc652930936643365b8c48a021), [`3fb9ce28`](https://github.com/latticexyz/mud/commit/3fb9ce2839271a0dcfe97f86394195f7a6f70f50), [`35c9f33d`](https://github.com/latticexyz/mud/commit/35c9f33dfb84b0bb94e0f7a8b0c9830761795bdb), [`5c965a91`](https://github.com/latticexyz/mud/commit/5c965a919355bf98d7ea69463890fe605bcde206), [`b02f9d0e`](https://github.com/latticexyz/mud/commit/b02f9d0e43089e5f9b46d817ea2032ce0a1b0b07), [`60cfd089`](https://github.com/latticexyz/mud/commit/60cfd089fc7a17b98864b631d265f36718df35a9), [`6071163f`](https://github.com/latticexyz/mud/commit/6071163f70599384c5034dd772ef6fc7cdae9983), [`6c673325`](https://github.com/latticexyz/mud/commit/6c6733256f91cddb0e913217cbd8e02e6bc484c7), [`cd5abcc3`](https://github.com/latticexyz/mud/commit/cd5abcc3b4744fab9a45c322bc76ff013355ffcb), [`afdba793`](https://github.com/latticexyz/mud/commit/afdba793fd84abf17eef5ef59dd56fabe353c8bd), [`cc2c8da0`](https://github.com/latticexyz/mud/commit/cc2c8da000c32c02a82a1a0fd17075d11eac56c3)]: - - @latticexyz/services@2.0.0-next.1 - - @latticexyz/store@2.0.0-next.1 - - @latticexyz/common@2.0.0-next.1 - - @latticexyz/schema-type@2.0.0-next.1 - - @latticexyz/recs@2.0.0-next.1 - - @latticexyz/world@2.0.0-next.1 - - @latticexyz/solecs@2.0.0-next.1 - - @latticexyz/utils@2.0.0-next.1 - -## 2.0.0-next.0 - -### Patch Changes - -- [#1179](https://github.com/latticexyz/mud/pull/1179) [`53522998`](https://github.com/latticexyz/mud/commit/535229984565539e6168042150b45fe0f9b48b0f) Thanks [@holic](https://github.com/holic)! - - bump to viem 1.3.0 and abitype 0.9.3 - - - move `@wagmi/chains` imports to `viem/chains` - - refine a few types - -- [#1109](https://github.com/latticexyz/mud/pull/1109) [`e019c776`](https://github.com/latticexyz/mud/commit/e019c77619f0ace6b7ee01f6ce96498446895934) Thanks [@Kooshaba](https://github.com/Kooshaba)! - Remove devEmit function when sending network events from SyncWorker because they can't be serialized across the web worker boundary. - -- Updated dependencies [[`904fd7d4`](https://github.com/latticexyz/mud/commit/904fd7d4ee06a86e481e3e02fd5744224376d0c9), [`8d51a034`](https://github.com/latticexyz/mud/commit/8d51a03486bc20006d8cc982f798dfdfe16f169f), [`1e2ad78e`](https://github.com/latticexyz/mud/commit/1e2ad78e277b551dd1b8efb0e4438fb10441644c), [`48909d15`](https://github.com/latticexyz/mud/commit/48909d151b3dfceab128c120bc6bb77de53c456b), [`66cc35a8`](https://github.com/latticexyz/mud/commit/66cc35a8ccb21c50a1882d6c741dd045acd8bc11), [`f03531d9`](https://github.com/latticexyz/mud/commit/f03531d97c999954a626ef63bc5bbae51a7b90f3), [`a7b30c79`](https://github.com/latticexyz/mud/commit/a7b30c79bcc78530d2d01858de46a0fb87954fda), [`4e4a3415`](https://github.com/latticexyz/mud/commit/4e4a34150aeae988c8e61e25d55c227afb6c2d4b), [`53522998`](https://github.com/latticexyz/mud/commit/535229984565539e6168042150b45fe0f9b48b0f), [`086be4ef`](https://github.com/latticexyz/mud/commit/086be4ef4f3c1ecb3eac0e9554d7d4eb64531fc2), [`0c4f9fea`](https://github.com/latticexyz/mud/commit/0c4f9fea9e38ba122316cdd52c3d158c62f8cfee)]: - - @latticexyz/store@2.0.0-next.0 - - @latticexyz/common@2.0.0-next.0 - - @latticexyz/world@2.0.0-next.0 - - @latticexyz/recs@2.0.0-next.0 - - @latticexyz/schema-type@2.0.0-next.0 - - @latticexyz/solecs@2.0.0-next.0 - - @latticexyz/utils@2.0.0-next.0 - - @latticexyz/services@2.0.0-next.0 - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [1.42.0](https://github.com/latticexyz/mud/compare/v1.41.0...v1.42.0) (2023-04-13) - -### Bug Fixes - -- **network:** fall back to RPC sync if MODE is not available ([#555](https://github.com/latticexyz/mud/issues/555)) ([4de4b6d](https://github.com/latticexyz/mud/commit/4de4b6d5ab4f8b27873af6d298ba3e6e1c1fd02f)) -- **network:** fix mode decoding ([#562](https://github.com/latticexyz/mud/issues/562)) ([fb82313](https://github.com/latticexyz/mud/commit/fb823131fc8f7bcbdcbfa57de59a8a5dbca2f8b6)) -- **network:** handle singleton/empty keys ([#541](https://github.com/latticexyz/mud/issues/541)) ([1e0ddb9](https://github.com/latticexyz/mud/commit/1e0ddb9adaf2376ee6b578e3fb1d1eb0b3e22206)) -- **network:** skip sync from cache in dev mode ([#521](https://github.com/latticexyz/mud/issues/521)) ([818c1e2](https://github.com/latticexyz/mud/commit/818c1e2e6e7acb45f4d65d47f5395cb2d3811a57)) - -### Features - -- **create-mud:** use pnpm in templates, move to root so they can be installed/run ([#599](https://github.com/latticexyz/mud/issues/599)) ([010740d](https://github.com/latticexyz/mud/commit/010740d09d40d4ff6d95538d498a513fbb65ca45)) -- **network,recs,std-client:** support StoreSetField before StoreSetRecord ([#581](https://github.com/latticexyz/mud/issues/581)) ([f259f90](https://github.com/latticexyz/mud/commit/f259f90e1c561163a6675f4ec51b1659681d880b)), closes [#479](https://github.com/latticexyz/mud/issues/479) [#523](https://github.com/latticexyz/mud/issues/523) -- **network:** add fastTxExecute util ([#543](https://github.com/latticexyz/mud/issues/543)) ([f05a70a](https://github.com/latticexyz/mud/commit/f05a70a2e4be077af44c6d6a0b9c8da8d0c18bc5)) -- **network:** add option to sync in main thread instead of worker ([#522](https://github.com/latticexyz/mud/issues/522)) ([4e8e7d7](https://github.com/latticexyz/mud/commit/4e8e7d774c574de5d08c03f02ef1811bade2ce7c)) -- **network:** integrate initial sync from MODE ([#493](https://github.com/latticexyz/mud/issues/493)) ([7d06c1b](https://github.com/latticexyz/mud/commit/7d06c1b5cf00df627000c907e78f60d3cd2415cd)) -- use viem when creating burner wallet ([#576](https://github.com/latticexyz/mud/issues/576)) ([d5d22e0](https://github.com/latticexyz/mud/commit/d5d22e0b855cc9a606aa6e1380449a0840ea7343)) -- v2 event decoding ([#415](https://github.com/latticexyz/mud/issues/415)) ([374ed54](https://github.com/latticexyz/mud/commit/374ed542c3387a4ec2b36ab68ae534419aa58763)) - -# [1.41.0](https://github.com/latticexyz/mud/compare/v1.40.0...v1.41.0) (2023-03-09) - -**Note:** Version bump only for package @latticexyz/network - -# [1.40.0](https://github.com/latticexyz/mud/compare/v1.39.0...v1.40.0) (2023-03-03) - -**Note:** Version bump only for package @latticexyz/network - -# [1.39.0](https://github.com/latticexyz/mud/compare/v1.38.0...v1.39.0) (2023-02-22) - -**Note:** Version bump only for package @latticexyz/network - -# [1.38.0](https://github.com/latticexyz/mud/compare/v1.37.1...v1.38.0) (2023-02-22) - -**Note:** Version bump only for package @latticexyz/network - -## [1.37.1](https://github.com/latticexyz/mud/compare/v1.37.0...v1.37.1) (2023-02-17) - -**Note:** Version bump only for package @latticexyz/network - -# [1.37.0](https://github.com/latticexyz/mud/compare/v1.36.1...v1.37.0) (2023-02-16) - -### Bug Fixes - -- package entry points, peer dep versions ([#409](https://github.com/latticexyz/mud/issues/409)) ([66a7fd6](https://github.com/latticexyz/mud/commit/66a7fd6f74620ce02c60e3d55701d4740cc65251)) - -### Reverts - -- Revert "chore(release): publish v1.37.0" ([c934f53](https://github.com/latticexyz/mud/commit/c934f5388c1e56f2fe6390fdda30f5b9b1ea1c20)) - -## [1.36.1](https://github.com/latticexyz/mud/compare/v1.36.0...v1.36.1) (2023-02-16) - -**Note:** Version bump only for package @latticexyz/network - -# [1.35.0](https://github.com/latticexyz/mud/compare/v1.34.0...v1.35.0) (2023-02-15) - -### Bug Fixes - -- **network:** add explicit return type to createFaucetService ([#399](https://github.com/latticexyz/mud/issues/399)) ([cae82e5](https://github.com/latticexyz/mud/commit/cae82e5781931f86d0bc53eb05306197fab3d7aa)) -- **network:** use current block number while waiting for new blocks ([#368](https://github.com/latticexyz/mud/issues/368)) ([09b77a7](https://github.com/latticexyz/mud/commit/09b77a7e27d2056a30f9b9c41046b7d6eec8dda7)) - -# [1.34.0](https://github.com/latticexyz/mud/compare/v1.33.1...v1.34.0) (2023-01-29) - -### Bug Fixes - -- **network:** throw errors from txQueue calls ([#351](https://github.com/latticexyz/mud/issues/351)) ([a811ff7](https://github.com/latticexyz/mud/commit/a811ff76e500bbc3e983f32c13877bdee855113d)), closes [#315](https://github.com/latticexyz/mud/issues/315) - -### Features - -- **network:** add support for external wallets (eg MetaMask) ([#256](https://github.com/latticexyz/mud/issues/256)) ([bf0b5cf](https://github.com/latticexyz/mud/commit/bf0b5cff5f70903ef8b43a46ad07b649946b21a9)) - -## [1.33.1](https://github.com/latticexyz/mud/compare/v1.33.0...v1.33.1) (2023-01-12) - -**Note:** Version bump only for package @latticexyz/network - -# [1.33.0](https://github.com/latticexyz/mud/compare/v1.32.0...v1.33.0) (2023-01-12) - -**Note:** Version bump only for package @latticexyz/network - -# [1.32.0](https://github.com/latticexyz/mud/compare/v1.31.3...v1.32.0) (2023-01-06) - -**Note:** Version bump only for package @latticexyz/network - -## [1.31.3](https://github.com/latticexyz/mud/compare/v1.31.2...v1.31.3) (2022-12-16) - -**Note:** Version bump only for package @latticexyz/network - -## [1.31.2](https://github.com/latticexyz/mud/compare/v1.31.1...v1.31.2) (2022-12-15) - -**Note:** Version bump only for package @latticexyz/network - -## [1.31.1](https://github.com/latticexyz/mud/compare/v1.31.0...v1.31.1) (2022-12-15) - -**Note:** Version bump only for package @latticexyz/network - -# [1.31.0](https://github.com/latticexyz/mud/compare/v1.30.1...v1.31.0) (2022-12-14) - -**Note:** Version bump only for package @latticexyz/network - -## [1.30.1](https://github.com/latticexyz/mud/compare/v1.30.0...v1.30.1) (2022-12-02) - -**Note:** Version bump only for package @latticexyz/network - -# [1.30.0](https://github.com/latticexyz/mud/compare/v1.29.0...v1.30.0) (2022-12-02) - -**Note:** Version bump only for package @latticexyz/network - -# [1.29.0](https://github.com/latticexyz/mud/compare/v1.28.1...v1.29.0) (2022-11-29) - -**Note:** Version bump only for package @latticexyz/network - -## [1.28.1](https://github.com/latticexyz/mud/compare/v1.28.0...v1.28.1) (2022-11-24) - -### Bug Fixes - -- typescript errors ([#253](https://github.com/latticexyz/mud/issues/253)) ([83e0c7a](https://github.com/latticexyz/mud/commit/83e0c7a1eda900d254a73115446c4ce38b531645)) - -# [1.28.0](https://github.com/latticexyz/mud/compare/v1.27.1...v1.28.0) (2022-11-20) - -### Features - -- **network:** system call stream available in streaming service ([0244eb8](https://github.com/latticexyz/mud/commit/0244eb8d3ec1a7798136cf4ddefbd766cb830b8c)), closes [#254](https://github.com/latticexyz/mud/issues/254) - -# [1.27.0](https://github.com/latticexyz/mud/compare/v1.26.0...v1.27.0) (2022-11-15) - -### Bug Fixes - -- **network:** disable browser cache in dev mode ([#213](https://github.com/latticexyz/mud/issues/213)) ([ba9e6bc](https://github.com/latticexyz/mud/commit/ba9e6bcaa869d48ce4e63c85e4f8c3b0c1d986b0)) - -# [1.26.0](https://github.com/latticexyz/mud/compare/v1.25.1...v1.26.0) (2022-11-07) - -**Note:** Version bump only for package @latticexyz/network - -## [1.25.1](https://github.com/latticexyz/mud/compare/v1.25.0...v1.25.1) (2022-11-03) - -**Note:** Version bump only for package @latticexyz/network - -# [1.25.0](https://github.com/latticexyz/mud/compare/v1.24.1...v1.25.0) (2022-11-03) - -### Features - -- **network,std-client:** add support for SystemCall events in default MUD network setup ([#232](https://github.com/latticexyz/mud/issues/232)) ([93d947b](https://github.com/latticexyz/mud/commit/93d947b24bd641d8b6105f0d5ac308944903c26b)) -- **network:** export createBlockNumberStream ([#230](https://github.com/latticexyz/mud/issues/230)) ([c227e5d](https://github.com/latticexyz/mud/commit/c227e5df39dd9ca81652af142f2b07f1b64b3629)) - -## [1.24.1](https://github.com/latticexyz/mud/compare/v1.24.0...v1.24.1) (2022-10-29) - -**Note:** Version bump only for package @latticexyz/network - -# [1.24.0](https://github.com/latticexyz/mud/compare/v1.23.1...v1.24.0) (2022-10-28) - -### Features - -- v2 endpoint for pruned snapshot that returns entities as raw bytes ([#215](https://github.com/latticexyz/mud/issues/215)) ([28cce1e](https://github.com/latticexyz/mud/commit/28cce1e8a1240d72363fe786704e7fe976f7c995)) - -## [1.23.1](https://github.com/latticexyz/mud/compare/v1.23.0...v1.23.1) (2022-10-28) - -**Note:** Version bump only for package @latticexyz/network - -# [1.23.0](https://github.com/latticexyz/mud/compare/v1.22.0...v1.23.0) (2022-10-26) - -**Note:** Version bump only for package @latticexyz/network - -# [1.22.0](https://github.com/latticexyz/mud/compare/v1.21.0...v1.22.0) (2022-10-26) - -### Features - -- **network:** expose method to register new system contracts on the client ([#224](https://github.com/latticexyz/mud/issues/224)) ([4583767](https://github.com/latticexyz/mud/commit/45837676ebe776f1e752affb7ea1dadf44e451f2)) -- **network:** simplify calling untyped systems ([#223](https://github.com/latticexyz/mud/issues/223)) ([94e4788](https://github.com/latticexyz/mud/commit/94e4788174b019d3f57df98f3a291d0498d1f17c)) - -# [1.21.0](https://github.com/latticexyz/mud/compare/v1.20.0...v1.21.0) (2022-10-26) - -### Features - -- **network:** send ack between main thread and sync worker ([#220](https://github.com/latticexyz/mud/issues/220)) ([e06978a](https://github.com/latticexyz/mud/commit/e06978aafc37a0992ca0d7cb58a97da0a5295781)) - -# [1.20.0](https://github.com/latticexyz/mud/compare/v1.19.0...v1.20.0) (2022-10-22) - -**Note:** Version bump only for package @latticexyz/network - -# [1.19.0](https://github.com/latticexyz/mud/compare/v1.18.0...v1.19.0) (2022-10-21) - -### Features - -- **network:** only create encoders if asked for it ([c5af08c](https://github.com/latticexyz/mud/commit/c5af08c7a0aa26ccc6e7085b1539ad4f271d4a41)) - -# [1.18.0](https://github.com/latticexyz/mud/compare/v1.17.0...v1.18.0) (2022-10-21) - -### Features - -- service stabilizations, send ecs tx on drip, new pruned snapshot endpoint ([#204](https://github.com/latticexyz/mud/issues/204)) ([d0de185](https://github.com/latticexyz/mud/commit/d0de185ca7fa2418064706928853e5cd691bdde9)) - -# [1.17.0](https://github.com/latticexyz/mud/compare/v1.16.0...v1.17.0) (2022-10-19) - -### Features - -- allow specific snapshot chunk ratio ([#212](https://github.com/latticexyz/mud/issues/212)) ([827d972](https://github.com/latticexyz/mud/commit/827d972ac9ca11918520b5f040045dfb4cca1552)) - -# [1.16.0](https://github.com/latticexyz/mud/compare/v1.15.0...v1.16.0) (2022-10-19) - -### Features - -- **network:** expose more sync settings ([#211](https://github.com/latticexyz/mud/issues/211)) ([48987f1](https://github.com/latticexyz/mud/commit/48987f1c37af9a82a7f92da6f3c8247ece4a750f)) - -# [1.15.0](https://github.com/latticexyz/mud/compare/v1.14.2...v1.15.0) (2022-10-18) - -### Features - -- **network): expose relay ping method, feat(std-client:** add tx hash to action component ([#209](https://github.com/latticexyz/mud/issues/209)) ([3e0b4a7](https://github.com/latticexyz/mud/commit/3e0b4a75ec93605f8dc6f561b140ccc9d9722566)) - -## [1.14.2](https://github.com/latticexyz/mud/compare/v1.14.1...v1.14.2) (2022-10-18) - -**Note:** Version bump only for package @latticexyz/network - -## [1.14.1](https://github.com/latticexyz/mud/compare/v1.14.0...v1.14.1) (2022-10-18) - -**Note:** Version bump only for package @latticexyz/network - -# [1.14.0](https://github.com/latticexyz/mud/compare/v1.13.0...v1.14.0) (2022-10-18) - -**Note:** Version bump only for package @latticexyz/network - -# [1.13.0](https://github.com/latticexyz/mud/compare/v1.12.0...v1.13.0) (2022-10-15) - -**Note:** Version bump only for package @latticexyz/network - -# [1.12.0](https://github.com/latticexyz/mud/compare/v1.11.0...v1.12.0) (2022-10-12) - -**Note:** Version bump only for package @latticexyz/network - -# [1.11.0](https://github.com/latticexyz/mud/compare/v1.10.0...v1.11.0) (2022-10-11) - -**Note:** Version bump only for package @latticexyz/network - -# [1.10.0](https://github.com/latticexyz/mud/compare/v1.9.0...v1.10.0) (2022-10-11) - -**Note:** Version bump only for package @latticexyz/network - -# [1.9.0](https://github.com/latticexyz/mud/compare/v1.8.0...v1.9.0) (2022-10-11) - -### Features - -- **network:** fall back to rpc if stream service errors ([#190](https://github.com/latticexyz/mud/issues/190)) ([414e777](https://github.com/latticexyz/mud/commit/414e77799259cdb28bf92c1ef603608d0bdb05fd)) - -# [1.8.0](https://github.com/latticexyz/mud/compare/v1.7.1...v1.8.0) (2022-10-07) - -### Bug Fixes - -- **network:** use websocket to subscribe to relayer messages ([8218249](https://github.com/latticexyz/mud/commit/8218249a228a6b3acd52776b653688aa8d73e9a9)) - -### Features - -- connected relayer clients ([#188](https://github.com/latticexyz/mud/issues/188)) ([dc3fcdf](https://github.com/latticexyz/mud/commit/dc3fcdf7a02f3cced981ca933faf145c38b43fe0)) -- **network:** expose connectedAddressChecksum ([#189](https://github.com/latticexyz/mud/issues/189)) ([e39d245](https://github.com/latticexyz/mud/commit/e39d245f62e5edf91896a39bb52c993df814ffb6)) -- wss for stream service ([#186](https://github.com/latticexyz/mud/issues/186)) ([d4511ac](https://github.com/latticexyz/mud/commit/d4511acb1805ddacc71f83cdd9dc7858bd07aee1)) - -## [1.7.1](https://github.com/latticexyz/mud/compare/v1.7.0...v1.7.1) (2022-10-06) - -**Note:** Version bump only for package @latticexyz/network - -# [1.7.0](https://github.com/latticexyz/mud/compare/v1.6.0...v1.7.0) (2022-10-06) - -### Features - -- add utils to normalize hex ids ([#185](https://github.com/latticexyz/mud/issues/185)) ([170e963](https://github.com/latticexyz/mud/commit/170e963eebce61b27d1b999f8afd8c8e176a739c)) - -# [1.6.0](https://github.com/latticexyz/mud/compare/v1.5.1...v1.6.0) (2022-10-04) - -**Note:** Version bump only for package @latticexyz/network - -## [1.5.1](https://github.com/latticexyz/mud/compare/v1.5.0...v1.5.1) (2022-10-03) - -**Note:** Version bump only for package @latticexyz/network - -# [1.5.0](https://github.com/latticexyz/mud/compare/v1.4.1...v1.5.0) (2022-10-03) - -### Features - -- add a stream rpc for message push ([#174](https://github.com/latticexyz/mud/issues/174)) ([e0aa956](https://github.com/latticexyz/mud/commit/e0aa956ac871064ecde87a07394525ca69e7f17d)) - -## [1.4.1](https://github.com/latticexyz/mud/compare/v1.4.0...v1.4.1) (2022-10-03) - -**Note:** Version bump only for package @latticexyz/network - -# [1.4.0](https://github.com/latticexyz/mud/compare/v1.3.0...v1.4.0) (2022-10-03) - -### Features - -- add signature verification for all client actions via relay service ([#167](https://github.com/latticexyz/mud/issues/167)) ([7920d6e](https://github.com/latticexyz/mud/commit/7920d6eec20f3a669cb3a1a9e39cd822e421961a)) -- **network:** add util for creating faucet service ([#171](https://github.com/latticexyz/mud/issues/171)) ([9f50d9c](https://github.com/latticexyz/mud/commit/9f50d9c3ae31132507c19bce7d3d5c8df7684cad)) - -# [1.3.0](https://github.com/latticexyz/mud/compare/v1.2.0...v1.3.0) (2022-09-30) - -**Note:** Version bump only for package @latticexyz/network - -# [1.2.0](https://github.com/latticexyz/mud/compare/v1.1.0...v1.2.0) (2022-09-29) - -### Bug Fixes - -- **network:** check event type instead of just value before decoding ([#166](https://github.com/latticexyz/mud/issues/166)) ([f4dedd9](https://github.com/latticexyz/mud/commit/f4dedd9005a110b2548f5b372f5a53abe06aacbf)) - -### Features - -- **network:** increase network performance by reducing unnecessary rpc calls ([#165](https://github.com/latticexyz/mud/issues/165)) ([195b710](https://github.com/latticexyz/mud/commit/195b71019b2be623d99f7a90c93a661cdb743a87)) - -# [1.1.0](https://github.com/latticexyz/mud/compare/v1.0.0...v1.1.0) (2022-09-28) - -### Features - -- add createRelayService, add utils to work with Uint8Arrays ([#164](https://github.com/latticexyz/mud/issues/164)) ([b02992b](https://github.com/latticexyz/mud/commit/b02992b73393740d7510b1f9d3d9e6ea0030f462)) -- initial implementation of ecs relay service ([#157](https://github.com/latticexyz/mud/issues/157)) ([140aec3](https://github.com/latticexyz/mud/commit/140aec3e92269f8c79fe0ef5e6639ca0ff056282)) - -# [1.0.0](https://github.com/latticexyz/mud/compare/v0.16.4...v1.0.0) (2022-09-27) - -**Note:** Version bump only for package @latticexyz/network - -## [0.16.4](https://github.com/latticexyz/mud/compare/v0.16.3...v0.16.4) (2022-09-26) - -### Bug Fixes - -- **network:** cancel tx request if gas estimation failed ([565b37f](https://github.com/latticexyz/mud/commit/565b37f5a7408c06e2fd5fdab2f42d69f8db6610)) - -## [0.16.3](https://github.com/latticexyz/mud/compare/v0.16.2...v0.16.3) (2022-09-26) - -### Bug Fixes - -- do gas estimation right before sending tx to avoid invalid gas estimations ([f251642](https://github.com/latticexyz/mud/commit/f25164268834390d35637b7aea84998cf88e16ae)) - -## [0.16.2](https://github.com/latticexyz/mud/compare/v0.16.1...v0.16.2) (2022-09-26) - -**Note:** Version bump only for package @latticexyz/network - -## [0.16.1](https://github.com/latticexyz/mud/compare/v0.16.0...v0.16.1) (2022-09-26) - -**Note:** Version bump only for package @latticexyz/network - -# [0.16.0](https://github.com/latticexyz/mud/compare/v0.15.1...v0.16.0) (2022-09-26) - -### Features - -- **network:** add system call stream ([#162](https://github.com/latticexyz/mud/issues/162)) ([5caef57](https://github.com/latticexyz/mud/commit/5caef57165ed1a927dc8631a361189abfd54ea7a)) -- **std-contracts:** add FunctionComponent ([#161](https://github.com/latticexyz/mud/issues/161)) ([d720277](https://github.com/latticexyz/mud/commit/d7202774a5a068a99b88a63cb18100482dc18cb8)) - -## [0.15.1](https://github.com/latticexyz/mud/compare/v0.15.0...v0.15.1) (2022-09-23) - -**Note:** Version bump only for package @latticexyz/network - -# [0.15.0](https://github.com/latticexyz/mud/compare/v0.14.2...v0.15.0) (2022-09-21) - -**Note:** Version bump only for package @latticexyz/network - -## [0.14.2](https://github.com/latticexyz/mud/compare/v0.14.1...v0.14.2) (2022-09-21) - -**Note:** Version bump only for package @latticexyz/network - -## [0.14.1](https://github.com/latticexyz/mud/compare/v0.14.0...v0.14.1) (2022-09-21) - -### Bug Fixes - -- **network:** initial sync ([#156](https://github.com/latticexyz/mud/issues/156)) ([6116585](https://github.com/latticexyz/mud/commit/611658584ffd52c63f837f239d888aa55959320e)) - -# [0.14.0](https://github.com/latticexyz/mud/compare/v0.13.0...v0.14.0) (2022-09-20) - -**Note:** Version bump only for package @latticexyz/network - -# [0.13.0](https://github.com/latticexyz/mud/compare/v0.12.0...v0.13.0) (2022-09-19) - -**Note:** Version bump only for package @latticexyz/network - -# [0.12.0](https://github.com/latticexyz/mud/compare/v0.11.1...v0.12.0) (2022-09-16) - -### Features - -- **cli:** forge bulk upload ecs state script ([#142](https://github.com/latticexyz/mud/issues/142)) ([bbd6e1f](https://github.com/latticexyz/mud/commit/bbd6e1f4be18dcae94addc65849136ad01d1ba2a)) - -## [0.11.1](https://github.com/latticexyz/mud/compare/v0.11.0...v0.11.1) (2022-09-15) - -### Bug Fixes - -- do not run prepack multiple times when publishing ([4f6f4c3](https://github.com/latticexyz/mud/commit/4f6f4c35a53c105951b32a071e47a748b2502cda)) - -# [0.11.0](https://github.com/latticexyz/mud/compare/v0.10.0...v0.11.0) (2022-09-15) - -### Features - -- add more granularity to initial sync state report ([#146](https://github.com/latticexyz/mud/issues/146)) ([d4ba338](https://github.com/latticexyz/mud/commit/d4ba338a50048c2d5180ce4f917d94f5b0893935)) - -# [0.10.0](https://github.com/latticexyz/mud/compare/v0.9.0...v0.10.0) (2022-09-14) - -### Features - -- add chunk snapshot and stream service ([#139](https://github.com/latticexyz/mud/issues/139)) ([8c9d4b3](https://github.com/latticexyz/mud/commit/8c9d4b30ed70470ca8770565b6472359e0e0f2bc)) - -# [0.9.0](https://github.com/latticexyz/mud/compare/v0.8.1...v0.9.0) (2022-09-13) - -### Bug Fixes - -- **network:** align hex entity id formatting ([#140](https://github.com/latticexyz/mud/issues/140)) ([93b1bd6](https://github.com/latticexyz/mud/commit/93b1bd6688780dc185a1c7e353954e2c5c85f648)) - -### Features - -- **network:** add loading state component update stream to SyncWorker ([#141](https://github.com/latticexyz/mud/issues/141)) ([824c4f3](https://github.com/latticexyz/mud/commit/824c4f366775be1f0e636b3781c743333421b679)) - -### BREAKING CHANGES - -- **network:** The loading state component is attached to the entity with id 0x060D (GodID). The - std-client package previously exported a different mudwar specific GodID, which has been replaced - with the 0x060D GodID exported by the network package. - -- test(network): add test for LoadingState and fix existing tests - -## [0.8.1](https://github.com/latticexyz/mud/compare/v0.8.0...v0.8.1) (2022-08-22) - -### Bug Fixes - -- start from initialBlockNumber, build settings, fix github actions, and other minor additions ([#137](https://github.com/latticexyz/mud/issues/137)) ([08eab5c](https://github.com/latticexyz/mud/commit/08eab5c6b0d99a9ffa8a315e16454ecc9306f410)) - -# [0.8.0](https://github.com/latticexyz/mud/compare/v0.7.0...v0.8.0) (2022-08-22) - -### Features - -- add mud.dev ([#133](https://github.com/latticexyz/mud/issues/133)) ([302588c](https://github.com/latticexyz/mud/commit/302588cbbab2803396b894bc006d13e6ac943da9)) -- integrate proto from services into network ([#131](https://github.com/latticexyz/mud/issues/131)) ([756fdb7](https://github.com/latticexyz/mud/commit/756fdb7cae6441e692088fd9cbbc8d9d327a70e0)) - -# [0.7.0](https://github.com/latticexyz/mud/compare/v0.6.0...v0.7.0) (2022-08-19) - -**Note:** Version bump only for package @latticexyz/network - -# [0.6.0](https://github.com/latticexyz/mud/compare/v0.5.1...v0.6.0) (2022-08-15) - -### Code Refactoring - -- sync worker (+ integrated snapshot service) ([#125](https://github.com/latticexyz/mud/issues/125)) ([6173b59](https://github.com/latticexyz/mud/commit/6173b596713b0dad73d14288ece3ac66ca3972d3)) - -### BREAKING CHANGES - -- sync worker update stream returns component id instead of component key - -- test(network): add tests for sync utils and SyncWorker logic - -- chore: remove logs and improve comments - -- chore: add logs - -Co-authored-by: andrii dobroshynski <24281657+andriidski@users.noreply.github.com> - -## [0.5.1](https://github.com/latticexyz/mud/compare/v0.5.0...v0.5.1) (2022-08-05) - -**Note:** Version bump only for package @latticexyz/network - -# [0.5.0](https://github.com/latticexyz/mud/compare/v0.4.3...v0.5.0) (2022-08-05) - -### Bug Fixes - -- CacheWorker ([#118](https://github.com/latticexyz/mud/issues/118)) ([bfe006e](https://github.com/latticexyz/mud/commit/bfe006e6adf064982a14d5dc1541d39b1b6016e2)) -- optimism, cancel action if gas check fails, add noise utils, fix ecs-browser entry point ([#119](https://github.com/latticexyz/mud/issues/119)) ([f35d3c3](https://github.com/latticexyz/mud/commit/f35d3c3cc7fc9385a215dfda6762a2a825c9fd6d)) - -### Features - -- logging library with support for topics/filters ([#123](https://github.com/latticexyz/mud/issues/123)) ([4eac3c6](https://github.com/latticexyz/mud/commit/4eac3c6f45cf300c683397d68e405001d31d8dda)) - -## [0.4.3](https://github.com/latticexyz/mud/compare/v0.4.2...v0.4.3) (2022-07-30) - -**Note:** Version bump only for package @latticexyz/network - -## [0.4.2](https://github.com/latticexyz/mud/compare/v0.4.1...v0.4.2) (2022-07-29) - -**Note:** Version bump only for package @latticexyz/network - -## [0.4.1](https://github.com/latticexyz/mud/compare/v0.4.0...v0.4.1) (2022-07-29) - -**Note:** Version bump only for package @latticexyz/network - -# [0.4.0](https://github.com/latticexyz/mud/compare/v0.3.2...v0.4.0) (2022-07-29) - -### Bug Fixes - -- **cli:** extract encoded arguments from signature ([#116](https://github.com/latticexyz/mud/issues/116)) ([f630319](https://github.com/latticexyz/mud/commit/f6303194c5d7147a64542e43669fddebf3abad1a)) - -### Features - -- **network:** faster execution of multiple tx, better revert message logging ([#111](https://github.com/latticexyz/mud/issues/111)) ([bee34dc](https://github.com/latticexyz/mud/commit/bee34dc38194bd54d02cfb7f763025359b49fb05)) - -## [0.3.2](https://github.com/latticexyz/mud/compare/v0.3.1...v0.3.2) (2022-07-26) - -**Note:** Version bump only for package @latticexyz/network - -## [0.3.1](https://github.com/latticexyz/mud/compare/v0.3.0...v0.3.1) (2022-07-26) - -**Note:** Version bump only for package @latticexyz/network - -# [0.3.0](https://github.com/latticexyz/mud/compare/v0.2.0...v0.3.0) (2022-07-26) - -### Bug Fixes - -- use hardhat as node (better logs) and make hardhat compatible with forge ([#54](https://github.com/latticexyz/mud/issues/54)) ([45a5981](https://github.com/latticexyz/mud/commit/45a5981a07f330b222775c0ad797db677f9e8897)) - -### Features - -- mudwar prototype (nyc sprint 2) ([#59](https://github.com/latticexyz/mud/issues/59)) ([a3db20e](https://github.com/latticexyz/mud/commit/a3db20e14c641b8b456775ee191eca6f016d47f5)), closes [#58](https://github.com/latticexyz/mud/issues/58) [#61](https://github.com/latticexyz/mud/issues/61) [#64](https://github.com/latticexyz/mud/issues/64) [#62](https://github.com/latticexyz/mud/issues/62) [#66](https://github.com/latticexyz/mud/issues/66) [#69](https://github.com/latticexyz/mud/issues/69) [#72](https://github.com/latticexyz/mud/issues/72) [#73](https://github.com/latticexyz/mud/issues/73) [#74](https://github.com/latticexyz/mud/issues/74) [#76](https://github.com/latticexyz/mud/issues/76) [#75](https://github.com/latticexyz/mud/issues/75) [#77](https://github.com/latticexyz/mud/issues/77) [#78](https://github.com/latticexyz/mud/issues/78) [#79](https://github.com/latticexyz/mud/issues/79) [#80](https://github.com/latticexyz/mud/issues/80) [#82](https://github.com/latticexyz/mud/issues/82) [#86](https://github.com/latticexyz/mud/issues/86) [#83](https://github.com/latticexyz/mud/issues/83) [#81](https://github.com/latticexyz/mud/issues/81) [#85](https://github.com/latticexyz/mud/issues/85) [#84](https://github.com/latticexyz/mud/issues/84) [#87](https://github.com/latticexyz/mud/issues/87) [#91](https://github.com/latticexyz/mud/issues/91) [#88](https://github.com/latticexyz/mud/issues/88) [#90](https://github.com/latticexyz/mud/issues/90) [#92](https://github.com/latticexyz/mud/issues/92) [#93](https://github.com/latticexyz/mud/issues/93) [#89](https://github.com/latticexyz/mud/issues/89) [#94](https://github.com/latticexyz/mud/issues/94) [#95](https://github.com/latticexyz/mud/issues/95) [#98](https://github.com/latticexyz/mud/issues/98) [#100](https://github.com/latticexyz/mud/issues/100) [#97](https://github.com/latticexyz/mud/issues/97) [#101](https://github.com/latticexyz/mud/issues/101) [#105](https://github.com/latticexyz/mud/issues/105) [#106](https://github.com/latticexyz/mud/issues/106) -- new systems pattern ([#63](https://github.com/latticexyz/mud/issues/63)) ([fb6197b](https://github.com/latticexyz/mud/commit/fb6197b997eb7232e38ecfb9116ff256491dc38c)) - -# [0.2.0](https://github.com/latticexyz/mud/compare/v0.1.8...v0.2.0) (2022-07-05) - -### Features - -- add webworker architecture for contract/client sync, add cache webworker ([#10](https://github.com/latticexyz/mud/issues/10)) ([4ef9f90](https://github.com/latticexyz/mud/commit/4ef9f909d1d3c10f6bea888b2c32b1d1df04185a)), closes [#14](https://github.com/latticexyz/mud/issues/14) -- component browser 📈 ([#16](https://github.com/latticexyz/mud/issues/16)) ([37af75e](https://github.com/latticexyz/mud/commit/37af75ecb11266e5877d04cb3224698605b87646)) -- **network:** integrate snapshot service ([#24](https://github.com/latticexyz/mud/issues/24)) ([a146164](https://github.com/latticexyz/mud/commit/a146164e1ccab77b88499c213b21f60270ed714b)) -- on-chain maps (nyc sprint 1) ([#38](https://github.com/latticexyz/mud/issues/38)) ([089c46d](https://github.com/latticexyz/mud/commit/089c46d7c0e112d1670e3bcd01a35f08ee21d593)), closes [#17](https://github.com/latticexyz/mud/issues/17) [#20](https://github.com/latticexyz/mud/issues/20) [#18](https://github.com/latticexyz/mud/issues/18) [#25](https://github.com/latticexyz/mud/issues/25) [#26](https://github.com/latticexyz/mud/issues/26) [#27](https://github.com/latticexyz/mud/issues/27) [#28](https://github.com/latticexyz/mud/issues/28) [#29](https://github.com/latticexyz/mud/issues/29) [#30](https://github.com/latticexyz/mud/issues/30) [#31](https://github.com/latticexyz/mud/issues/31) [#33](https://github.com/latticexyz/mud/issues/33) [#32](https://github.com/latticexyz/mud/issues/32) [#34](https://github.com/latticexyz/mud/issues/34) [#35](https://github.com/latticexyz/mud/issues/35) [#36](https://github.com/latticexyz/mud/issues/36) [#37](https://github.com/latticexyz/mud/issues/37) [#39](https://github.com/latticexyz/mud/issues/39) [#40](https://github.com/latticexyz/mud/issues/40) [#41](https://github.com/latticexyz/mud/issues/41) [#42](https://github.com/latticexyz/mud/issues/42) [#43](https://github.com/latticexyz/mud/issues/43) [#44](https://github.com/latticexyz/mud/issues/44) [#45](https://github.com/latticexyz/mud/issues/45) [#46](https://github.com/latticexyz/mud/issues/46) [#48](https://github.com/latticexyz/mud/issues/48) [#49](https://github.com/latticexyz/mud/issues/49) [#50](https://github.com/latticexyz/mud/issues/50) -- **recs:** rewrite for performance improvements (without integrating in ri) ([#22](https://github.com/latticexyz/mud/issues/22)) ([887564d](https://github.com/latticexyz/mud/commit/887564dbe0fad4250b82fd29d144305f176e3b89)) - -### BREAKING CHANGES - -- Components have to implement a getSchema() function - -- feat(network): make Sync worker return a stream of ECS events (prev contract events) - -- feat(ri-contracts): integrate solecs change (add getSchema to components) - -- feat(ri-client): integrate network package changes - -- feat(network): store ECS state in cache - -- feat(network): load state from cache - -- feat(utils): add more utils for iterables - -- refactor(network): clean up - -- feat(network): generalize component value decoder function, add tests - -- fix(network): make it possible to subscribe to ecsStream from sync worker multiple times - -- fix(network): start sync from provided initial block number - -- feat(network): move storing ecs to indexDB to its own Cache worker - -- feat(network): create separate cache for every World contract address - -- fix(network): fix issues discovered during live review - -- chore: remove unused import - -- Update packages/network/src/createBlockNumberStream.ts - -Co-authored-by: ludens - -- feat(network): add clock syncInterval as config parameter - -- feat(utils): emit values through componentToStream and observableToStream only if non-null - -- feat(network): add chain id to cache id, disable loading from cache on hardhat - -- fix(contracts): change Position and EntityType schema to int32/uint32 to fit in js number - -- docs(client): fix typos in comments - -- fix(network): fix tests - -- fix(scripting): integrate new network package into ri scripting - -- fix(network): fix sending multiple requests for component schema if many events get reduced - -## [0.1.8](https://github.com/latticexyz/mud/compare/v0.1.7...v0.1.8) (2022-05-25) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.7](https://github.com/latticexyz/mud/compare/v0.1.6...v0.1.7) (2022-05-25) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.6](https://github.com/latticexyz/mud/compare/v0.1.5...v0.1.6) (2022-05-25) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.5](https://github.com/latticexyz/mud/compare/v0.1.4...v0.1.5) (2022-05-24) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.4](https://github.com/latticexyz/mud/compare/v0.1.3...v0.1.4) (2022-05-24) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.3](https://github.com/latticexyz/mud/compare/v0.1.2...v0.1.3) (2022-05-23) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.2](https://github.com/latticexyz/mud/compare/v0.1.1...v0.1.2) (2022-05-23) - -**Note:** Version bump only for package @latticexyz/network - -## [0.1.1](https://github.com/latticexyz/mud/compare/v0.1.0...v0.1.1) (2022-05-23) - -**Note:** Version bump only for package @latticexyz/network - -# 0.1.0 (2022-05-23) - -### Bug Fixes - -- **@mud/network:** do not increase nonce for view functions ([233c4b5](https://github.com/latticexyz/mud/commit/233c4b51c9cb11ab40fa2c299c2303bc195b6a10)) -- **@mud/network:** use component id for ecs event mapping (instead of address) ([baa5f10](https://github.com/latticexyz/mud/commit/baa5f101796086bff7123186e8e0eba1941d20d0)) -- **@mud/network:** use component id instead of address for mapping ([39b516c](https://github.com/latticexyz/mud/commit/39b516c477b7e430ef0d00064c65f03afe29d1b4)) - -### Features - -- **@mud/network:** add @mud/network ([9a29452](https://github.com/latticexyz/mud/commit/9a29452e76b743f4bf1de3599eb0755fbcb93533)) -- **@mud/network:** add option to ignore tx confirmation to txQueue, add ready state, add fetch log ([438549a](https://github.com/latticexyz/mud/commit/438549adf92c42bb987eec46015c9c6f2235be80)) - -### Performance Improvements - -- **@mud/network:** add initial sync in stages ([d0c026a](https://github.com/latticexyz/mud/commit/d0c026a51bd8570c00517f8502485465d58bc5bb)) -- **@mud/network:** move sync and processing of chain events to a webworker ([dad52ea](https://github.com/latticexyz/mud/commit/dad52eaad4a4d8e67582bde8130455159173f609)) diff --git a/packages/network/README.md b/packages/network/README.md deleted file mode 100644 index 5b28b4729b..0000000000 --- a/packages/network/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# Network - -This package includes general low level utilities to interact with Ethereum contracts, as well as specialized MUD contract/client sync utilities. - -The general utilities can be used without MUD. For the specialized but much more powerful utilities, usage of `solecs` is required and `recs` is recommended. - -See [mud.dev/network](https://mud.dev/network) for the detailed API documentation. - -# Example - -This is a real-world example from an upcoming game built with MUD. - -```typescript -// setupContracts.ts - -import { - createNetwork, - createContracts, - Mappings, - createTxQueue, - createSyncWorker, - createEncoder, - NetworkComponentUpdate, - createSystemExecutor, -} from "@latticexyz/network"; - -import { Component as SolecsComponent, World as WorldContract } from "@latticexyz/solecs"; -import WorldAbi from "@latticexyz/solecs/abi/World.sol/World.abi.json"; -import ComponentAbi from "@latticexyz/solecs/abi/Component.sol/Component.json"; - -import { - Component, - Components, - EntityIndex, - getComponentEntities, - getComponentValueStrict, - removeComponent, - Schema, - setComponent, - Type, - World, -} from "@latticexyz/recs"; - -import { keccak256, stretch, toEthAddress } from "@latticexyz/utils"; - -import { defineStringComponent } from "@latticexyz/std-client"; - -import { bufferTime, filter, Observable, Subject } from "rxjs"; -import { computed, IComputedValue } from "mobx"; -import { Contract, ethers, Signer, Wallet } from "ethers"; -import { JsonRpcProvider } from "@ethersproject/providers"; - -import { SystemTypes } from "/types/SystemTypes"; -import { SystemAbis } from "/types/SystemAbis.mjs"; -import { config } from "./config"; - -export type ContractComponents = { - [key: string]: Component; -}; - -export async function setupContracts(world: World, components: C) { - const SystemsRegistry = defineStringComponent(world, { - id: "SystemsRegistry", - metadata: { contractId: "world.component.systems" }, - }); - - const ComponentsRegistry = defineStringComponent(world, { - id: "ComponentsRegistry", - metadata: { contractId: "world.component.components" }, - }); - - components = { - ...components, - SystemsRegistry, - ComponentsRegistry, - }; - - const mappings: Mappings = {}; - - for (const key of Object.keys(components)) { - const { contractId } = components[key].metadata; - mappings[keccak256(contractId)] = key; - } - - const network = await createNetwork(config); - - const signerOrProvider = computed(() => network.signer.get() || network.providers.get().json); - - const { contracts, config: contractsConfig } = await createContracts<{ World: WorldContract }>({ - config: { World: { abi: WorldAbi, address: config.worldAddress } }, - signerOrProvider, - }); - - const { txQueue, dispose: disposeTxQueue } = createTxQueue(contracts, network); - - const systems = createSystemExecutor(world, network, SystemsRegistry, SystemAbis, { - devMode: config.devMode, - }); - - const { ecsEvent$, config$, dispose } = createSyncWorker(); - - function startSync() { - config$.next({ - provider: networkConfig.provider, - worldContract: contractsConfig.World, - initialBlockNumber: config.initialBlockNumber ?? 0, - chainId: config.chainId, - disableCache: false, - snapshotServiceUrl: networkConfig.snapshotServiceUrl, - streamServiceUrl: networkConfig.streamServiceUrl, - }); - } - - const { txReduced$ } = applyNetworkUpdates(world, components, ecsEvent$, mappings); - - const encoders = createEncoders(world, ComponentsRegistry, signerOrProvider); - - return { txQueue, txReduced$, encoders, network, startSync, systems }; -} - -async function createEncoders( - world: World, - components: Component<{ value: Type.String }>, - signerOrProvider: IComputedValue -) { - const encoders = {} as Record>; - - async function fetchAndCreateEncoder(entity: EntityIndex) { - const componentAddress = toEthAddress(world.entities[entity]); - const componentId = getComponentValueStrict(components, entity).value; - const componentContract = new Contract( - componentAddress, - ComponentAbi.abi, - signerOrProvider.get() - ) as SolecsComponent; - const [componentSchemaPropNames, componentSchemaTypes] = await componentContract.getSchema(); - encoders[componentId] = createEncoder(componentSchemaPropNames, componentSchemaTypes); - } - - // Initial setup - for (const entity of getComponentEntities(components)) fetchAndCreateEncoder(entity); - - // Keep up to date - const subscription = components.update$.subscribe((update) => fetchAndCreateEncoder(update.entity)); - world.registerDisposer(() => subscription?.unsubscribe()); - - return encoders; -} - -/** - * Sets up synchronization between contract components and client components - */ -function applyNetworkUpdates( - world: World, - components: C, - ecsEvent$: Observable>, - mappings: Mappings -) { - const txReduced$ = new Subject(); - - const ecsEventSub = ecsEvent$.subscribe((update) => { - const entityIndex = world.entityToIndex.get(update.entity) ?? world.registerEntity({ id: update.entity }); - const componentKey = mappings[update.component]; - - if (update.value === undefined) { - // undefined value means component removed - removeComponent(components[componentKey] as Component, entityIndex); - } else { - setComponent(components[componentKey] as Component, entityIndex, update.value); - } - - if (update.lastEventInTx) txReduced$.next(update.txHash); - }); - - return { txReduced$: txReduced$.asObservable() }; -} -``` diff --git a/packages/network/jest.config.js b/packages/network/jest.config.js deleted file mode 100644 index 5ad5adee62..0000000000 --- a/packages/network/jest.config.js +++ /dev/null @@ -1,23 +0,0 @@ -/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -export default { - preset: "ts-jest", - roots: ["src"], - testEnvironment: "jsdom", - setupFilesAfterEnv: ["./jest.setup.ts"], - moduleNameMapper: { - // fix TS build issues - "^(..?/.*).js$": "$1", - // jest can't handle esm imports, so we import the typescript source instead - "^@latticexyz/common$": "/../common/src/index.ts", - "^@latticexyz/common/chains$": "/../common/src/chains/index.ts", - "^@latticexyz/common/deprecated$": "/../common/src/deprecated/index.ts", - "^@latticexyz/common/utils$": "/../common/src/utils/index.ts", - "^@latticexyz/recs$": "/../recs/src/index.ts", - "^@latticexyz/schema-type$": "/../schema-type/src/typescript/index.ts", - "^@latticexyz/schema-type/deprecated$": "/../schema-type/src/typescript/deprecated/index.ts", - "^@latticexyz/services/ecs-snapshot$": "/../services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts", - "^@latticexyz/services/ecs-stream$": "/../services/protobuf/ts/ecs-stream/ecs-stream.ts", - "^@latticexyz/services/mode$": "/../services/protobuf/ts/mode/mode.ts", - "^@latticexyz/utils$": "/../utils/src/index.ts", - }, -}; diff --git a/packages/network/jest.setup.ts b/packages/network/jest.setup.ts deleted file mode 100644 index 6ee6314169..0000000000 --- a/packages/network/jest.setup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TextEncoder as NodeTextEncoder, TextDecoder as NodeTextDecoder } from "util"; - -if (typeof globalThis.TextEncoder === "undefined") { - globalThis.TextEncoder = NodeTextEncoder as unknown as typeof TextEncoder; -} - -if (typeof globalThis.TextDecoder === "undefined") { - globalThis.TextDecoder = NodeTextDecoder as unknown as typeof TextDecoder; -} diff --git a/packages/network/package.json b/packages/network/package.json deleted file mode 100644 index 57769e4f93..0000000000 --- a/packages/network/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@latticexyz/network", - "version": "2.0.0-next.2", - "repository": { - "type": "git", - "url": "https://github.com/latticexyz/mud.git", - "directory": "packages/network" - }, - "license": "MIT", - "type": "module", - "exports": { - ".": "./dist/index.js", - "./dev": "./dist/dev/index.js" - }, - "typesVersions": { - "*": { - "index": [ - "./src/index.ts" - ], - "dev": [ - "./src/dev/index.ts" - ] - } - }, - "scripts": { - "build": "pnpm run build:js", - "build:js": "tsup", - "clean": "pnpm run clean:js", - "clean:js": "rimraf dist", - "dev": "tsup --watch", - "lint": "eslint . --ext .ts", - "test": "tsc --noEmit && jest --forceExit" - }, - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/providers": "^5.7.2", - "@improbable-eng/grpc-web": "^0.15.0", - "@latticexyz/common": "workspace:*", - "@latticexyz/recs": "workspace:*", - "@latticexyz/schema-type": "workspace:*", - "@latticexyz/services": "workspace:*", - "@latticexyz/solecs": "workspace:*", - "@latticexyz/store": "workspace:*", - "@latticexyz/utils": "workspace:*", - "@latticexyz/world": "workspace:*", - "async-mutex": "^0.3.2", - "debug": "^4.3.4", - "ethers": "^5.7.2", - "lodash": "^4.17.21", - "mobx": "^6.7.0", - "nice-grpc-web": "^2.0.1", - "rxjs": "7.5.5", - "threads": "^1.7.0", - "viem": "1.6.0" - }, - "devDependencies": { - "@types/debug": "^4.1.7", - "@types/jest": "^27.4.1", - "@types/lodash": "^4.14.182", - "@types/node": "^18.15.11", - "fake-indexeddb": "^4.0.0", - "jest": "^29.3.1", - "jest-environment-jsdom": "^29.3.1", - "ts-jest": "^29.0.5", - "tsup": "^6.7.0" - }, - "gitHead": "914a1e0ae4a573d685841ca2ea921435057deb8f" -} diff --git a/packages/network/src/createBlockNumberStream.ts b/packages/network/src/createBlockNumberStream.ts deleted file mode 100644 index e7ff2d70c3..0000000000 --- a/packages/network/src/createBlockNumberStream.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { stretch } from "@latticexyz/utils"; -import { IComputedValue, reaction } from "mobx"; -import { concat, concatMap, EMPTY, endWith, filter, map, range, ReplaySubject, take } from "rxjs"; -import { Providers } from "./createProvider"; - -/** - * Creates a stream of block numbers based on the `block` event of the currently connected provider. - * In case `initialSync` is provided, this stream will also output a stream of past block numbers to drive replaying events. - * - * @param providers Mobx computed providers object (created by {@link createReconnectingProvider}). - * @param options - * @returns Stream of block numbers based on connected provider's `block` event. - */ -export function createBlockNumberStream( - providers: IComputedValue, - options?: { - initialSync?: { - initialBlockNumber: number; - interval: number; - }; - } -) { - const blockNumberEvent$ = new ReplaySubject(1); - - const initialSync$ = options?.initialSync - ? blockNumberEvent$.pipe( - take(1), // Take the first block number - filter((blockNr) => blockNr > (options.initialSync!.initialBlockNumber || 0)), // Only do inital sync if the first block number we receive is larger than the block number to start from - concatMap((blockNr) => { - // Create a stepped range that ends with the current number - const blocksToSync = blockNr - options.initialSync!.initialBlockNumber; - return range(0, Math.ceil(blocksToSync / options.initialSync!.interval)).pipe( - map((i) => options.initialSync!.initialBlockNumber + i * options.initialSync!.interval), - endWith(blockNr) - ); - }), - stretch(50) // Stretch processing of block number to one every 32 milliseconds (during initial sync) - ) - : EMPTY; - - const dispose = reaction( - () => providers.get(), - (currProviders) => { - const provider = currProviders?.ws || currProviders?.json; - - let streamEmpty = true; - // Get the current block number (skipped if a new block arrives faster) - provider?.getBlockNumber().then((blockNumber) => { - if (streamEmpty) { - blockNumberEvent$.next(blockNumber); - } - }); - // Stream new block numbers - provider?.on("block", (blockNumber: number) => { - streamEmpty = false; - blockNumberEvent$.next(blockNumber); - }); - }, - { fireImmediately: true } - ); - - const blockNumber$ = concat(initialSync$, blockNumberEvent$); - - return { blockNumber$, dispose }; -} diff --git a/packages/network/src/createClock.spec.ts b/packages/network/src/createClock.spec.ts deleted file mode 100644 index dd4594333d..0000000000 --- a/packages/network/src/createClock.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Clock } from "./types"; -import { createClock } from "./createClock"; - -describe("Clock", () => { - let clock: Clock; - - beforeAll(() => { - jest.useFakeTimers(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - - beforeEach(() => { - clock = createClock({ period: 1000, initialTime: 0, syncInterval: 1000 }); - }); - - it("should emit a value every period", () => { - // - const mock = jest.fn(); - clock.time$.subscribe((time) => { - mock(time); - }); - jest.advanceTimersByTime(5000); - expect(mock).toHaveBeenCalledTimes(6); - expect(mock).toHaveBeenNthCalledWith(1, 0); - expect(mock).toHaveBeenNthCalledWith(2, 1000); - expect(mock).toHaveBeenNthCalledWith(3, 2000); - expect(mock).toHaveBeenNthCalledWith(4, 3000); - expect(mock).toHaveBeenNthCalledWith(5, 4000); - expect(mock).toHaveBeenNthCalledWith(6, 5000); - expect(clock.currentTime).toBe(5000); - }); - - it("should start a new interval if the clock is ticked externally", () => { - // - const mock = jest.fn(); - clock.time$.subscribe((time) => { - mock(time); - }); - - jest.advanceTimersByTime(2500); - - // Before updating the lastFreshTime should be 0 - expect(clock.lastUpdateTime).toBe(0); - - // Externally setting the time - clock.update(10000); - - jest.advanceTimersByTime(2500); - - expect(mock).toHaveBeenCalledTimes(6); - - // First three calls from initial interval - expect(mock).toHaveBeenNthCalledWith(1, 0); - expect(mock).toHaveBeenNthCalledWith(2, 1000); - expect(mock).toHaveBeenNthCalledWith(3, 2000); - - // One call from setting the new time - expect(mock).toHaveBeenNthCalledWith(4, 10000); - - // Two more calls from the second interval - expect(mock).toHaveBeenNthCalledWith(5, 11000); - expect(mock).toHaveBeenNthCalledWith(6, 12000); - - expect(clock.lastUpdateTime).toBe(10000); - expect(clock.currentTime).toBe(12000); - }); -}); diff --git a/packages/network/src/createClock.ts b/packages/network/src/createClock.ts deleted file mode 100644 index 673fdb6717..0000000000 --- a/packages/network/src/createClock.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ReplaySubject } from "rxjs"; -import { Clock, ClockConfig } from "./types"; - -/** - * Create a clock optimistically keeping track of the current chain time. - * The optimisitic chain time should be synced to the actual chain time in regular intervals using the `update` function. - * - * @param config - * @returns: {@link Clock} - */ -export function createClock(config: ClockConfig): Clock { - const { initialTime, period } = config; - - const clock = { - currentTime: initialTime, - lastUpdateTime: initialTime, - time$: new ReplaySubject(1), - dispose: () => clearInterval(intervalId), - update, - }; - - let intervalId = createTickInterval(); - emit(); - - function emit() { - clock.time$.next(clock.currentTime); - } - - function createTickInterval() { - return setInterval(() => { - clock.currentTime += period; - emit(); - }, period); - } - - function update(time: number) { - clearInterval(intervalId); - clock.currentTime = time; - clock.lastUpdateTime = time; - emit(); - intervalId = createTickInterval(); - } - - return clock; -} diff --git a/packages/network/src/createContracts.ts b/packages/network/src/createContracts.ts deleted file mode 100644 index a353850b30..0000000000 --- a/packages/network/src/createContracts.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Contracts, ContractsConfig } from "./types"; -import { Contract, Signer } from "ethers"; -import { Provider } from "@ethersproject/providers"; -import { computed, IComputedValue } from "mobx"; -import { mapObject } from "@latticexyz/utils"; - -/** - * Create an object of contracts connected to the currently connected provider. - * - * @param config: {@link ContractsConfig} - * @returns Object with contracts connected to the currently connected provider. - */ -export async function createContracts({ - config, - asyncConfig, - signerOrProvider, -}: { - config: Partial>; - asyncConfig?: (contracts: C) => Promise>>; - signerOrProvider: IComputedValue; -}): Promise<{ contracts: IComputedValue; config: ContractsConfig }> { - const contracts = computed(() => - mapObject>, C>( - config, - (c) => c && (new Contract(c.address, c.abi, signerOrProvider.get()) as C[keyof C]) - ) - ); - - if (!asyncConfig) return { contracts, config: config as ContractsConfig }; - - const asyncConfigResult = await asyncConfig(contracts.get()); - - const asyncContracts = computed(() => - mapObject>, C>( - asyncConfigResult, - (c) => c && (new Contract(c.address, c.abi, signerOrProvider.get()) as C[keyof C]) - ) - ); - - return { - contracts: computed(() => ({ ...contracts.get(), ...asyncContracts.get() })), - config: { ...config, ...asyncConfigResult } as ContractsConfig, - }; -} diff --git a/packages/network/src/createDecoder.spec.ts b/packages/network/src/createDecoder.spec.ts deleted file mode 100644 index 9aad8b1d9f..0000000000 --- a/packages/network/src/createDecoder.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { createDecoder, flattenValue } from "./createDecoder"; -import { createEncoder } from "./createEncoder"; -import { BigNumber } from "ethers"; -import { ContractSchemaValue } from "./types"; -import { defaultAbiCoder as abi } from "ethers/lib/utils.js"; - -describe("Decoder", () => { - it("decodes the component value", () => { - const decoder = createDecoder<{ first: string; second: number; third: string[]; fourth: boolean }>( - ["first", "second", "third", "fourth"], - [ - ContractSchemaValue.UINT256, - ContractSchemaValue.INT32, - ContractSchemaValue.UINT256_ARRAY, - ContractSchemaValue.BOOL, - ] - ); - const encoded = abi.encode(["uint256", "int32", "uint256[]", "bool"], [1, 2, [3, 4], true]); - expect(decoder(encoded)).toEqual({ first: "0x01", second: 2, third: ["0x03", "0x04"], fourth: true }); - }); - - it("can encode and decode a component value", () => { - const propNames = ["first", "second", "third", "fourth"] as ["first", "second", "third", "fourth"]; - const types = [ - ContractSchemaValue.UINT256, - ContractSchemaValue.INT32, - ContractSchemaValue.UINT256_ARRAY, - ContractSchemaValue.BOOL, - ]; - const decoder = createDecoder<{ first: string; second: number; third: string[]; fourth: boolean }>( - propNames, - types - ); - const value = { first: "0x01", second: 2, third: ["0x03", "0x04"], fourth: true }; - const encoded = createEncoder(propNames, types)(value); - expect(decoder(encoded)).toEqual(value); - }); - - describe("flattenValue", () => { - it("flattens BigNumberish values into the appropriate js type", () => { - expect(flattenValue(true, ContractSchemaValue.BOOL)).toBe(true); - expect(flattenValue(BigNumber.from(1), ContractSchemaValue.INT8)).toBe(1); - expect(flattenValue(BigNumber.from(1), ContractSchemaValue.INT64)).toBe("0x01"); - expect(flattenValue(BigNumber.from(1), ContractSchemaValue.STRING)).toBe("1"); - expect(flattenValue([BigNumber.from(1), BigNumber.from(2)], ContractSchemaValue.STRING_ARRAY)).toEqual([ - "1", - "2", - ]); - }); - }); -}); diff --git a/packages/network/src/createDecoder.ts b/packages/network/src/createDecoder.ts deleted file mode 100644 index f9be21bdcd..0000000000 --- a/packages/network/src/createDecoder.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { BigNumber } from "ethers"; -import { BytesLike, defaultAbiCoder as abi } from "ethers/lib/utils.js"; -import { - ContractSchemaValue, - ContractSchemaValueArrayToElement, - ContractSchemaValueId, - ContractSchemaValueTypes, -} from "./types"; - -export function flattenValue( - value: BigNumber | BigNumber[] | number | number[] | boolean | boolean[] | string | string[], - valueType: V -): ContractSchemaValueTypes[V] { - // If value is array, recursively flatten elements - if (Array.isArray(value)) - return value.map((v) => - flattenValue(v, ContractSchemaValueArrayToElement[valueType]) - ) as unknown as ContractSchemaValueTypes[V]; // Typescript things it is possible we return a nested array, but it is not - - // Value is already flat - if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") - return value as ContractSchemaValueTypes[V]; - - // The value returned by abi.decode is Hexable but not a ethers.BigNumber - value = BigNumber.from(value); - - // Value is a representable number - if ( - [ - ContractSchemaValue.INT8, - ContractSchemaValue.INT16, - ContractSchemaValue.INT32, - ContractSchemaValue.UINT8, - ContractSchemaValue.UINT16, - ContractSchemaValue.UINT32, - ].includes(valueType) - ) { - return value.toNumber() as ContractSchemaValueTypes[V]; - } - - // Value should be represented as a hex string - if ( - [ - ContractSchemaValue.INT64, - ContractSchemaValue.INT128, - ContractSchemaValue.INT256, - ContractSchemaValue.UINT64, - ContractSchemaValue.UINT128, - ContractSchemaValue.UINT256, - ContractSchemaValue.BYTES, - ContractSchemaValue.ADDRESS, - ContractSchemaValue.BYTES4, - ].includes(valueType) - ) { - return value.toHexString() as ContractSchemaValueTypes[V]; - } - - // Value should be represented a plain string - if ([ContractSchemaValue.STRING].includes(valueType)) { - return value.toString() as ContractSchemaValueTypes[V]; - } - - throw new Error("Unknown value type"); -} - -/** - * Construct a decoder function from given keys and valueTypes. - * The consumer is responsible for providing a type D matching the keys and valueTypes. - * - * @param keys Keys of the component value schema. - * @param valueTypes Value types if the component value schema. - * @returns Function to decode encoded hex value to component value. - */ -export function createDecoder( - keys: (keyof D)[], - valueTypes: ContractSchemaValue[] -): (data: BytesLike) => D { - return (data: BytesLike) => { - // Decode data with the schema values provided by the component - const decoded = abi.decode( - valueTypes.map((valueType) => ContractSchemaValueId[valueType]), - data - ); - - // Now keys and valueTypes lengths must match - if (keys.length !== valueTypes.length) { - throw new Error("Component schema keys and values length does not match"); - } - - // Construct the client component value - const result: Partial<{ [key in keyof D]: unknown }> = {}; - for (let i = 0; i < keys.length; i++) { - result[keys[i]] = flattenValue(decoded[i], valueTypes[i]); - } - - return result as D; - }; -} diff --git a/packages/network/src/createEncoder.ts b/packages/network/src/createEncoder.ts deleted file mode 100644 index 03b26bc68a..0000000000 --- a/packages/network/src/createEncoder.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ContractSchemaValue, ContractSchemaValueId } from "./types"; -import { defaultAbiCoder as abi } from "ethers/lib/utils.js"; - -/** - * Creates a function to automatically encode component values given a contract component schema. - * - * @param keys Schema keys - * @param valueTypes Schema value types - * @returns Function to encode component values - */ -export function createEncoder( - keys: (keyof D)[], - valueTypes: ContractSchemaValue[] -): (value: D) => string { - return (value) => { - const contractArgTypes = [] as string[]; - const contractArgs = Object.values(value); - - for (const componentValueProp of Object.keys(value)) { - const index = keys.findIndex((key) => key === componentValueProp); - contractArgTypes.push(ContractSchemaValueId[valueTypes[index] as ContractSchemaValue]); - } - - return abi.encode(contractArgTypes, contractArgs); - }; -} diff --git a/packages/network/src/createFastTxExecutor.ts b/packages/network/src/createFastTxExecutor.ts deleted file mode 100644 index cadf687d65..0000000000 --- a/packages/network/src/createFastTxExecutor.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { BigNumber, Contract, Overrides, Signer } from "ethers"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import * as devObservables from "./dev/observables"; - -/** - * Create a stateful util to execute transactions as fast as possible. - * Internal state includes the current nonce and the current gas price. - * - * Note: since the signer's nonce is managed in the internal state of this - * function, using the same signer to send transactions outside of this function - * or creating multiple instances of this function with the same signer will result - * in nonce errors. - */ -export async function createFastTxExecutor( - signer: Signer & { provider: JsonRpcProvider }, - globalOptions: { priorityFeeMultiplier: number } = { priorityFeeMultiplier: 1 } -) { - const chainId = await signer.getChainId(); - - const currentNonce = { - nonce: await signer.getTransactionCount(), - }; - - // This gas config is updated - const gasConfig: { - maxPriorityFeePerGas?: number; - maxFeePerGas?: BigNumber; - } = {}; - await updateFeePerGas(globalOptions.priorityFeeMultiplier); - - /** - * Execute a transaction as fast as possible by skipping a couple unnecessary RPC calls ethers does. - */ - async function fastTxExecute( - contract: C, - func: F, - args: Parameters, - options: { - retryCount?: number; - } = { retryCount: 0 } - ): Promise<{ hash: string; tx: ReturnType }> { - const functionName = `${func as string}(${args.map((arg) => `'${arg}'`).join(",")})`; - console.log(`executing transaction: ${functionName} with nonce ${currentNonce.nonce}`); - - try { - // Separate potential overrides from the args to extend the overrides below - const { argsWithoutOverrides, overrides } = separateOverridesFromArgs(args); - - // Estimate gas if no gas limit was provided - const gasLimit = overrides.gasLimit ?? (await contract.estimateGas[func as string].apply(null, args)); - - // Apply default overrides - const fullOverrides = { type: 2, gasLimit, nonce: currentNonce.nonce++, ...gasConfig, ...overrides }; - - // Populate the transaction - const populatedTx = await contract.populateTransaction[func as string](...argsWithoutOverrides, fullOverrides); - populatedTx.chainId = chainId; - - // Execute the transaction - let hash: string; - try { - // Attempt to sign the transaction and send it raw for higher performance - const signedTx = await signer.signTransaction(populatedTx); - hash = await signer.provider.perform("sendTransaction", { - signedTransaction: signedTx, - }); - } catch (e) { - // Some signers don't support signing without sending (looking at you MetaMask), - // so sign+send using the signer as a fallback - console.warn("signing failed, falling back to sendTransaction", e); - const tx = await signer.sendTransaction(populatedTx); - hash = tx.hash; - } - - // TODO: emit txs that fail gas estimation so we can display em in dev tools - devObservables.transactionHash$.next(hash); - - // Return the transaction promise and transaction hash. - // The hash is available immediately, the full transaction is available as a promise - const tx = signer.provider.getTransaction(hash) as ReturnType; - - return { hash, tx }; - } catch (error: any) { - // Handle "transaction already imported" errors - if (error?.message.includes("transaction already imported")) { - if (options.retryCount === 0) { - updateFeePerGas(globalOptions.priorityFeeMultiplier * 1.1); - return fastTxExecute(contract, func, args, { retryCount: options.retryCount++ }); - } else { - throw new Error(`Gas estimation error for ${functionName}: ${error?.reason}`); - } - } - - // TODO: potentially handle more transaction errors here, like: - // "insufficient funds for gas * price + value" -> request funds from faucet - // "invalid nonce" -> update nonce - - // Rethrow all other errors - throw error; - } - } - - /** - * Set the maxFeePerGas and maxPriorityFeePerGas based on the current base fee and the given multiplier. - * The multiplier is used to allow replacing pending transactions. - * @param multiplier Multiplier to apply to the base fee - */ - async function updateFeePerGas(multiplier: number) { - // Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions - const feeData = await signer.provider.getFeeData(); - if (!feeData.lastBaseFeePerGas) throw new Error("Can not fetch lastBaseFeePerGas from RPC"); - - // Set the priority fee to 0 for development chains with no base fee, to allow transactions from unfunded wallets - gasConfig.maxPriorityFeePerGas = feeData.lastBaseFeePerGas.eq(0) ? 0 : Math.floor(1_500_000_000 * multiplier); - gasConfig.maxFeePerGas = feeData.lastBaseFeePerGas.mul(2).add(gasConfig.maxPriorityFeePerGas); - } - - return { - fastTxExecute, - updateFeePerGas, - gasConfig: gasConfig as Readonly, - currentNonce: currentNonce as Readonly, - }; -} - -function separateOverridesFromArgs(args: Array) { - // Extract existing overrides from function call - const hasOverrides = args.length > 0 && isOverrides(args[args.length - 1]); - const overrides = (hasOverrides ? args[args.length - 1] : {}) as Overrides; - const argsWithoutOverrides = hasOverrides ? args.slice(0, args.length - 1) : args; - - return { argsWithoutOverrides, overrides }; -} - -function isOverrides(obj: any): obj is Overrides { - if (typeof obj !== "object" || Array.isArray(obj) || obj === null) return false; - return ( - "gasLimit" in obj || - "gasPrice" in obj || - "maxFeePerGas" in obj || - "maxPriorityFeePerGas" in obj || - "nonce" in obj || - "type" in obj || - "accessList" in obj || - "customData" in obj || - "value" in obj || - "blockTag" in obj || - "from" in obj - ); -} diff --git a/packages/network/src/createNetwork.spec.ts b/packages/network/src/createNetwork.spec.ts deleted file mode 100644 index 066794c4c6..0000000000 --- a/packages/network/src/createNetwork.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { sleep } from "@latticexyz/utils"; -import { reaction, runInAction } from "mobx"; -import { createNetwork, Network } from "./createNetwork"; - -const config = { - clock: { - period: 1000, - initialTime: 0, - syncInterval: 1000, - }, - provider: { - jsonRpcUrl: "https://rpc.gnosischain.com", - wsRpcUrl: "wss://rpc.gnosischain.com/wss", - options: { - batch: true, - pollingInterval: 1000, - skipNetworkCheck: true, - }, - chainId: 31337, - }, - chainId: 31337, -}; - -describe("Network", () => { - let network: Network; - beforeEach(async () => { - network = await createNetwork(config); - }); - - afterEach(() => { - network.dispose(); - }); - - it("updates the provider if the provider config changes", async () => { - const mock = jest.fn(); - // Call mock every time providers change - reaction(() => network.providers.get(), mock); - - expect(mock).toHaveBeenCalledTimes(0); - - runInAction(() => { - if (network.config?.provider?.options) network.config.provider.options.batch = false; - }); - - expect(mock).toHaveBeenCalledTimes(1); - }); - - it("updates the signer if the signer config changes", () => { - const mock = jest.fn(); - reaction(() => network.signer.get(), mock); - - expect(mock).toHaveBeenCalledTimes(0); - expect(network.signer.get()).toBeUndefined(); - - runInAction(() => { - network.config.privateKey = "0x044C7963E9A89D4F8B64AB23E02E97B2E00DD57FCB60F316AC69B77135003AEF"; - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(network.signer.get()).toBeDefined(); - - runInAction(() => { - network.config.privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; - }); - - expect(mock).toHaveBeenCalledTimes(2); - expect(network.signer.get()).toBeDefined(); - - runInAction(() => { - network.config.privateKey = undefined; - }); - - expect(mock).toHaveBeenCalledTimes(3); - expect(network.signer.get()).toBeUndefined(); - }); - - it("updates the singner if the provider changes", () => { - const mock = jest.fn(); - // Call mock every time providers change - reaction(() => network.signer.get(), mock); - - runInAction(() => { - network.config.privateKey = "0x044C7963E9A89D4F8B64AB23E02E97B2E00DD57FCB60F316AC69B77135003AEF"; - }); - - expect(mock).toHaveBeenCalledTimes(1); - - runInAction(() => { - if (network.config?.provider?.options) network.config.provider.options.batch = false; - }); - - expect(mock).toHaveBeenCalledTimes(2); - }); - - it.skip("listens to new block numbers", async () => { - const mock = jest.fn(); - network.blockNumber$.subscribe(mock); - await sleep(5000); - expect(mock).toHaveBeenCalledTimes(1); - }, 10000); -}); diff --git a/packages/network/src/createNetwork.ts b/packages/network/src/createNetwork.ts deleted file mode 100644 index ff959b0382..0000000000 --- a/packages/network/src/createNetwork.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { computed, observable, toJS } from "mobx"; -import { createSigner } from "./createSigner"; -import { createReconnectingProvider } from "./createProvider"; -import { NetworkConfig } from "./types"; -import { combineLatest, concatMap, EMPTY, filter, map, throttleTime } from "rxjs"; -import { createClock } from "./createClock"; -import { fetchBlock } from "./networkUtils"; -import { createBlockNumberStream } from "./createBlockNumberStream"; -import { Signer, Wallet } from "ethers"; -import { computedToStream } from "@latticexyz/utils"; -import { privateKeyToAccount } from "viem/accounts"; -import { Address, fallback, webSocket, http, createPublicClient, createWalletClient, Chain } from "viem"; -import * as mudChains from "@latticexyz/common/chains"; -import * as chains from "viem/chains"; -import * as devObservables from "./dev/observables"; - -export type Network = Awaited>; - -/** - * Set up network. - * - * @param initialConfig Initial config (see {@link NetworkConfig}). - * @returns Network object - */ -export async function createNetwork(initialConfig: NetworkConfig) { - const config = observable(initialConfig); - const disposers: (() => void)[] = []; - const { - providers, - connected, - dispose: disposeProvider, - } = await createReconnectingProvider(computed(() => toJS(config.provider))); - disposers.push(disposeProvider); - - // Create signer - const signer = computed(() => { - const currentProviders = providers.get(); - if (config.provider.externalProvider) return currentProviders.json.getSigner(); - const privateKey = config.privateKey; - if (privateKey && currentProviders) return createSigner(privateKey, currentProviders); - }); - - // Get address - const initialConnectedAddress = config.provider.externalProvider ? await signer.get()?.getAddress() : undefined; - const connectedAddress = computed(() => - config.privateKey ? new Wallet(config.privateKey).address.toLowerCase() : initialConnectedAddress?.toLowerCase() - ); - const connectedAddressChecksummed = computed(() => - config.privateKey ? new Wallet(config.privateKey).address : initialConnectedAddress - ); - - // Listen to new block numbers - const { blockNumber$, dispose: disposeBlockNumberStream } = createBlockNumberStream(providers); - disposers.push(disposeBlockNumberStream); - - // Create local clock - const clock = createClock(config.clock); - disposers.push(clock.dispose); - - // Sync the local time to the chain time in regular intervals - const syncBlockSub = combineLatest([blockNumber$, computedToStream(providers)]) - .pipe( - throttleTime(config.clock.syncInterval, undefined, { leading: true, trailing: true }), - concatMap(([blockNumber, currentProviders]) => - currentProviders ? fetchBlock(currentProviders.json, blockNumber) : EMPTY - ), // Fetch the latest block if a provider is available - map((block) => 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 - disposers.push(() => syncBlockSub?.unsubscribe()); - - // Create viem clients - try { - const possibleChains = Object.values({ ...mudChains, ...chains }) as Chain[]; - if (config.chainConfig) { - possibleChains.unshift(config.chainConfig); - } - const chain = possibleChains.find((c) => c.id === config.chainId); - if (!chain) { - throw new Error(`No chain found for chain ID ${config.chainId}`); - } - - const publicClient = createPublicClient({ - chain, - transport: fallback([webSocket(), http()]), - pollingInterval: config.provider.options?.pollingInterval ?? config.clock.period ?? 1000, - }); - const burnerAccount = config.privateKey ? privateKeyToAccount(config.privateKey as Address) : null; - const burnerWalletClient = burnerAccount - ? createWalletClient({ - account: burnerAccount, - chain, - transport: fallback([webSocket(), http()]), - pollingInterval: config.provider.options?.pollingInterval ?? config.clock.period ?? 1000, - }) - : null; - - devObservables.publicClient$.next(publicClient); - devObservables.walletClient$.next(burnerWalletClient); - } catch (error) { - console.error("Could not initialize viem clients, dev tools may not work:", error); - } - - return { - providers, - signer, - connected, - blockNumber$, - dispose: () => { - for (const disposer of disposers) disposer(); - }, - clock, - config, - connectedAddress, - connectedAddressChecksummed, - // TODO: TS complains about exporting these, figure out why - // publicClient, - // burnerWalletClient, - }; -} diff --git a/packages/network/src/createProvider.ts b/packages/network/src/createProvider.ts deleted file mode 100644 index e6077db0a7..0000000000 --- a/packages/network/src/createProvider.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { Networkish, Web3Provider, WebSocketProvider } from "@ethersproject/providers"; -import { callWithRetry, observableToComputed, timeoutAfter } from "@latticexyz/utils"; -import { IComputedValue, IObservableValue, observable, reaction, runInAction } from "mobx"; -import { ensureNetworkIsUp } from "./networkUtils"; -import { MUDJsonRpcBatchProvider, MUDJsonRpcProvider } from "./provider"; -import { ProviderConfig } from "./types"; - -export type Providers = ReturnType; - -/** - * Create a JsonRpcProvider and WebsocketProvider pair - * - * @param config Config for the provider pair (see {@link ProviderConfig}). - * @returns Provider pair: { - * json: JsonRpcProvider, - * ws: WebSocketProvider - * } - */ -export function createProvider({ chainId, jsonRpcUrl, wsRpcUrl, externalProvider, options }: ProviderConfig) { - const network: Networkish = { - chainId, - name: "mudChain", - }; - const providers = externalProvider - ? { json: new Web3Provider(externalProvider, network), ws: undefined } - : { - json: options?.batch - ? new MUDJsonRpcBatchProvider(jsonRpcUrl, network) - : new MUDJsonRpcProvider(jsonRpcUrl, network), - ws: wsRpcUrl ? new WebSocketProvider(wsRpcUrl, network) : undefined, - }; - - if (options?.pollingInterval) { - providers.json.pollingInterval = options.pollingInterval; - } - - return providers; -} - -export enum ConnectionState { - DISCONNECTED, - CONNECTING, - CONNECTED, -} - -/** - * Creates a {@link createProvider provider pair} that automatically updates if the config changes - * and automatically reconnects if the connection is lost. - * - * @param config Mobx computed provider config object (see {@link ProviderConfig}). - * Automatically updates the returned provider pair if the config changes. - * @returns Automatically reconnecting {@link createProvider provider pair} that updates if the config changes. - */ -export async function createReconnectingProvider(config: IComputedValue) { - const connected = observable.box(ConnectionState.DISCONNECTED); - const providers = observable.box() as IObservableValue; - const disposers: (() => void)[] = []; - - async function initProviders() { - // Abort if connection is currently being established - if (connected.get() === ConnectionState.CONNECTING) return; - - // Invalidate current providers - runInAction(() => connected.set(ConnectionState.CONNECTING)); - - // Remove listeners from stale providers and close open connections - const prevProviders = providers.get(); - prevProviders?.json.removeAllListeners(); - prevProviders?.ws?.removeAllListeners(); - try { - prevProviders?.ws?._websocket?.close(); - } catch { - // Ignore errors when closing websocket that was not in an open state - } - - const conf = config.get(); - - // Create new providers - await callWithRetry(async () => { - const newProviders = createProvider(conf); - // If the connection is not successful, this will throw an error, triggering a retry - !conf?.options?.skipNetworkCheck && (await ensureNetworkIsUp(newProviders.json, newProviders.ws)); - runInAction(() => { - providers.set(newProviders); - connected.set(ConnectionState.CONNECTED); - }); - }); - } - - // Create new providers if config changes - disposers.push( - reaction( - () => config.get(), - () => initProviders() - ) - ); - - // Reconnect providers in case of error - disposers.push( - reaction( - () => providers.get(), - (currentProviders) => { - if (currentProviders?.ws?._websocket) { - currentProviders.ws._websocket.onerror = initProviders; - currentProviders.ws._websocket.onclose = () => { - // Only reconnect if closed unexpectedly - if (connected.get() === ConnectionState.CONNECTED) { - initProviders(); - } - }; - } - } - ) - ); - - // Keep websocket connection alive - const keepAliveInterval = setInterval(async () => { - if (connected.get() !== ConnectionState.CONNECTED) return; - const currentProviders = providers.get(); - if (!currentProviders?.ws) return; - try { - await timeoutAfter(currentProviders.ws.getBlockNumber(), 10000, "Network Request Timed out"); - } catch { - initProviders(); - } - }, 10000); - disposers.push(() => clearInterval(keepAliveInterval)); - - await initProviders(); - - return { - connected: observableToComputed(connected), - providers: observableToComputed(providers), - dispose: () => { - for (const disposer of disposers) disposer(); - try { - providers.get()?.ws?._websocket?.close(); - } catch { - // Ignore error if websocket is not on OPEN state - } - }, - }; -} diff --git a/packages/network/src/createRelayStream.ts b/packages/network/src/createRelayStream.ts deleted file mode 100644 index 25688d31cf..0000000000 --- a/packages/network/src/createRelayStream.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Signer } from "ethers"; -import { from, map, Subject } from "rxjs"; -import { spawn } from "threads"; -import { messagePayload } from "./utils"; -import { createChannel, createClient } from "nice-grpc-web"; -import { awaitPromise, awaitStreamValue } from "@latticexyz/utils"; -import { grpc } from "@improbable-eng/grpc-web"; -import { ECSRelayServiceDefinition, Message, PushRequest } from "@latticexyz/services/ecs-relay"; - -/** - * Create a RelayService connection, including event$ and utils - * @param url ECSRelayService URL - * @param id User id (eg address) - * @returns RelayService connection - */ -export async function createRelayStream(signer: Signer, url: string, id: string) { - const httpClient = createClient(ECSRelayServiceDefinition, createChannel(url)); - const wsClient = createClient(ECSRelayServiceDefinition, createChannel(url, grpc.WebsocketTransport())); - - const recoverWorker = await spawn( - new Worker(new URL("./workers/Recover.worker.js", import.meta.url), { type: "module" }) - ); - - // Signature that should be used to prove identity - const signature = { signature: await signer.signMessage("ecs-relay-service") }; - await httpClient.authenticate(signature); - - // Subscribe to the stream of relayed events - const event$ = from(wsClient.openStream(signature)).pipe( - map(async (message) => ({ - message, - address: await recoverWorker.recoverAddress(message), - })), - awaitPromise() - ); - - // Subscribe to new labels - function subscribe(label: string) { - httpClient.subscribe({ signature, subscription: { label } }); - } - - // Unsubscribe from labels - function unsubscribe(label: string) { - httpClient.unsubscribe({ signature, subscription: { label } }); - } - - // Fetch amount of connected clients - async function countConnected(): Promise { - const { count } = await httpClient.countConnected({}); - return count; - } - - // Set up stream to push messages to the relay service - const push$ = new Subject(); - const generatorLoop = { done: false }; - - async function* pushGenerator(): AsyncIterable { - while (!generatorLoop.done) { - yield await awaitStreamValue(push$); - } - } - - // Open push stream - const responseSubscription = from(wsClient.pushStream(pushGenerator())).subscribe(); - - function dispose() { - generatorLoop.done = true; - responseSubscription?.unsubscribe(); - } - - // Expose method for consumers to push data through the stream - async function push(label: string, data: Uint8Array) { - const message: Message = { version: 1, id: Date.now() + id, timestamp: Date.now(), data, signature: "" }; - message.signature = await signer.signMessage(messagePayload(message)); - push$.next({ label, message }); - } - - // Expose method for consumers to ping the stream to keep receiving messages without pushing - function ping() { - return httpClient.ping(signature); - } - - return { event$, dispose, subscribe, unsubscribe, push, countConnected, ping }; -} diff --git a/packages/network/src/createSigner.ts b/packages/network/src/createSigner.ts deleted file mode 100644 index daf93dcd97..0000000000 --- a/packages/network/src/createSigner.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Wallet } from "ethers"; -import { Providers } from "./createProvider"; - -export function createSigner(privateKey: string, providers: Providers) { - return new Wallet(privateKey, providers.json); -} diff --git a/packages/network/src/createSyncWorker.ts b/packages/network/src/createSyncWorker.ts deleted file mode 100644 index 9d5bc03894..0000000000 --- a/packages/network/src/createSyncWorker.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Components } from "@latticexyz/recs"; -import { fromWorker } from "@latticexyz/utils"; -import { map, Observable, Subject, timer } from "rxjs"; -import { NetworkEvent } from "./types"; -import { Input, Ack, ack, SyncWorker } from "./workers/SyncWorker"; - -/** - * Create a new SyncWorker ({@link Sync.worker.ts}) to performn contract/client state sync. - * The main thread and worker communicate via RxJS streams. - * - * @returns Object { - * ecsEvent$: Stream of network component updates synced by the SyncWorker, - * config$: RxJS subject to pass in config for the SyncWorker, - * dispose: function to dispose of the sync worker - * } - */ -export function createSyncWorker( - ack$?: Observable, - options?: { thread?: "main" | "worker" } -) { - const thread = options?.thread || "worker"; - const input$ = new Subject(); - const ecsEvents$ = new Subject[]>(); - let dispose: () => void; - - // Send ack every 16ms if no external ack$ is provided - ack$ = ack$ || timer(0, 16).pipe(map(() => ack)); - const ackSub = ack$.subscribe(input$); - - // If thread option is "worker", create a new web worker to sync the state - if (thread === "worker") { - const worker = new Worker(new URL("./workers/Sync.worker.js", import.meta.url), { type: "module" }); - - // Pass in a "config stream", receive a stream of ECS events - const subscription = fromWorker[]>(worker, input$).subscribe(ecsEvents$); - dispose = () => { - worker.terminate(); - subscription?.unsubscribe(); - ackSub?.unsubscribe(); - }; - } else { - // Otherwise sync the state in the main thread - // Pass in a "config stream", receive a stream of ECS events - const subscription = new SyncWorker().work(input$).subscribe(ecsEvents$); - dispose = () => { - subscription?.unsubscribe(); - ackSub?.unsubscribe(); - }; - } - - return { - ecsEvents$, - input$, - dispose, - }; -} diff --git a/packages/network/src/createSystemExecutor.ts b/packages/network/src/createSystemExecutor.ts deleted file mode 100644 index 9e6bfef171..0000000000 --- a/packages/network/src/createSystemExecutor.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Provider } from "@ethersproject/providers"; -import { Component, Entity, getComponentEntities, getComponentValue, Type, World } from "@latticexyz/recs"; -import { deferred, keccak256, toEthAddress } from "@latticexyz/utils"; -import { Contract, ContractInterface, Signer } from "ethers"; -import { observable, runInAction } from "mobx"; -import { createTxQueue } from "./createTxQueue"; -import { Network } from "./createNetwork"; -import { BehaviorSubject } from "rxjs"; - -/** - * Create a system executor object. - * The system executor object is an object indexed by available system ids (given in the interfaces object) - * with {@link createTxQueue tx-queue enabled system contracts} as value. - * - * @param world Recs World object. - * @param network Network ({@link createNetwork}). - * @param systems Recs registry component containing the mapping from system address to system id. - * @param interfaces Interfaces of the systems to create. - * @param options - * @returns Systems object to call system contracts. - */ -export function createSystemExecutor( - world: World, - network: Network, - systems: Component<{ value: Type.String }>, - interfaces: { [key in keyof T]: ContractInterface }, - gasPrice$: BehaviorSubject, - options?: { devMode?: boolean; concurrency?: number } -) { - const systemContracts = observable.box({} as T); - const systemIdPreimages: { [key: string]: string } = Object.keys(interfaces).reduce((acc, curr) => { - return { ...acc, [keccak256(curr)]: curr }; - }, {}); - - // Util to add new systems to the systems tx queue - function registerSystem(system: { id: string; contract: Contract }) { - const [resolve, , promise] = deferred(); - runInAction(() => { - systemContracts.set({ ...systemContracts.get(), [system.id]: system.contract }); - systemIdPreimages[keccak256(system.id)] = system.id; - resolve(); - }); - - return promise; - } - - // Initialize systems - const initialContracts = {} as T; - for (const systemEntity of getComponentEntities(systems)) { - const system = createSystemContract(systemEntity, network.signer.get()); - if (!system) continue; - initialContracts[system.id as keyof T] = system.contract as T[keyof T]; - } - runInAction(() => systemContracts.set(initialContracts)); - - // Keep up to date - systems.update$.subscribe((update) => { - if (!update.value[0]) return; - const system = createSystemContract(update.entity, network.signer.get()); - if (!system) return; - registerSystem(system); - }); - - const { txQueue, dispose } = createTxQueue(systemContracts, network, gasPrice$, options); - world.registerDisposer(dispose); - - return { systems: txQueue, registerSystem, getSystemContract }; - - function getSystemContract(systemId: string) { - const name = systemIdPreimages[systemId]; - - return { - name, - contract: systemContracts.get()[name], - }; - } - - function createSystemContract( - entity: Entity, - signerOrProvider?: Signer | Provider - ): { id: string; contract: C } | undefined { - const { value: hashedSystemId } = getComponentValue(systems, entity) || {}; - if (!hashedSystemId) throw new Error("System entity not found"); - const id = systemIdPreimages[hashedSystemId]; - if (!id) { - console.warn("Unknown system:", hashedSystemId); - return; - } - return { - id, - contract: new Contract(toEthAddress(entity), interfaces[id], signerOrProvider) as C, - }; - } -} diff --git a/packages/network/src/createTopics.ts b/packages/network/src/createTopics.ts deleted file mode 100644 index 39db29741c..0000000000 --- a/packages/network/src/createTopics.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ethers, VoidSigner } from "ethers"; -import { ContractTopics } from "./types"; -import { Contracts } from "./types"; - -export type TopicsConfig = { - [ContractType in keyof C]: { - abi: ethers.ContractInterface; - topics: (keyof C[ContractType]["filters"])[]; - }; -}; - -export function createTopics(config: TopicsConfig): ContractTopics[] { - const contractTopics: ContractTopics[] = []; - for (const key of Object.keys(config)) { - const { abi, topics } = config[key]; - const dummyContract = new ethers.Contract( - ethers.constants.AddressZero, - abi, - new VoidSigner(ethers.constants.AddressZero) - ) as C[typeof key]; - const contractTopic = [ - topics - .map((t) => dummyContract.filters[t as string]().topics) - .map((topicsOrUndefined) => (topicsOrUndefined || [])[0]), - ] as Array>; - contractTopics.push({ - key, - topics: contractTopic, - }); - } - return contractTopics; -} diff --git a/packages/network/src/createTxQueue.ts b/packages/network/src/createTxQueue.ts deleted file mode 100644 index e79bdfb5a0..0000000000 --- a/packages/network/src/createTxQueue.ts +++ /dev/null @@ -1,358 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { BaseContract, BigNumberish, CallOverrides, Overrides } from "ethers"; -import { autorun, computed, IComputedValue, IObservableValue, observable, runInAction } from "mobx"; -import { mapObject, deferred, uuid, awaitValue, cacheUntilReady } from "@latticexyz/utils"; -import { Mutex } from "async-mutex"; -import { JsonRpcProvider, TransactionReceipt } from "@ethersproject/providers"; -import { Contracts, TxQueue } from "./types"; -import { ConnectionState } from "./createProvider"; -import { Network } from "./createNetwork"; -import { getRevertReason } from "./networkUtils"; -import { BehaviorSubject } from "rxjs"; - -type ReturnTypeStrict = T extends (...args: any) => any ? ReturnType : never; - -/** - * The TxQueue takes care of nonce management, concurrency and caching calls if the contracts are not connected. - * Cached calls are passed to the queue once the contracts are available. - * - * @param computedContracts A computed object containing the contracts to be channelled through the txQueue - * @param network A network object containing provider, signer, etc - * @param options The concurrency declares how many transactions can wait for confirmation at the same time. - * @returns TxQueue object - */ -export function createTxQueue( - computedContracts: IComputedValue | IObservableValue, - network: Network, - gasPrice$: BehaviorSubject, - options?: { concurrency?: number; devMode?: boolean } -): { txQueue: TxQueue; dispose: () => void; ready: IComputedValue } { - const { concurrency } = options || {}; - - const queue = createPriorityQueue<{ - execute: ( - nonce: number, - gasLimit: BigNumberish - ) => Promise<{ hash: string; wait: () => Promise }>; - estimateGas: () => BigNumberish | Promise; - cancel: (error: any) => void; - stateMutability?: string; - }>(); - const submissionMutex = new Mutex(); - const _nonce = observable.box(null); - - const readyState = computed(() => { - const connected = network.connected.get(); - const contracts = computedContracts.get(); - const signer = network.signer.get(); - const provider = network.providers.get()?.json; - const nonce = _nonce.get(); - - if (connected !== ConnectionState.CONNECTED || !contracts || !signer || !provider || nonce == null) - return undefined; - - return { contracts, signer, provider, nonce }; - }); - - let utilization = 0; - - async function resetNonce() { - runInAction(() => _nonce.set(null)); - const newNonce = (await network.signer.get()?.getTransactionCount()) ?? null; - runInAction(() => _nonce.set(newNonce)); - } - - // Set the nonce on init and reset if the signer changed - const dispose = autorun(resetNonce); - - function incNonce() { - runInAction(() => { - const currentNonce = _nonce.get(); - const newNonce = currentNonce == null ? null : currentNonce + 1; - _nonce.set(newNonce); - }); - } - - function queueCall( - target: C[keyof C], - prop: keyof C[keyof C], - args: unknown[] - ): Promise<{ - hash: string; - wait: () => Promise; - response: Promise>; - }> { - const [resolve, reject, promise] = deferred<{ - hash: string; - wait: () => Promise; - response: Promise>; - }>(); - - // Extract existing overrides from function call - const hasOverrides = args.length > 0 && isOverrides(args[args.length - 1]); - const overrides = (hasOverrides ? args[args.length - 1] : {}) as CallOverrides; - const argsWithoutOverrides = hasOverrides ? args.slice(0, args.length - 1) : args; - - // Store state mutability to know when to increase the nonce - const fragment = target.interface.fragments.find((fragment) => fragment.name === prop); - const stateMutability = fragment && (fragment as { stateMutability?: string }).stateMutability; - - // Create a function that estimates gas if no gas is provided - const gasLimit = overrides["gasLimit"]; - const estimateGas = gasLimit == null ? () => target.estimateGas[prop as string](...args) : () => gasLimit; - - // Create a function that executes the tx when called - const execute = async (nonce: number, gasLimit: BigNumberish) => { - try { - const member = target.populateTransaction[prop as string]; - if (member == undefined) { - throw new Error("Member does not exist."); - } - - if (!(member instanceof Function)) { - throw new Error( - `Internal TxQueue error: Member is not a function and should not be proxied. Tried to call "${String( - prop - )}".` - ); - } - - // Populate config - const configOverrides = { - gasPrice: gasPrice$.getValue(), - ...overrides, - nonce, - gasLimit, - }; - if (options?.devMode) configOverrides.gasPrice = 0; - - // Populate tx - const populatedTx = await member(...argsWithoutOverrides, configOverrides); - populatedTx.nonce = nonce; - populatedTx.chainId = network.config.chainId; - - // Execute tx - let hash: string; - try { - // Attempt to sign the transaction and send it raw for higher performance - const signedTx = await target.signer.signTransaction(populatedTx); - hash = await (target.provider as JsonRpcProvider).perform("sendTransaction", { - signedTransaction: signedTx, - }); - } catch (e) { - // Some signers don't support signing without sending (looking at you MetaMask), - // so sign+send using the signer as a fallback - const tx = await target.signer.sendTransaction(populatedTx); - hash = tx.hash; - } - const response = target.provider.getTransaction(hash) as Promise< - ReturnTypeStrict<(typeof target)[typeof prop]> - >; - // This promise is awaited asynchronously in the tx queue and the action queue to catch errors - const wait = async () => (await response).wait(); - - // Resolved value goes to the initiator of the transaction - resolve({ hash, wait, response }); - - // Returned value gets processed inside the tx queue - return { hash, wait }; - } catch (e) { - reject(e as Error); - throw e; // Rethrow error to catch when processing the queue - } - }; - - // Queue the tx execution - queue.add(uuid(), { - execute, - estimateGas, - cancel: (error?: any) => reject(error ?? new Error("TX_CANCELLED")), - stateMutability, - }); - - // Start processing the queue - processQueue(); - - // Promise resolves when the transaction is confirmed and is rejected if the - // transaction fails or is cancelled. - return promise; - } - - async function processQueue() { - // Don't enter if at max capacity - if (concurrency != null && utilization >= concurrency) return; - - // Check if there is a request to process - const txRequest = queue.next(); - if (!txRequest) return; - - // Increase utilization to prevent executing more tx than allowed by capacity - utilization++; - - // Start processing another request from the queue - // Note: we start processing again after increasing the utilization to process up to `concurrency` tx request in parallel. - // At the end of this function after decreasing the utilization we call processQueue again trigger tx requests waiting for capacity. - processQueue(); - - // Run exclusive to avoid two tx requests awaiting the nonce in parallel and submitting with the same nonce. - const txResult = await submissionMutex.runExclusive(async () => { - // Define variables in scope visible to finally block - let error: any; - const stateMutability = txRequest.stateMutability; - - // Await gas estimation to avoid increasing nonce before tx is actually sent - let gasLimit: BigNumberish; - try { - gasLimit = await txRequest.estimateGas(); - } catch (e) { - console.error("[TXQueue] GAS ESTIMATION ERROR", e); - return txRequest.cancel(e); - } - - // Wait if nonce is not ready - const { nonce } = await awaitValue(readyState); - - try { - return await txRequest.execute(nonce, gasLimit); - } catch (e: any) { - console.warn("[TXQueue] TXQUEUE EXECUTION FAILED", e); - // Nonce is handled centrally in finally block (for both failing and successful tx) - error = e; - } finally { - // If the error includes information about the transaction, - // then the transaction was submitted and the nonce needs to be - // increased regardless of the error - const isNonViewTransaction = - error && - "transaction" in error && - !("insufficient funds" in error) && - !("mispriced" in error) && - txRequest.stateMutability !== "view"; - const shouldIncreaseNonce = (!error && stateMutability !== "view") || isNonViewTransaction; - - const shouldResetNonce = - error && - (("code" in error && error.code === "NONCE_EXPIRED") || - JSON.stringify(error).includes("transaction already imported")); - console.log( - `[TXQueue] TX Sent (error=${!!error}, isMutationError=${!!isNonViewTransaction} incNonce=${!!shouldIncreaseNonce} resetNonce=${!!shouldResetNonce})` - ); - // Nonce handeling - if (shouldIncreaseNonce) incNonce(); - if (shouldResetNonce) await resetNonce(); - // Bubble up error - if (error) txRequest.cancel(error); - } - }); - - // Await confirmation - if (txResult?.hash) { - try { - await txResult.wait(); - } catch (e) { - console.warn("[TXQueue] tx failed in block", e); - - // Decode and log the revert reason. - // Use `then` instead of `await` to avoid letting consumers wait. - getRevertReason(txResult.hash, network.providers.get().json) - .then((reason) => console.warn("[TXQueue] Revert reason:", reason)) - .catch((_) => "This transaction didn't make it into a block. Was it mispriced?"); - - const params = new URLSearchParams(window.location.search); - const worldAddress = params.get("worldAddress"); - // Log useful commands that can be used to replay this tx - const trace = `mud trace --config deploy.json --world ${worldAddress} --tx ${txResult.hash}`; - - console.log("---------- DEBUG COMMANDS (RUN IN TERMINAL) -------------"); - console.log("Trace:"); - console.log(trace); - console.log("---------------------------------------------------------"); - } - } - - utilization--; - - // Check if there are any transactions waiting to be processed - processQueue(); - } - - function proxyContract(contract: Contract): Contract { - return mapObject(contract as any, (value, key) => { - // Relay all base contract methods to the original target - if (key in BaseContract.prototype) return value; - - // Relay everything that is not a function call to the original target - if (!(value instanceof Function)) return value; - - // Channel all contract specific methods through the queue - return (...args: unknown[]) => queueCall(contract, key as keyof BaseContract, args); - }) as Contract; - } - - const proxiedContracts = computed(() => { - const contracts = readyState.get()?.contracts; - if (!contracts) return undefined; - return mapObject(contracts, proxyContract); - }); - - const cachedProxiedContracts = cacheUntilReady(proxiedContracts); - - return { txQueue: cachedProxiedContracts, dispose, ready: computed(() => (readyState ? true : undefined)) }; -} - -function isOverrides(obj: any): obj is Overrides { - if (typeof obj !== "object" || Array.isArray(obj) || obj === null) return false; - return ( - "gasLimit" in obj || - "gasPrice" in obj || - "maxFeePerGas" in obj || - "maxPriorityFeePerGas" in obj || - "nonce" in obj || - "type" in obj || - "accessList" in obj || - "customData" in obj || - "value" in obj || - "blockTag" in obj || - "from" in obj - ); -} - -/** - * Simple priority queue - * @returns priority queue object - */ -function createPriorityQueue() { - const queue = new Map(); - - function queueByPriority() { - // Entries with a higher priority get executed first - return [...queue.entries()].sort((a, b) => (a[1].priority >= b[1].priority ? -1 : 1)); - } - - function add(id: string, element: T, priority = 1) { - queue.set(id, { element, priority }); - } - - function remove(id: string) { - queue.delete(id); - } - - function setPriority(id: string, priority: number) { - const entry = queue.get(id); - if (!entry) return; - queue.set(id, { ...entry, priority }); - } - - function next(): T | undefined { - if (queue.size === 0) return; - const [key, value] = queueByPriority()[0]; - queue.delete(key); - return value.element; - } - - function size(): number { - return queue.size; - } - - return { add, remove, setPriority, next, size }; -} diff --git a/packages/network/src/debug.ts b/packages/network/src/debug.ts deleted file mode 100644 index 79fb179b9a..0000000000 --- a/packages/network/src/debug.ts +++ /dev/null @@ -1,3 +0,0 @@ -import createDebug from "debug"; - -export const debug = createDebug("mud:network"); diff --git a/packages/network/src/dev/index.ts b/packages/network/src/dev/index.ts deleted file mode 100644 index 9fabcf647e..0000000000 --- a/packages/network/src/dev/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./observables"; - -export { keyTupleToEntityID } from "../v2/keyTupleToEntityID"; diff --git a/packages/network/src/dev/observables.ts b/packages/network/src/dev/observables.ts deleted file mode 100644 index 18205b0c35..0000000000 --- a/packages/network/src/dev/observables.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { BehaviorSubject, Subject } from "rxjs"; -import type { PublicClient, WalletClient, Chain, Transport } from "viem"; -import type { CacheStore } from "../workers"; -import { TableId } from "@latticexyz/common/deprecated"; -import { StoreEvent, EphemeralEvent } from "../v2/common"; - -// TODO: connection status? -// TODO: sync status (rpc vs mode vs cache) - -export const storeEvent$ = new Subject<{ - chainId: number; - worldAddress: string; - transactionHash: string; - blockNumber: number; - logIndex: number; - event: StoreEvent | EphemeralEvent; - table: TableId; - keyTuple: any; // TODO: refine - indexedValues?: Record; // TODO: refine - namedValues?: Record; // TODO: refine -}>(); - -export const transactionHash$ = new Subject(); - -// require chain for now so we can use it downstream -export const publicClient$: BehaviorSubject | null> = new BehaviorSubject | null>(null); -// require chain for now so we can use it downstream -export const walletClient$: BehaviorSubject | null> = new BehaviorSubject | null>(null); - -export const cacheStore$ = new BehaviorSubject(null); - -export const worldAddress$ = new BehaviorSubject(null); diff --git a/packages/network/src/index.ts b/packages/network/src/index.ts deleted file mode 100644 index 7038444a38..0000000000 --- a/packages/network/src/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export * from "./types"; -export * from "./createNetwork"; -export * from "./createProvider"; -export * from "./createSigner"; -export * from "./createContracts"; -export * from "./createTxQueue"; -export * from "./createSyncWorker"; -export * from "./createTopics"; -export * from "./createDecoder"; -export * from "./createEncoder"; -export * from "./createSystemExecutor"; -export * from "./networkUtils"; -export * from "./workers"; -export * from "./createClock"; -export * from "./createRelayStream"; -export * from "./createBlockNumberStream"; -export * from "./createFaucetService"; -export * from "./utils"; -export * from "./createFastTxExecutor"; -export * from "./v2/snapSync"; -export { keyTupleToEntityID } from "./v2/keyTupleToEntityID"; diff --git a/packages/network/src/initCache.ts b/packages/network/src/initCache.ts deleted file mode 100644 index 78a8b39f37..0000000000 --- a/packages/network/src/initCache.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { arrayToIterator, deferred, mergeIterators, transformIterator } from "@latticexyz/utils"; - -const indexedDB = self.indexedDB; -const VERSION = 2; - -/** - * Initialize an indexedDB store. - * - * @param db IDBDatabase - * @param storeId Id of the store to initialize - */ -function initStore(db: IDBDatabase, storeId: string) { - if (!db.objectStoreNames.contains(storeId)) { - db.createObjectStore(storeId); - } -} - -/** - * Initialize an indexedDB database. - * - * @param dbId Id of the database to initialize. - * @param stores Keys of the stores to initialize. - * @param version Optional: version of the database to initialize. - * @param idb Optional: custom indexedDB factory - * @returns Promise resolving with IDBDatabase object - */ -function initDb(dbId: string, stores: string[], version = VERSION, idb: IDBFactory = indexedDB) { - const [resolve, reject, promise] = deferred(); - - const request = idb.open(dbId, version); - - // Create store and index - request.onupgradeneeded = () => { - const db = request.result; - for (const store of stores) { - initStore(db, store); - } - }; - - request.onsuccess = () => { - const db = request.result; - resolve(db); - }; - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - return promise; -} - -type Stores = { [key: string]: unknown }; -type StoreKey = keyof S & string; - -/** - * Initialize an abstracted Cache object to simplify interaction with the indexedDB database. - * - * @param id Id of the database to initialize. - * @param stores Keys of the stores to initialize. - * @param version Optional: version of the database to initialize. - * @param idb Optional: custom indexedDB factory - * @returns Promise resolving with Cache object - */ -export async function initCache( - id: string, - stores: StoreKey[], - version?: number, - idb?: IDBFactory -) { - const db = await initDb(id, stores, version, idb); - - function openStore(store: StoreKey): IDBObjectStore { - const tx = db.transaction(store, "readwrite"); - const objectStore = tx.objectStore(store); - return objectStore; - } - - function set>(store: Store, key: string, value: S[Store], ignoreResult = false) { - const objectStore = openStore(store); - const request = objectStore.put(value, key); - - if (ignoreResult) return; - - const [resolve, reject, promise] = deferred(); - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - request.onsuccess = () => { - resolve(); - }; - - return promise; - } - - function get>(store: Store, key: string): Promise { - const [resolve, reject, promise] = deferred(); - - const objectStore = openStore(store); - const request = objectStore.get(key); - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - request.onsuccess = () => { - const item = request.result; - resolve(item); - }; - - return promise; - } - - function remove(store: StoreKey, key: string): Promise { - const [resolve, reject, promise] = deferred(); - - const objectStore = openStore(store); - const request = objectStore.delete(key); - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - request.onsuccess = () => { - resolve(); - }; - - return promise; - } - - function keys(store: StoreKey): Promise> { - const [resolve, reject, promise] = deferred>(); - - const objectStore = openStore(store); - const request = objectStore.getAllKeys(); - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - request.onsuccess = () => { - const rawKeys = arrayToIterator(request.result); - const stringKeys = transformIterator(rawKeys, (k) => k.toString()); - resolve(stringKeys); - }; - - return promise; - } - - function values>(store: Store): Promise> { - const [resolve, reject, promise] = deferred>(); - - const objectStore = openStore(store); - const request = objectStore.getAll(); - - request.onerror = (error) => { - reject(new Error(JSON.stringify(error))); - }; - - request.onsuccess = () => { - resolve(arrayToIterator(request.result)); - }; - - return promise; - } - - async function entries>(store: Store): Promise> { - const [keyIterator, valueIterator] = await Promise.all([keys(store), values(store)]); - return mergeIterators(keyIterator, valueIterator); - } - - return { set, get, remove, keys, values, entries, db }; -} diff --git a/packages/network/src/networkUtils.ts b/packages/network/src/networkUtils.ts deleted file mode 100644 index 60c31fc980..0000000000 --- a/packages/network/src/networkUtils.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { - JsonRpcProvider, - WebSocketProvider, - Block, - Log, - Formatter, - BaseProvider, - TransactionRequest, -} from "@ethersproject/providers"; -import { callWithRetry, extractEncodedArguments, range, sleep } from "@latticexyz/utils"; -import { BigNumber, Contract } from "ethers"; -import { resolveProperties, defaultAbiCoder as abi } from "ethers/lib/utils.js"; -import { Contracts, ContractTopics, ContractEvent, ContractsConfig } from "./types"; - -/** - * Await network to be reachable. - * - * @param provider ethers JsonRpcProvider - * @param wssProvider ethers WebSocketProvider - * @returns Promise resolving once the network is reachable - */ -export async function ensureNetworkIsUp(provider: JsonRpcProvider, wssProvider?: WebSocketProvider): Promise { - const networkInfoPromise = () => { - return Promise.all([provider.getBlockNumber(), wssProvider ? wssProvider.getBlockNumber() : Promise.resolve()]); - }; - await callWithRetry(networkInfoPromise, [], 10, 1000); - return; -} - -/** - * Fetch the latest Ethereum block - * - * @param provider ethers JsonRpcProvider - * @param requireMinimumBlockNumber Minimal required block number. - * If the latest block number is below this number, the method waits for 1300ms and tries again, for at most 10 times. - * @returns Promise resolving with the latest Ethereum block - */ -export async function fetchBlock(provider: JsonRpcProvider, requireMinimumBlockNumber?: number): Promise { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const _ of range(10)) { - const blockPromise = async () => { - const rawBlock = await provider.perform("getBlock", { - includeTransactions: false, - blockTag: provider.formatter.blockTag(await provider._getBlockTag("latest")), - }); - return provider.formatter.block(rawBlock); - }; - const block = await callWithRetry(blockPromise, [], 10, 1000); - if (requireMinimumBlockNumber && block.number < requireMinimumBlockNumber) { - await sleep(300); - continue; - } else { - return block; - } - } - throw new Error("Could not fetch a block with blockNumber " + requireMinimumBlockNumber); -} - -/** - * Fetch logs with the given topics from a given block range. - * - * @param provider ethers JsonRpcProvider - * @param topics Topics to fetch logs for - * @param startBlockNumber Start of block range to fetch logs from (inclusive) - * @param endBlockNumber End of block range to fetch logs from (inclusive) - * @param contracts Contracts to fetch logs from - * @param requireMinimumBlockNumber Minimal block number required to fetch blocks - * @returns Promise resolving with an array of logs from the specified block range and topics - */ -export async function fetchLogs( - provider: JsonRpcProvider, - topics: ContractTopics[], - startBlockNumber: number, - endBlockNumber: number, - contracts: ContractsConfig, - requireMinimumBlockNumber?: number -): Promise> { - const getLogPromise = async (contractAddress: string, topics: string[][]): Promise> => { - const params = await resolveProperties({ - filter: provider._getFilter({ - fromBlock: startBlockNumber, // inclusive - toBlock: endBlockNumber, // inclusive - address: contractAddress, - topics: topics, - }), - }); - const logs: Array = await provider.perform("getLogs", params); - logs.forEach((log) => { - if (log.removed == null) { - log.removed = false; - } - }); - return Formatter.arrayOf(provider.formatter.filterLog.bind(provider.formatter))(logs); - }; - - const blockPromise = async () => { - const _blockNumber = await provider.perform("getBlockNumber", {}); - const blockNumber = BigNumber.from(_blockNumber).toNumber(); - return blockNumber; - }; - - const getLogPromises = () => { - const logPromises: Array>> = []; - for (const [k, c] of Object.entries(contracts)) { - const topicsForContract = topics.find((t) => t.key === k)?.topics; - if (topicsForContract) { - logPromises.push(getLogPromise(c.address, topicsForContract)); - } - } - return logPromises; - }; - - if (requireMinimumBlockNumber) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const _ in range(10)) { - const call = () => Promise.all([blockPromise(), ...getLogPromises()]); - const [blockNumber, logs] = await callWithRetry<[number, ...Array>]>(call, [], 10, 1000); - if (blockNumber < requireMinimumBlockNumber) { - await sleep(500); - } else { - return logs.flat(); - } - } - throw new Error("Could not fetch logs with a required minimum block number"); - } else { - const call = () => Promise.all([...getLogPromises()]); - const logs = await callWithRetry>>(call, [], 10, 1000); - return logs.flat(); - } -} - -/** - * Fetch events from block range, ordered by block, transaction index and log index - * - * @param provider ethers JsonRpcProvider - * @param topics Topics to fetch events for - * @param startBlockNumber Start of block range to fetch events from (inclusive) - * @param endBlockNumber End of block range to fetch events from (inclusive) - * @param contracts Contracts to fetch events from - * @param supportsBatchQueries Set to true if the provider supports batch queries (recommended) - * @returns Promise resolving with an array of ContractEvents - */ -export async function fetchEventsInBlockRange( - provider: JsonRpcProvider, - topics: ContractTopics[], - startBlockNumber: number, - endBlockNumber: number, - contracts: ContractsConfig, - supportsBatchQueries?: boolean -): Promise>> { - const logs: Array = await fetchLogs( - provider, - topics, - startBlockNumber, - endBlockNumber, - contracts, - supportsBatchQueries ? endBlockNumber : undefined - ); - - // console.log(`[Network] fetched ${logs.length} events from ${startBlockNumber} -> ${endBlockNumber}`); - // console.log(`got ${logs.length} logs from range ${startBlockNumber} -> ${endBlockNumber}`); - // we need to sort per block, transaction index, and log index - logs.sort((a: Log, b: Log) => { - if (a.blockNumber < b.blockNumber) { - return -1; - } else if (a.blockNumber > b.blockNumber) { - return 1; - } else { - if (a.transactionIndex < b.transactionIndex) { - return -1; - } else if (a.transactionIndex > b.transactionIndex) { - return 1; - } else { - return a.logIndex < b.logIndex ? -1 : 1; - } - } - }); - - // construct an object: address => keyof C - const addressToContractKey: { [key in string]: keyof C } = {}; - for (const contractKey of Object.keys(contracts)) { - addressToContractKey[contracts[contractKey].address.toLowerCase()] = contractKey; - } - - // parse the logs to get the logs description, then turn them into contract events - const contractEvents: Array> = []; - - for (let i = 0; i < logs.length; i++) { - const log = logs[i]; - const contractKey = addressToContractKey[log.address.toLowerCase()]; - if (!contractKey) { - throw new Error( - "This should not happen. An event's address is not part of the contracts dictionnary: " + log.address - ); - } - - const { address, abi } = contracts[contractKey]; - const contract = new Contract(address, abi); - try { - const logDescription = contract.interface.parseLog(log); - - // Set a flag if this is the last event in this transaction - const lastEventInTx = logs[i + 1]?.transactionHash !== log.transactionHash; - - contractEvents.push({ - contractKey, - eventKey: logDescription.name, - args: logDescription.args, - txHash: log.transactionHash, - lastEventInTx, - blockNumber: log.blockNumber, - logIndex: log.logIndex, - }); - } catch (e) { - console.warn("Error", e); - console.warn("A log couldn't be parsed with the corresponding contract interface!"); - } - } - - return contractEvents; -} - -/** - * Get the revert reason from a given transaction hash - * - * @param txHash Transaction hash to get the revert reason from - * @param provider ethers Provider - * @returns Promise resolving with revert reason string - */ -export async function getRevertReason(txHash: string, provider: BaseProvider): Promise { - // Decoding the revert reason: https://docs.soliditylang.org/en/latest/control-structures.html#revert - const tx = await provider.getTransaction(txHash); - if (!tx) throw new Error("This transaction doesn't exist. Can't get the revert reason"); - tx.gasPrice = undefined; // tx object contains both gasPrice and maxFeePerGas - const encodedRevertReason = await provider.call(tx as TransactionRequest); - const decodedRevertReason = abi.decode(["string"], extractEncodedArguments(encodedRevertReason)); - return decodedRevertReason[0]; -} diff --git a/packages/network/src/provider.ts b/packages/network/src/provider.ts deleted file mode 100644 index faa6831a57..0000000000 --- a/packages/network/src/provider.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { JsonRpcBatchProvider, JsonRpcProvider, Network, Networkish } from "@ethersproject/providers"; -import { ConnectionInfo } from "ethers/lib/utils.js"; - -export class MUDJsonRpcProvider extends JsonRpcProvider { - constructor(url: string | ConnectionInfo | undefined, network: Networkish) { - super(url, network); - } - async detectNetwork(): Promise { - const network = this.network; - if (network == null) { - throw new Error("No network"); - } - return network; - } -} - -export class MUDJsonRpcBatchProvider extends JsonRpcBatchProvider { - constructor(url?: string | ConnectionInfo | undefined, network?: Networkish | undefined) { - super(url, network); - } - async detectNetwork(): Promise { - const network = this.network; - if (network == null) { - throw new Error("No network"); - } - return network; - } -} diff --git a/packages/network/src/types.ts b/packages/network/src/types.ts deleted file mode 100644 index ccc46adc5d..0000000000 --- a/packages/network/src/types.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { Result } from "@ethersproject/abi"; -import { ExternalProvider } from "@ethersproject/providers"; -import { Components, ComponentValue, Entity, SchemaOf } from "@latticexyz/recs"; -import { TxMetadata } from "@latticexyz/services/ecs-stream"; -import { Cached } from "@latticexyz/utils"; -import { TableId } from "@latticexyz/common/deprecated"; -import { BaseContract, BigNumber, ContractInterface } from "ethers"; -import { Observable } from "rxjs"; -import { SyncState } from "./workers"; -import { MUDChain } from "@latticexyz/common/chains"; - -export interface NetworkConfig { - chainId: number; - privateKey?: string; - clock: ClockConfig; - provider: ProviderConfig; - snapshotServiceUrl?: string; - streamServiceUrl?: string; - initialBlockNumber?: number; - blockExplorer?: string; - cacheAgeThreshold?: number; - cacheInterval?: number; - encoders?: boolean; - pruneOptions?: { playerAddress: string; hashedComponentId: string }; - chainConfig?: MUDChain; -} - -export interface ClockConfig { - period: number; - initialTime: number; - syncInterval: number; -} - -export type Clock = { - time$: Observable; - currentTime: number; - lastUpdateTime: number; - update: (time: number, maintainStale?: boolean) => void; - dispose: () => void; -}; - -export interface ProviderConfig { - chainId: number; - jsonRpcUrl: string; - wsRpcUrl?: string; - externalProvider?: ExternalProvider; - options?: { batch?: boolean; pollingInterval?: number; skipNetworkCheck?: boolean }; -} - -export type Contracts = { - [key: string]: BaseContract; -}; - -export type ContractConfig = { - address: string; - abi: ContractInterface; -}; - -export type ContractsConfig = { - [key in keyof C]: ContractConfig; -}; - -export type TxQueue = Cached; - -export type ContractTopics = { - key: string; - topics: string[][]; -}; - -export type ContractEvent = { - contractKey: keyof C; - eventKey: string; - args: Result; - txHash: string; - lastEventInTx: boolean; - blockNumber: number; - // TODO: make this required, so we can later sort by logIndex when concatenating event types - // would require updating the ECS snapshot, though - logIndex?: number; -}; - -// Mapping from hashed contract component id to client component key -export type Mappings = { - [hashedContractId: string]: keyof C; -}; - -export type NetworkComponentUpdate = { - [key in keyof C]: { - type: NetworkEvents.NetworkComponentUpdate; - component: key & string; - value: ComponentValue> | undefined; - partialValue?: Partial>>; - initialValue?: ComponentValue>; - ephemeral?: boolean; - devEmit?: () => void; - }; -}[keyof C] & { - entity: Entity; - namespace: string; - table: string; - key: Record; - lastEventInTx: boolean; - txHash: string; - txMetadata?: TxMetadata; - blockNumber: number; - // TODO: make this required, so we can later sort by logIndex when concatenating event types - // would require updating the ECS snapshot, though - logIndex?: number; -}; - -export type SystemCallTransaction = { - hash: string; - to: string; - data: string; - value: BigNumber; -}; - -export type SystemCall = { - type: NetworkEvents.SystemCall; - tx: SystemCallTransaction; - updates: NetworkComponentUpdate[]; -}; - -export enum NetworkEvents { - SystemCall = "SystemCall", - NetworkComponentUpdate = "NetworkComponentUpdate", -} - -export type NetworkEvent = SystemCall | NetworkComponentUpdate; -export function isSystemCallEvent(e: NetworkEvent): e is SystemCall { - return e.type === NetworkEvents.SystemCall; -} - -export function isNetworkComponentUpdateEvent( - e: NetworkEvent -): e is NetworkComponentUpdate { - return e.type === NetworkEvents.NetworkComponentUpdate; -} - -export type RawTableRecord = { - tableId: TableId; - keyTuple: string[]; - value: string; -}; - -export type SyncWorkerConfig = { - provider: ProviderConfig; - initialBlockNumber: number; - worldContract: ContractConfig; - disableCache?: boolean; - chainId: number; - modeUrl?: string; - snapshotServiceUrl?: string; - streamServiceUrl?: string; - fetchSystemCalls?: boolean; - cacheInterval?: number; - cacheAgeThreshold?: number; - snapshotNumChunks?: number; - pruneOptions?: { playerAddress: string; hashedComponentId: string }; - initialRecords?: RawTableRecord[]; -}; - -export enum ContractSchemaValue { - BOOL, - INT8, - INT16, - INT32, - INT64, - INT128, - INT256, - INT, - UINT8, - UINT16, - UINT32, - UINT64, - UINT128, - UINT256, - BYTES, - STRING, - ADDRESS, - BYTES4, - BOOL_ARRAY, - INT8_ARRAY, - INT16_ARRAY, - INT32_ARRAY, - INT64_ARRAY, - INT128_ARRAY, - INT256_ARRAY, - INT_ARRAY, - UINT8_ARRAY, - UINT16_ARRAY, - UINT32_ARRAY, - UINT64_ARRAY, - UINT128_ARRAY, - UINT256_ARRAY, - BYTES_ARRAY, - STRING_ARRAY, -} - -export const ContractSchemaValueId: { [key in ContractSchemaValue]: string } = { - [ContractSchemaValue.BOOL]: "bool", - [ContractSchemaValue.INT8]: "int8", - [ContractSchemaValue.INT16]: "int16", - [ContractSchemaValue.INT32]: "int32", - [ContractSchemaValue.INT64]: "int64", - [ContractSchemaValue.INT128]: "int128", - [ContractSchemaValue.INT256]: "int256", - [ContractSchemaValue.INT]: "int", - [ContractSchemaValue.UINT8]: "uint8", - [ContractSchemaValue.UINT16]: "uint16", - [ContractSchemaValue.UINT32]: "uint32", - [ContractSchemaValue.UINT64]: "uint64", - [ContractSchemaValue.UINT128]: "uint128", - [ContractSchemaValue.UINT256]: "uint256", - [ContractSchemaValue.BYTES]: "bytes", - [ContractSchemaValue.STRING]: "string", - [ContractSchemaValue.ADDRESS]: "address", - [ContractSchemaValue.BYTES4]: "bytes4", - [ContractSchemaValue.BOOL_ARRAY]: "bool[]", - [ContractSchemaValue.INT8_ARRAY]: "int8[]", - [ContractSchemaValue.INT16_ARRAY]: "int16[]", - [ContractSchemaValue.INT32_ARRAY]: "int32[]", - [ContractSchemaValue.INT64_ARRAY]: "int64[]", - [ContractSchemaValue.INT128_ARRAY]: "int128[]", - [ContractSchemaValue.INT256_ARRAY]: "int256[]", - [ContractSchemaValue.INT_ARRAY]: "int[]", - [ContractSchemaValue.UINT8_ARRAY]: "uint8[]", - [ContractSchemaValue.UINT16_ARRAY]: "uint16[]", - [ContractSchemaValue.UINT32_ARRAY]: "uint32[]", - [ContractSchemaValue.UINT64_ARRAY]: "uint64[]", - [ContractSchemaValue.UINT128_ARRAY]: "uint128[]", - [ContractSchemaValue.UINT256_ARRAY]: "uint256[]", - [ContractSchemaValue.BYTES_ARRAY]: "bytes[]", - [ContractSchemaValue.STRING_ARRAY]: "string[]", -}; - -export const ContractSchemaValueArrayToElement = { - [ContractSchemaValue.BOOL_ARRAY]: ContractSchemaValue.BOOL, - [ContractSchemaValue.INT8_ARRAY]: ContractSchemaValue.INT8, - [ContractSchemaValue.INT16_ARRAY]: ContractSchemaValue.INT16, - [ContractSchemaValue.INT32_ARRAY]: ContractSchemaValue.INT32, - [ContractSchemaValue.INT64_ARRAY]: ContractSchemaValue.INT64, - [ContractSchemaValue.INT128_ARRAY]: ContractSchemaValue.INT128, - [ContractSchemaValue.INT256_ARRAY]: ContractSchemaValue.INT256, - [ContractSchemaValue.INT_ARRAY]: ContractSchemaValue.INT, - [ContractSchemaValue.UINT8_ARRAY]: ContractSchemaValue.UINT8, - [ContractSchemaValue.UINT16_ARRAY]: ContractSchemaValue.UINT16, - [ContractSchemaValue.UINT32_ARRAY]: ContractSchemaValue.UINT32, - [ContractSchemaValue.UINT64_ARRAY]: ContractSchemaValue.UINT64, - [ContractSchemaValue.UINT128_ARRAY]: ContractSchemaValue.UINT128, - [ContractSchemaValue.UINT256_ARRAY]: ContractSchemaValue.INT256, - [ContractSchemaValue.BYTES_ARRAY]: ContractSchemaValue.BYTES, - [ContractSchemaValue.STRING_ARRAY]: ContractSchemaValue.STRING, -} as { [key in ContractSchemaValue]: ContractSchemaValue }; - -export type ContractSchemaValueTypes = { - [ContractSchemaValue.BOOL]: boolean; - [ContractSchemaValue.INT8]: number; - [ContractSchemaValue.INT16]: number; - [ContractSchemaValue.INT32]: number; - [ContractSchemaValue.INT64]: string; - [ContractSchemaValue.INT128]: string; - [ContractSchemaValue.INT256]: string; - [ContractSchemaValue.INT]: string; - [ContractSchemaValue.UINT8]: number; - [ContractSchemaValue.UINT16]: number; - [ContractSchemaValue.UINT32]: number; - [ContractSchemaValue.UINT64]: string; - [ContractSchemaValue.UINT128]: string; - [ContractSchemaValue.UINT256]: string; - [ContractSchemaValue.BYTES]: string; - [ContractSchemaValue.STRING]: string; - [ContractSchemaValue.ADDRESS]: string; - [ContractSchemaValue.BYTES4]: string; - [ContractSchemaValue.BOOL_ARRAY]: boolean[]; - [ContractSchemaValue.INT8_ARRAY]: number[]; - [ContractSchemaValue.INT16_ARRAY]: number[]; - [ContractSchemaValue.INT32_ARRAY]: number[]; - [ContractSchemaValue.INT64_ARRAY]: string[]; - [ContractSchemaValue.INT128_ARRAY]: string[]; - [ContractSchemaValue.INT256_ARRAY]: string[]; - [ContractSchemaValue.INT_ARRAY]: string[]; - [ContractSchemaValue.UINT8_ARRAY]: number[]; - [ContractSchemaValue.UINT16_ARRAY]: number[]; - [ContractSchemaValue.UINT32_ARRAY]: number[]; - [ContractSchemaValue.UINT64_ARRAY]: string[]; - [ContractSchemaValue.UINT128_ARRAY]: string[]; - [ContractSchemaValue.UINT256_ARRAY]: string[]; - [ContractSchemaValue.BYTES_ARRAY]: string[]; - [ContractSchemaValue.STRING_ARRAY]: string[]; -}; - -export type SyncStateStruct = { - state: SyncState; - msg: string; - percentage: number; -}; diff --git a/packages/network/src/utils.ts b/packages/network/src/utils.ts deleted file mode 100644 index f260c7b6e2..0000000000 --- a/packages/network/src/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Message } from "@latticexyz/services/ecs-relay"; -import { keccak256 } from "ethers/lib/utils.js"; - -// Message payload to sign and use to recover signer -export function messagePayload(msg: Message) { - return `(${msg.version},${msg.id},${keccak256(msg.data)},${msg.timestamp})`; -} diff --git a/packages/network/src/v2/README.md b/packages/network/src/v2/README.md deleted file mode 100644 index 74af005bdb..0000000000 --- a/packages/network/src/v2/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# v2 support in v1 network package - -Here you'll find the code to fetch and process MUD v2 data events (e.g. `StoreSetRecord`) and turn them into `NetworkComponentUpdate`s that MUD v1 network package can use to keep client component values up to date. - -# Usage - -Use the v2 `mud.config.mts` configuration and `mud tablegen` to define schemas and generate tables. - -Then use `defineStoreComponents` to translate your v2 config into v1 component definitions (i.e `defineComponent`). - -# Client expectations - -Because we autogenerate libraries for v2 tables, we avoid the extra gas of doing on-chain validation. As such, it's technically possible to "corrupt" on-chain data by sidestepping this autogen code and calling into `Store` directly. We also expects a certain order of events to happen before data is considered "valid" enough to be used by v1's networking and storage code. - -- Record/field updates are ignored until both the table schema is registered and table metadata is set. -- Any tables metadata updates after the first are ignored. -- Any field updates are ignored until the record is set. - -We'll release new v2 packages for the networking layer that can remove some of these requirements, but we're aiming for feature parity first. - -If you use MUD's autogenerated Solidity libraries for your tables and MUD's CLI deploy tooling, you mostly won't need to worry about the above constraints. diff --git a/packages/network/src/v2/common.ts b/packages/network/src/v2/common.ts deleted file mode 100644 index 1f028050ad..0000000000 --- a/packages/network/src/v2/common.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TableId } from "@latticexyz/common/deprecated"; -import { SchemaType } from "@latticexyz/schema-type/deprecated"; - -export const schemaTableId = new TableId("mudstore", "schema"); -export const metadataTableId = new TableId("mudstore", "StoreMetadata"); - -export const storeEvents = ["StoreSetRecord", "StoreSetField", "StoreDeleteRecord"] as const; -export const ephemeralEvents = ["StoreEphemeralRecord"] as const; - -export type StoreEvent = (typeof storeEvents)[number]; -export type EphemeralEvent = (typeof ephemeralEvents)[number]; - -export type TableSchema = { valueSchema: Schema; keySchema: Schema }; - -export type Schema = Readonly<{ - staticDataLength: number; - staticFields: SchemaType[]; - dynamicFields: SchemaType[]; - rawSchema: string; - abi: string; - isEmpty: boolean; -}>; - -export type TableMetadata = Readonly<{ - tableName: string; - fieldNames: string[]; -}>; diff --git a/packages/network/src/v2/decodeStoreSetField.ts b/packages/network/src/v2/decodeStoreSetField.ts deleted file mode 100644 index 74dc7e4ae1..0000000000 --- a/packages/network/src/v2/decodeStoreSetField.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ComponentValue } from "@latticexyz/recs"; -import { TableId } from "@latticexyz/common/deprecated"; -import { Contract } from "ethers"; -import { registerSchema } from "./schemas/tableSchemas"; -import { registerMetadata } from "./schemas/tableMetadata"; -import { decodeField } from "./schemas/decodeField"; -import { TableSchema } from "./common"; -import { decodeStaticField } from "./schemas/decodeStaticField"; -import { DynamicSchemaType, StaticSchemaType } from "@latticexyz/schema-type/deprecated"; -import { decodeDynamicField } from "./schemas/decodeDynamicField"; -import { decodeKeyTuple } from "./schemas/decodeKeyTuple"; - -export async function decodeStoreSetField( - contract: Contract, - table: TableId, - keyTuple: string[], - schemaIndex: number, - data: string -): Promise<{ - schema: TableSchema; - indexedValues: Record; - indexedInitialValues: Record; - namedValues?: Record; - namedInitialValues?: Record; - indexedKey: Record; - namedKey?: Record; -}> { - const schema = await registerSchema(contract, table); - const { valueSchema, keySchema } = schema; - const indexedValues = decodeField(valueSchema, schemaIndex, data); - const indexedKey = decodeKeyTuple(keySchema, keyTuple); - - // Create an object that represents an "uninitialized" record as it would exist in Solidity - // to help populate RECS state when using StoreSetField before StoreSetRecord. - const defaultValues = [ - ...valueSchema.staticFields.map((fieldType) => - decodeStaticField(fieldType as StaticSchemaType, new Uint8Array(0), 0) - ), - ...valueSchema.dynamicFields.map((fieldType) => - decodeDynamicField(fieldType as DynamicSchemaType, new Uint8Array(0)) - ), - ]; - const indexedInitialValues = Object.fromEntries( - defaultValues.map((value, index) => [index, value]) - ) as ComponentValue; - - const metadata = await registerMetadata(contract, table); - if (metadata) { - const { tableName, fieldNames } = metadata; - const namedInitialValues = Object.fromEntries( - defaultValues.map((fieldValue, schemaIndex) => { - return [fieldNames[schemaIndex], fieldValue]; - }) - ) as ComponentValue; - - // TODO: once TableMetadata supports key names we can decode them here. - // For now we extract the key names of known tables from the `mud.config.ts` - // and ignore others in `applyNetworkUpdate`. - // (see https://github.com/latticexyz/mud/issues/824) - - return { - schema, - indexedValues, - indexedInitialValues, - namedValues: { - [fieldNames[schemaIndex]]: indexedValues[schemaIndex], - }, - namedInitialValues, - indexedKey, - }; - } - - console.warn( - `Received data for ${table.toString()}, but could not find table metadata for field names. Did your contracts get autogenerated and deployed properly?` - ); - return { - schema, - indexedValues, - indexedInitialValues, - indexedKey, - }; -} diff --git a/packages/network/src/v2/decodeStoreSetRecord.ts b/packages/network/src/v2/decodeStoreSetRecord.ts deleted file mode 100644 index 97c09a6dcf..0000000000 --- a/packages/network/src/v2/decodeStoreSetRecord.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { TableId } from "@latticexyz/common/deprecated"; -import { Contract, utils } from "ethers"; -import { registerSchema } from "./schemas/tableSchemas"; -import { registerMetadata } from "./schemas/tableMetadata"; -import { decodeData } from "./schemas/decodeData"; -import { schemaTableId, metadataTableId } from "./common"; -import { decodeKeyTuple } from "./schemas/decodeKeyTuple"; -import { Hex } from "viem"; - -export async function decodeStoreSetRecord( - contract: Contract, - table: TableId, - keyTuple: string[], - data: string -): Promise<{ - indexedValues: Record; - namedValues?: Record; - indexedKey: Record; - namedKey?: Record; -}> { - // registerSchema event - if (table.toHex() === schemaTableId.toHex()) { - const [tableForSchema, ...otherKeys] = keyTuple; - if (otherKeys.length) { - console.warn( - "registerSchema event has more than one value in key tuple, but this method only supports a single key", - { table, keyTuple } - ); - } - registerSchema(contract, TableId.fromHex(tableForSchema as Hex), data); - } - - const { keySchema, valueSchema } = await registerSchema(contract, table); - const indexedValues = decodeData(valueSchema, data); - const indexedKey = decodeKeyTuple(keySchema, keyTuple); - - if (table.toHex() === metadataTableId.toHex()) { - const [tableForMetadata, ...otherKeys] = keyTuple; - if (otherKeys.length) { - console.warn( - "setMetadata event has more than one value in key tuple, but this method only supports a single key", - { table, keyTuple } - ); - } - const tableName = indexedValues[0]; - const [fieldNames] = utils.defaultAbiCoder.decode(["string[]"], indexedValues[1]); - registerMetadata(contract, TableId.fromHex(tableForMetadata as Hex), { tableName, fieldNames }); - } - - const metadata = await registerMetadata(contract, table); - if (metadata) { - const { tableName, fieldNames } = metadata; - const namedValues: Record = {}; - for (const [index, fieldName] of fieldNames.entries()) { - namedValues[fieldName] = indexedValues[index]; - } - - // TODO: once TableMetadata supports key names we can decode them here. - // For now we extract the key names of known tables from the `mud.config.ts` - // and ignore others in `applyNetworkUpdate`. - // (see https://github.com/latticexyz/mud/issues/824) - - return { - indexedValues, - namedValues, - indexedKey, - }; - } - - console.warn( - `Received data for ${table.toString()}, but could not find table metadata for field names. Did your contracts get autogenerated and deployed properly?` - ); - return { - indexedValues, - indexedKey, - }; -} diff --git a/packages/network/src/v2/ecsEventFromLog.ts b/packages/network/src/v2/ecsEventFromLog.ts deleted file mode 100644 index e3b8cdc239..0000000000 --- a/packages/network/src/v2/ecsEventFromLog.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { Contract, utils } from "ethers"; -import { Log } from "@ethersproject/providers"; -import { LogDescription } from "@ethersproject/abi"; -import { TableId } from "@latticexyz/common/deprecated"; -import { NetworkComponentUpdate, NetworkEvents } from "../types"; -import { decodeStoreSetRecord } from "./decodeStoreSetRecord"; -import { decodeStoreSetField } from "./decodeStoreSetField"; -import { keyTupleToEntityID } from "./keyTupleToEntityID"; -import * as devObservables from "../dev/observables"; -import { registerSchema } from "./schemas/tableSchemas"; -import { decodeKeyTuple } from "./schemas/decodeKeyTuple"; - -export const ecsEventFromLog = async ( - chainId: number, - contract: Contract, - log: Log, - parsedLog: LogDescription, - lastEventInTx: boolean -): Promise => { - const { blockNumber, transactionHash, logIndex } = log; - const { args, name } = parsedLog; - - const tableId = TableId.fromHex(args.table); - const component = tableId.toString(); - const entity = keyTupleToEntityID(args.key); - - const ecsEvent = { - type: NetworkEvents.NetworkComponentUpdate, - component, - entity, - value: undefined, - blockNumber, - txHash: transactionHash, - logIndex, - lastEventInTx, - namespace: tableId.namespace, - table: tableId.name, - key: {}, - } satisfies NetworkComponentUpdate; - - if (name === "StoreSetRecord") { - const { indexedValues, namedValues, indexedKey, namedKey } = await decodeStoreSetRecord( - contract, - tableId, - args.key, - args.data - ); - return { - ...ecsEvent, - value: { - ...indexedValues, - ...namedValues, - }, - key: { - ...indexedKey, - ...namedKey, - }, - devEmit: () => { - devObservables.storeEvent$.next({ - event: name, - chainId, - worldAddress: contract.address, - blockNumber, - logIndex, - transactionHash, - table: tableId, - keyTuple: args.key, - indexedValues, - namedValues, - }); - }, - }; - } - - if (name === "StoreEphemeralRecord") { - const { indexedValues, namedValues, indexedKey, namedKey } = await decodeStoreSetRecord( - contract, - tableId, - args.key, - args.data - ); - return { - ...ecsEvent, - ephemeral: true, - value: { - ...indexedValues, - ...namedValues, - }, - key: { - ...indexedKey, - ...namedKey, - }, - devEmit: () => { - devObservables.storeEvent$.next({ - event: name, - chainId, - worldAddress: contract.address, - blockNumber, - logIndex, - transactionHash, - table: tableId, - keyTuple: args.key, - indexedValues, - namedValues, - }); - }, - }; - } - - if (name === "StoreSetField") { - console.log("set field"); - const { indexedValues, indexedInitialValues, namedValues, namedInitialValues, indexedKey, namedKey } = - await decodeStoreSetField(contract, tableId, args.key, args.schemaIndex, args.data); - return { - ...ecsEvent, - partialValue: { - ...indexedValues, - ...namedValues, - }, - initialValue: { - ...indexedInitialValues, - ...namedInitialValues, - }, - key: { - ...indexedKey, - ...namedKey, - }, - devEmit: () => { - devObservables.storeEvent$.next({ - event: name, - chainId, - worldAddress: contract.address, - blockNumber, - logIndex, - transactionHash, - table: tableId, - keyTuple: args.key, - indexedValues, - namedValues, - }); - }, - }; - } - - if (name === "StoreDeleteRecord") { - const { keySchema } = await registerSchema(contract, tableId); - const indexedKey = decodeKeyTuple(keySchema, args.key); - - return { - ...ecsEvent, - key: indexedKey, - devEmit: () => { - devObservables.storeEvent$.next({ - event: name, - chainId, - worldAddress: contract.address, - blockNumber, - logIndex, - transactionHash, - table: tableId, - keyTuple: args.key, - }); - }, - }; - } -}; diff --git a/packages/network/src/v2/fetchStoreEvents.ts b/packages/network/src/v2/fetchStoreEvents.ts deleted file mode 100644 index 7fd4ab9daa..0000000000 --- a/packages/network/src/v2/fetchStoreEvents.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Contract } from "ethers"; -import { NetworkComponentUpdate } from "../types"; -import orderBy from "lodash/orderBy"; -import { isDefined } from "@latticexyz/common/utils"; -import { ephemeralEvents, storeEvents } from "./common"; -import { ecsEventFromLog } from "./ecsEventFromLog"; - -export async function fetchStoreEvents( - store: Contract, - fromBlock: number, - toBlock: number -): Promise { - // TODO: pass the chain ID as an argument - const { chainId } = await store.provider.getNetwork(); - - const eventNames = [...storeEvents, ...ephemeralEvents]; - const topicSets = eventNames.map((eventName) => store.filters[eventName]().topics).filter(isDefined); - - const logSets = await Promise.all( - topicSets.map((topics) => store.provider.getLogs({ address: store.address, topics, fromBlock, toBlock })) - ); - - const logs = orderBy( - logSets.flatMap((logs) => logs.map((log) => ({ log, parsedLog: store.interface.parseLog(log) }))), - ["log.blockNumber", "log.logIndex"] - ); - - const lastLogForTx: Record = {}; - logs.map(({ log }) => { - lastLogForTx[log.transactionHash] = log.logIndex; - }); - - const unsortedEvents = await Promise.all( - logs.map(({ log, parsedLog }) => { - const { transactionHash, logIndex } = log; - return ecsEventFromLog(chainId, store, log, parsedLog, lastLogForTx[transactionHash] === logIndex); - }) - ); - - const events = orderBy(unsortedEvents.filter(isDefined), ["blockNumber", "logIndex"]); - - // We defer the emissions of dev events because `ecsEventFromLog` is async and emitting them - // from within that function causes them to arrive out of order. It's better if our emitter - // can guarantee ordering for now. - events.forEach((event) => event?.devEmit && event.devEmit()); - - return events; -} diff --git a/packages/network/src/v2/keyTupleToEntityID.spec.ts b/packages/network/src/v2/keyTupleToEntityID.spec.ts deleted file mode 100644 index 2a77a2be78..0000000000 --- a/packages/network/src/v2/keyTupleToEntityID.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { keyTupleToEntityID } from "./keyTupleToEntityID"; - -describe("keyTupleToEntityID", () => { - it("should have an empty key for empty key tuple (singleton table)", () => { - expect(keyTupleToEntityID([])).toBe("0x000000000000000000000000000000000000000000000000000000000000060d"); - }); - - it("should pad addresses", () => { - expect(keyTupleToEntityID(["0xc0035952298729c7086058EaA13921626e22C070"])).toBe( - "0x000000000000000000000000c0035952298729c7086058EaA13921626e22C070" - ); - }); - - it("convert number key to padded entity ID", () => { - expect(keyTupleToEntityID([1])).toBe("0x0000000000000000000000000000000000000000000000000000000000000001"); - }); - - it("concat composite keys to a string concatenated with :", () => { - expect(keyTupleToEntityID([1, 2])).toBe( - "0x0000000000000000000000000000000000000000000000000000000000000001:0x0000000000000000000000000000000000000000000000000000000000000002" - ); - }); -}); diff --git a/packages/network/src/v2/keyTupleToEntityID.ts b/packages/network/src/v2/keyTupleToEntityID.ts deleted file mode 100644 index 9b0f95fbd6..0000000000 --- a/packages/network/src/v2/keyTupleToEntityID.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Entity } from "@latticexyz/recs"; -import { toHex, pad, isHex } from "viem"; -import { SingletonID } from "../workers"; - -// TODO: revisit key tuple format? -export function keyTupleToEntityID(keyTuple: any[]): Entity { - // v2 uses an empty key tuple as the singleton ID, so we'll return the corresponding v1 singleton entity ID to normalize this for now - if (keyTuple.length === 0) { - return SingletonID; - } - // TODO: this should probably be padded based on key schema (uint vs bytes32 will have different leading/trailing zeroes) - return keyTuple.map((key) => (isHex(key) ? pad(key, { size: 32 }) : toHex(key, { size: 32 }))).join(":") as Entity; -} diff --git a/packages/network/src/v2/mode/createModeClient.ts b/packages/network/src/v2/mode/createModeClient.ts deleted file mode 100644 index 4f8ac9c266..0000000000 --- a/packages/network/src/v2/mode/createModeClient.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { QueryLayerClient, QueryLayerDefinition } from "@latticexyz/services/mode"; -import { createChannel, createClient } from "nice-grpc-web"; - -/** - * Create a MODE QueryLayerClient - * @param url MUDE URL - * @returns MODE QueryLayerClient - */ -export function createModeClient(url: string): QueryLayerClient { - return createClient(QueryLayerDefinition, createChannel(url)); -} diff --git a/packages/network/src/v2/mode/getBlockNumberFromModeTable.ts b/packages/network/src/v2/mode/getBlockNumberFromModeTable.ts deleted file mode 100644 index fd26c958e6..0000000000 --- a/packages/network/src/v2/mode/getBlockNumberFromModeTable.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TableData } from "@latticexyz/services/mode"; -import { SchemaType } from "@latticexyz/schema-type/deprecated"; -import { decodeValue } from "../schemas/decodeValue"; - -export function getBlockNumberFromModeTable(tableData: TableData): number { - // First column is the chain followed by the block number. - if (tableData.cols[1] !== "block_number") throw new Error("Table does not contain block_number column"); - return Number(decodeValue(SchemaType.UINT256, tableData.rows[0].values[1])); -} diff --git a/packages/network/src/v2/mode/getModeBlockNumber.ts b/packages/network/src/v2/mode/getModeBlockNumber.ts deleted file mode 100644 index f8f4ebc2ab..0000000000 --- a/packages/network/src/v2/mode/getModeBlockNumber.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { QueryLayerClient } from "@latticexyz/services/mode"; -import { getBlockNumberFromModeTable } from "./getBlockNumberFromModeTable"; - -export async function getModeBlockNumber(client: QueryLayerClient, chainId: number): Promise { - try { - const response = await client.getPartialState({ - table: "block_number", - namespace: { - chainId: chainId.toString(), - }, - }); - const blockNumber = getBlockNumberFromModeTable(response.chainTables["block_number"]); - return blockNumber; - } catch (e) { - console.error("MODE Error: ", e); - return -1; - } -} diff --git a/packages/network/src/v2/mode/syncTablesFromMode.ts b/packages/network/src/v2/mode/syncTablesFromMode.ts deleted file mode 100644 index 0caa1a1453..0000000000 --- a/packages/network/src/v2/mode/syncTablesFromMode.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { ComponentValue } from "@latticexyz/recs"; -import { AbiTypeToSchemaType, encodeSchema } from "@latticexyz/schema-type/deprecated"; -import { QueryLayerClient } from "@latticexyz/services/mode"; -import { arrayToHex } from "@latticexyz/utils"; -import { TableId } from "@latticexyz/common/deprecated"; -import { Contract } from "ethers"; -import { NetworkEvents } from "../../types"; - -import { CacheStore, createCacheStore, storeEvent } from "../../workers"; -import { keyTupleToEntityID } from "../keyTupleToEntityID"; -import { registerMetadata } from "../schemas/tableMetadata"; -import { registerSchema } from "../schemas/tableSchemas"; -import { getBlockNumberFromModeTable } from "./getBlockNumberFromModeTable"; -import { decodeAbiParameters } from "viem"; - -export async function syncTablesFromMode( - client: QueryLayerClient, - chainId: number, - world: Contract, - setPercentage?: (progress: number) => void -): Promise { - const cacheStore = createCacheStore(); - - const response = await client.getState({ - chainTables: [], - worldTables: [], - namespace: { - chainId: chainId.toString(), - worldAddress: world.address, - }, - }); - console.log("syncTablesFromMode", response); - - const numRowsTotal = Object.values(response.worldTables).reduce((sum, table) => sum + table.rows.length, 0); - let numRowsProcessed = 0; - - const blockNumber = getBlockNumberFromModeTable(response.chainTables["block_number"]); - const registrationPromises: Promise[] = []; - - for (const [fullTableName, { rows, cols, types }] of Object.entries(response.worldTables)) { - const [tableNamespace, tableName] = fullTableName.split("__"); - const tableId = new TableId(tableNamespace, tableName); - - const component = tableId.toString(); - - // TODO: separate keys and values/fields in MODE, but we'll infer for now - const keyLength = cols.findIndex((col) => !col.startsWith("key_")); - const keyAbiTypes = types.slice(0, keyLength); - const keySchemaTypes = keyAbiTypes.map((abiType) => AbiTypeToSchemaType[abiType]); - const keySchemaHex = arrayToHex(encodeSchema(keySchemaTypes)); - - const fieldNames = cols.slice(keyLength); - // TODO: remove this hack once MODE is fixed (https://github.com/latticexyz/mud/issues/444) - const fieldAbiTypes = types.slice(keyLength).map((modeType) => modeType.match(/tuple\((.*)\[]\)/)?.[1] ?? modeType); - const fieldSchemaTypes = fieldAbiTypes.map((abiType) => AbiTypeToSchemaType[abiType]); - const fieldSchemaHex = arrayToHex(encodeSchema(fieldSchemaTypes)); - - const rawSchema = fieldSchemaHex + keySchemaHex.substring(2); - // TODO: refactor registerSchema/registerMetadata to take chain+world address rather than Contract - registrationPromises.push(registerSchema(world, tableId, rawSchema)); - registrationPromises.push(registerMetadata(world, tableId, { tableName, fieldNames })); - - for (const row of rows) { - console.log(tableName, keyAbiTypes, fieldAbiTypes, row.values); - const keyTuple = row.values - .slice(0, keyLength) - .map((bytes, i) => decodeAbiParameters([{ type: keyAbiTypes[i] }], arrayToHex(bytes))[0]); - const values = row.values - .slice(keyLength) - .map((bytes, i) => decodeAbiParameters([{ type: fieldAbiTypes[i] }], arrayToHex(bytes))[0]); - - const key = keyTuple.reduce>((acc, curr, i) => ({ ...acc, [i]: curr }), {}); - const entity = keyTupleToEntityID(keyTuple); - const value = Object.fromEntries(values.map((value, i) => [fieldNames[i], value])) as ComponentValue; - - storeEvent(cacheStore, { - type: NetworkEvents.NetworkComponentUpdate, - component, - entity, - key, - value, - blockNumber, - namespace: tableId.namespace, - table: tableId.name, - }); - - numRowsProcessed++; - // Update progress every 100 rows - if (numRowsProcessed % 100 === 0 && setPercentage) { - setPercentage(Math.floor(numRowsProcessed / numRowsTotal)); - } - } - console.log("done syncing from mode table", tableName); - } - console.log("done syncing from mode", numRowsProcessed, "rows processed"); - - // make sure all schemas/metadata are registered before returning to avoid downstream lookup issues - await Promise.all(registrationPromises); - - return cacheStore; -} diff --git a/packages/network/src/v2/schemas/decodeData.spec.ts b/packages/network/src/v2/schemas/decodeData.spec.ts deleted file mode 100644 index beccdeedbc..0000000000 --- a/packages/network/src/v2/schemas/decodeData.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { decodeData } from "./decodeData"; -import { decodeSchema } from "./decodeSchema"; - -describe("decodeData", () => { - describe("dynamic data layout", () => { - it("should decode", () => { - const schema = decodeSchema( - "0x00000002c5c40000000000000000000000000000000000000000000000000000002001005f000000000000000000000000000000000000000000000000000000" - ); - const data = decodeData( - schema.valueSchema, - "0x0000000000010600000000060000000100000000000000000000000000000000736368656d610000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b76616c7565536368656d6100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096b6579536368656d610000000000000000000000000000000000000000000000" - ); - expect(data).toEqual({ - 0: "schema", - 1: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b76616c7565536368656d6100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096b6579536368656d610000000000000000000000000000000000000000000000", - }); - }); - }); -}); diff --git a/packages/network/src/v2/schemas/decodeData.ts b/packages/network/src/v2/schemas/decodeData.ts deleted file mode 100644 index a8b866d78d..0000000000 --- a/packages/network/src/v2/schemas/decodeData.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - DynamicSchemaType, - getStaticByteLength, - SchemaType, - StaticSchemaType, -} from "@latticexyz/schema-type/deprecated"; -import { hexToArray } from "@latticexyz/utils"; -import { Schema } from "../common"; -import { decodeStaticField } from "./decodeStaticField"; -import { decodeDynamicField } from "./decodeDynamicField"; - -export const decodeData = (schema: Schema, hexData: string): Record => { - const data: Record = {}; - const bytes = hexToArray(hexData); - - let bytesOffset = 0; - schema.staticFields.forEach((fieldType, i) => { - const value = decodeStaticField(fieldType as StaticSchemaType, bytes, bytesOffset); - bytesOffset += getStaticByteLength(fieldType); - data[i] = value; - }); - - // Warn user if static data length doesn't match the schema, because data corruption might be possible. - const actualStaticDataLength = bytesOffset; - if (actualStaticDataLength !== schema.staticDataLength) { - console.warn( - "Decoded static data length does not match schema's expected static data length. Data may get corrupted. Is `getStaticByteLength` outdated?", - { - expectedLength: schema.staticDataLength, - actualLength: actualStaticDataLength, - bytesOffset, - schema, - hexData, - } - ); - } - - if (schema.dynamicFields.length > 0) { - const dynamicDataLayout = bytes.slice(schema.staticDataLength, schema.staticDataLength + 32); - bytesOffset += 32; - - // keep in sync with PackedCounter.sol - const packedCounterAccumulatorType = SchemaType.UINT56; - const packedCounterCounterType = SchemaType.UINT40; - const dynamicDataLength = decodeStaticField(packedCounterAccumulatorType, dynamicDataLayout, 0) as bigint; - - schema.dynamicFields.forEach((fieldType, i) => { - const dataLength = decodeStaticField( - packedCounterCounterType, - dynamicDataLayout, - getStaticByteLength(packedCounterAccumulatorType) + i * getStaticByteLength(packedCounterCounterType) - ) as number; - const value = decodeDynamicField( - fieldType as DynamicSchemaType, - bytes.slice(bytesOffset, bytesOffset + dataLength) - ); - bytesOffset += dataLength; - data[schema.staticFields.length + i] = value; - }); - - // Warn user if dynamic data length doesn't match the schema, because data corruption might be possible. - const actualDynamicDataLength = bytesOffset - 32 - actualStaticDataLength; - // TODO: refactor this so we don't break for bytes offsets >UINT40 - if (BigInt(actualDynamicDataLength) !== dynamicDataLength) { - console.warn( - "Decoded dynamic data length does not match data layout's expected data length. Data may get corrupted. Did the data layout change?", - { - expectedLength: dynamicDataLength, - actualLength: actualDynamicDataLength, - bytesOffset, - schema, - hexData, - } - ); - } - } - - return data; -}; diff --git a/packages/network/src/v2/schemas/decodeDynamicField.ts b/packages/network/src/v2/schemas/decodeDynamicField.ts deleted file mode 100644 index 5eea222b00..0000000000 --- a/packages/network/src/v2/schemas/decodeDynamicField.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - SchemaType, - DynamicSchemaType, - SchemaTypeArrayToElement, - getStaticByteLength, - SchemaTypeToPrimitiveType, -} from "@latticexyz/schema-type/deprecated"; -import { toHex, bytesToString } from "viem"; -import { decodeStaticField } from "./decodeStaticField"; - -// TODO: figure out how to switch back to `fieldType: never` for exhaustiveness check -const unsupportedDynamicField = (fieldType: SchemaType): never => { - throw new Error(`Unsupported dynamic field type: ${SchemaType[fieldType] ?? fieldType}`); -}; - -export const decodeDynamicField = >( - fieldType: T, - bytes: Uint8Array -): P => { - if (fieldType === SchemaType.BYTES) { - return toHex(bytes) as P; - } - if (fieldType === SchemaType.STRING) { - return bytesToString(bytes) as P; - } - - const staticType = SchemaTypeArrayToElement[fieldType]; - if (staticType !== undefined) { - const fieldLength = getStaticByteLength(staticType); - const arrayLength = bytes.byteLength / fieldLength; - return new Array(arrayLength) - .fill(undefined) - .map((_, i) => decodeStaticField(staticType, bytes, i * fieldLength)) as any as P; - } - - return unsupportedDynamicField(fieldType); -}; diff --git a/packages/network/src/v2/schemas/decodeField.ts b/packages/network/src/v2/schemas/decodeField.ts deleted file mode 100644 index 62b6e470ad..0000000000 --- a/packages/network/src/v2/schemas/decodeField.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DynamicSchemaType, StaticSchemaType } from "@latticexyz/schema-type/deprecated"; -import { hexToArray } from "@latticexyz/utils"; -import { Schema } from "../common"; -import { decodeStaticField } from "./decodeStaticField"; -import { decodeDynamicField } from "./decodeDynamicField"; - -export const decodeField = (schema: Schema, schemaIndex: number, hexData: string): Record => { - const data: Record = {}; - const bytes = hexToArray(hexData); - - schema.staticFields.forEach((fieldType, index) => { - if (index === schemaIndex) { - data[schemaIndex] = decodeStaticField(fieldType as StaticSchemaType, bytes, 0); - } - }); - - if (schema.dynamicFields.length > 0) { - schema.dynamicFields.forEach((fieldType, i) => { - const index = schema.staticFields.length + i; - if (index === schemaIndex) { - data[schemaIndex] = decodeDynamicField(fieldType as DynamicSchemaType, bytes); - } - }); - } - - return data; -}; diff --git a/packages/network/src/v2/schemas/decodeKeyTuple.ts b/packages/network/src/v2/schemas/decodeKeyTuple.ts deleted file mode 100644 index e666b7045b..0000000000 --- a/packages/network/src/v2/schemas/decodeKeyTuple.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SchemaTypeToAbiType } from "@latticexyz/schema-type/deprecated"; -import { Schema } from "../common"; -import { decodeAbiParameters } from "viem"; - -export function decodeKeyTuple(keySchema: Schema, keyTuple: unknown[]) { - const abiTypes = keySchema.staticFields.map((schemaType) => SchemaTypeToAbiType[schemaType]); - const decodedKeys = keyTuple.map( - (key, index) => decodeAbiParameters([{ type: abiTypes[index] }], key as `0x${string}`)[0] - ); - return decodedKeys.reduce>((acc, curr, i) => ({ ...acc, [i]: curr }), {}); -} diff --git a/packages/network/src/v2/schemas/decodeSchema.ts b/packages/network/src/v2/schemas/decodeSchema.ts deleted file mode 100644 index 7742604d90..0000000000 --- a/packages/network/src/v2/schemas/decodeSchema.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getStaticByteLength, SchemaType, SchemaTypeToAbiType } from "@latticexyz/schema-type/deprecated"; -import { hexToArray } from "@latticexyz/utils"; -import { TableSchema } from "../common"; - -export function decodeSchema(rawSchema: string): TableSchema { - const isEmpty = !rawSchema || rawSchema === "0x"; - const buffer = isEmpty ? new Uint8Array(64).buffer : hexToArray(rawSchema).buffer; - const valueSchemaBytes = new DataView(buffer); // First 32 bytes of the raw schema are the value schema - const keySchemaBytes = new DataView(buffer.slice(32)); // Last 32 bytes of the raw schema are the key schema - - const valueSchema = { ...decodeSchemaBytes(valueSchemaBytes), rawSchema, isEmpty }; - const keySchema = { ...decodeSchemaBytes(keySchemaBytes), rawSchema, isEmpty }; - - return { valueSchema, keySchema }; -} - -function decodeSchemaBytes(schemaBytes: DataView) { - const staticDataLength = schemaBytes.getUint16(0); - const numStaticFields = schemaBytes.getUint8(2); - const numDynamicFields = schemaBytes.getUint8(3); - const staticFields: SchemaType[] = []; - const dynamicFields: SchemaType[] = []; - for (let i = 4; i < 4 + numStaticFields; i++) { - staticFields.push(schemaBytes.getUint8(i)); - } - for (let i = 4 + numStaticFields; i < 4 + numStaticFields + numDynamicFields; i++) { - dynamicFields.push(schemaBytes.getUint8(i)); - } - - // validate static data length - const actualStaticDataLength = staticFields.reduce((acc, fieldType) => acc + getStaticByteLength(fieldType), 0); - if (actualStaticDataLength !== staticDataLength) { - console.error("Schema static data length mismatch! Is `getStaticByteLength` outdated?", { - schemaStaticDataLength: staticDataLength, - actualStaticDataLength, - schemaBytes, - }); - throw new Error("Schema static data length mismatch! Is `getStaticByteLength` outdated?"); - } - - const abiTypes = [...staticFields, ...dynamicFields].map((type) => SchemaTypeToAbiType[type]); - const abi = `(${abiTypes.join(",")})`; - - return { staticDataLength, staticFields, dynamicFields, abi }; -} diff --git a/packages/network/src/v2/schemas/decodeStaticField.spec.ts b/packages/network/src/v2/schemas/decodeStaticField.spec.ts deleted file mode 100644 index 7eff689b82..0000000000 --- a/packages/network/src/v2/schemas/decodeStaticField.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { SchemaType } from "@latticexyz/schema-type/deprecated"; -import { hexToArray } from "@latticexyz/utils"; -import { decodeStaticField } from "./decodeStaticField"; - -describe("decodeStaticField", () => { - describe("SchemaType.BOOL", () => { - it("should decode with no offset", () => { - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array([0]), 0)).toEqual(false); - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array([1]), 0)).toEqual(true); - }); - it("should decode with offset", () => { - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array([0, 0]), 1)).toEqual(false); - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array([0, 1]), 1)).toEqual(true); - }); - it("should decode slice", () => { - const buffer = new Uint8Array([0, 1, 2, 3]).buffer; - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array(buffer, 0, 1), 0)).toEqual(false); - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array(buffer, 1, 1), 0)).toEqual(true); - }); - it("should decode empty array", () => { - expect(decodeStaticField(SchemaType.BOOL, new Uint8Array(0), 0)).toEqual(false); - }); - }); - - describe("SchemaType.UINT256", () => { - const bytes = hexToArray("0x00000000000000000000000000000000000000000000000000000000008e216c"); - it("should decode with no offset", () => { - expect(decodeStaticField(SchemaType.UINT256, bytes, 0)).toEqual(9314668n); - }); - it("should decode empty array", () => { - expect(decodeStaticField(SchemaType.UINT256, new Uint8Array(0), 0)).toEqual(0n); - }); - }); - - describe("SchemaType.ADDRESS", () => { - it("should decode empty array", () => { - expect(decodeStaticField(SchemaType.ADDRESS, new Uint8Array(0), 0)).toEqual( - "0x0000000000000000000000000000000000000000" - ); - }); - }); - - describe("SchemaType.INT8", () => { - it("should decode type(int8).max", () => { - expect(decodeStaticField(SchemaType.INT8, hexToArray("0x7f"), 0)).toEqual(127); - }); - it("should decode type(int8).min", () => { - expect(decodeStaticField(SchemaType.INT8, hexToArray("0x80"), 0)).toEqual(-128); - }); - it("should decode empty array", () => { - expect(decodeStaticField(SchemaType.INT8, new Uint8Array(0), 0)).toEqual(0); - }); - }); - - describe("SchemaType.INT48", () => { - it("should decode type(int48).max", () => { - expect(decodeStaticField(SchemaType.INT48, hexToArray("0x7fffffffffff"), 0)).toEqual(140737488355327); - }); - it("should decode type(int48).min", () => { - expect(decodeStaticField(SchemaType.INT48, hexToArray("0x800000000000"), 0)).toEqual(-140737488355328); - }); - }); - - describe("SchemaType.INT256", () => { - const bytes = hexToArray("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd8f0"); - it("should decode with no offset", () => { - expect(decodeStaticField(SchemaType.INT256, bytes, 0)).toEqual(-10000n); - }); - }); - - describe("SchemaType.BYTES2", () => { - const bytes = hexToArray("0x0123456789abcdef"); - it("should decode with no offset", () => { - expect(decodeStaticField(SchemaType.BYTES2, bytes, 0)).toEqual("0x0123"); - }); - it("should decode with offset", () => { - expect(decodeStaticField(SchemaType.BYTES2, bytes, 2)).toEqual("0x4567"); - }); - it("should decode empty array", () => { - expect(decodeStaticField(SchemaType.BYTES2, new Uint8Array(0), 2)).toEqual("0x0000"); - }); - }); -}); diff --git a/packages/network/src/v2/schemas/decodeStaticField.ts b/packages/network/src/v2/schemas/decodeStaticField.ts deleted file mode 100644 index 922c220b0b..0000000000 --- a/packages/network/src/v2/schemas/decodeStaticField.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - getStaticByteLength, - SchemaType, - SchemaTypeToPrimitiveType, - StaticSchemaType, -} from "@latticexyz/schema-type/deprecated"; -import { toHex, pad } from "viem"; - -const unsupportedStaticField = (fieldType: never): never => { - throw new Error(`Unsupported static field type: ${SchemaType[fieldType] ?? fieldType}`); -}; - -export const decodeStaticField = >( - fieldType: T, - bytes: Uint8Array, - offset: number -): P => { - const staticLength = getStaticByteLength(fieldType); - const slice = bytes.slice(offset, offset + staticLength); - const hex = toHex(slice); - const numberHex = hex.replace(/^0x$/, "0x0"); - - switch (fieldType) { - case SchemaType.BOOL: - return (Number(numberHex) !== 0) as P; - case SchemaType.UINT8: - case SchemaType.UINT16: - case SchemaType.UINT24: - case SchemaType.UINT32: - case SchemaType.UINT40: - case SchemaType.UINT48: - return Number(numberHex) as P; - case SchemaType.UINT56: - case SchemaType.UINT64: - case SchemaType.UINT72: - case SchemaType.UINT80: - case SchemaType.UINT88: - case SchemaType.UINT96: - case SchemaType.UINT104: - case SchemaType.UINT112: - case SchemaType.UINT120: - case SchemaType.UINT128: - case SchemaType.UINT136: - case SchemaType.UINT144: - case SchemaType.UINT152: - case SchemaType.UINT160: - case SchemaType.UINT168: - case SchemaType.UINT176: - case SchemaType.UINT184: - case SchemaType.UINT192: - case SchemaType.UINT200: - case SchemaType.UINT208: - case SchemaType.UINT216: - case SchemaType.UINT224: - case SchemaType.UINT232: - case SchemaType.UINT240: - case SchemaType.UINT248: - case SchemaType.UINT256: - return BigInt(numberHex) as P; - case SchemaType.INT8: - case SchemaType.INT16: - case SchemaType.INT24: - case SchemaType.INT32: - case SchemaType.INT40: - case SchemaType.INT48: { - const max = 2 ** (staticLength * 8); - const num = Number(numberHex); - return (num < max / 2 ? num : num - max) as P; - } - case SchemaType.INT56: - case SchemaType.INT64: - case SchemaType.INT72: - case SchemaType.INT80: - case SchemaType.INT88: - case SchemaType.INT96: - case SchemaType.INT104: - case SchemaType.INT112: - case SchemaType.INT120: - case SchemaType.INT128: - case SchemaType.INT136: - case SchemaType.INT144: - case SchemaType.INT152: - case SchemaType.INT160: - case SchemaType.INT168: - case SchemaType.INT176: - case SchemaType.INT184: - case SchemaType.INT192: - case SchemaType.INT200: - case SchemaType.INT208: - case SchemaType.INT216: - case SchemaType.INT224: - case SchemaType.INT232: - case SchemaType.INT240: - case SchemaType.INT248: - case SchemaType.INT256: { - const max = 2n ** (BigInt(staticLength) * 8n); - const num = BigInt(numberHex); - return (num < max / 2n ? num : num - max) as P; - } - case SchemaType.BYTES1: - case SchemaType.BYTES2: - case SchemaType.BYTES3: - case SchemaType.BYTES4: - case SchemaType.BYTES5: - case SchemaType.BYTES6: - case SchemaType.BYTES7: - case SchemaType.BYTES8: - case SchemaType.BYTES9: - case SchemaType.BYTES10: - case SchemaType.BYTES11: - case SchemaType.BYTES12: - case SchemaType.BYTES13: - case SchemaType.BYTES14: - case SchemaType.BYTES15: - case SchemaType.BYTES16: - case SchemaType.BYTES17: - case SchemaType.BYTES18: - case SchemaType.BYTES19: - case SchemaType.BYTES20: - case SchemaType.BYTES21: - case SchemaType.BYTES22: - case SchemaType.BYTES23: - case SchemaType.BYTES24: - case SchemaType.BYTES25: - case SchemaType.BYTES26: - case SchemaType.BYTES27: - case SchemaType.BYTES28: - case SchemaType.BYTES29: - case SchemaType.BYTES30: - case SchemaType.BYTES31: - case SchemaType.BYTES32: - case SchemaType.ADDRESS: - return pad(hex, { dir: "right", size: staticLength }) as P; - default: - return unsupportedStaticField(fieldType); - } -}; diff --git a/packages/network/src/v2/schemas/decodeValue.ts b/packages/network/src/v2/schemas/decodeValue.ts deleted file mode 100644 index b8b2c06387..0000000000 --- a/packages/network/src/v2/schemas/decodeValue.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { SchemaType, SchemaTypeToPrimitiveType } from "@latticexyz/schema-type/deprecated"; -import { decodeDynamicField } from "./decodeDynamicField"; -import { decodeStaticField } from "./decodeStaticField"; - -export function decodeValueJSON(bytes: Uint8Array): any { - return JSON.parse(new TextDecoder().decode(bytes)); -} - -export function decodeValue>( - schemaType: T, - bytes: Uint8Array -): P { - switch (schemaType) { - case SchemaType.BOOL: - case SchemaType.UINT8: - case SchemaType.UINT16: - case SchemaType.UINT24: - case SchemaType.UINT32: - case SchemaType.UINT40: - case SchemaType.UINT48: - case SchemaType.UINT56: - case SchemaType.UINT64: - case SchemaType.UINT72: - case SchemaType.UINT80: - case SchemaType.UINT88: - case SchemaType.UINT96: - case SchemaType.UINT104: - case SchemaType.UINT112: - case SchemaType.UINT120: - case SchemaType.UINT128: - case SchemaType.UINT136: - case SchemaType.UINT144: - case SchemaType.UINT152: - case SchemaType.UINT160: - case SchemaType.UINT168: - case SchemaType.UINT176: - case SchemaType.UINT184: - case SchemaType.UINT192: - case SchemaType.UINT200: - case SchemaType.UINT208: - case SchemaType.UINT216: - case SchemaType.UINT224: - case SchemaType.UINT232: - case SchemaType.UINT240: - case SchemaType.UINT248: - case SchemaType.UINT256: - case SchemaType.INT8: - case SchemaType.INT16: - case SchemaType.INT24: - case SchemaType.INT32: - case SchemaType.INT40: - case SchemaType.INT48: - case SchemaType.INT56: - case SchemaType.INT64: - case SchemaType.INT72: - case SchemaType.INT80: - case SchemaType.INT88: - case SchemaType.INT96: - case SchemaType.INT104: - case SchemaType.INT112: - case SchemaType.INT120: - case SchemaType.INT128: - case SchemaType.INT136: - case SchemaType.INT144: - case SchemaType.INT152: - case SchemaType.INT160: - case SchemaType.INT168: - case SchemaType.INT176: - case SchemaType.INT184: - case SchemaType.INT192: - case SchemaType.INT200: - case SchemaType.INT208: - case SchemaType.INT216: - case SchemaType.INT224: - case SchemaType.INT232: - case SchemaType.INT240: - case SchemaType.INT248: - case SchemaType.INT256: - case SchemaType.BYTES1: - case SchemaType.BYTES2: - case SchemaType.BYTES3: - case SchemaType.BYTES4: - case SchemaType.BYTES5: - case SchemaType.BYTES6: - case SchemaType.BYTES7: - case SchemaType.BYTES8: - case SchemaType.BYTES9: - case SchemaType.BYTES10: - case SchemaType.BYTES11: - case SchemaType.BYTES12: - case SchemaType.BYTES13: - case SchemaType.BYTES14: - case SchemaType.BYTES15: - case SchemaType.BYTES16: - case SchemaType.BYTES17: - case SchemaType.BYTES18: - case SchemaType.BYTES19: - case SchemaType.BYTES20: - case SchemaType.BYTES21: - case SchemaType.BYTES22: - case SchemaType.BYTES23: - case SchemaType.BYTES24: - case SchemaType.BYTES25: - case SchemaType.BYTES26: - case SchemaType.BYTES27: - case SchemaType.BYTES28: - case SchemaType.BYTES29: - case SchemaType.BYTES30: - case SchemaType.BYTES31: - case SchemaType.BYTES32: - case SchemaType.ADDRESS: - return decodeStaticField(schemaType, bytes, 0) as P; - default: - return decodeDynamicField(schemaType, bytes) as P; - } -} diff --git a/packages/network/src/v2/schemas/tableMetadata.ts b/packages/network/src/v2/schemas/tableMetadata.ts deleted file mode 100644 index 23eb55bf6f..0000000000 --- a/packages/network/src/v2/schemas/tableMetadata.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { TableId } from "@latticexyz/common/deprecated"; -import { Contract, utils } from "ethers"; -import { metadataTableId, schemaTableId, TableMetadata } from "../common"; -import { decodeData } from "./decodeData"; -import { registerSchema } from "./tableSchemas"; -import { IStore } from "@latticexyz/store/types/ethers-contracts/IStore.sol/IStore"; - -// worldAddress:tableId => metadata -// TODO: add chain ID to namespace? -export const metadataCache: Partial>> = {}; - -// the Contract arguments below assume that they're bound to a provider - -export function getMetadata(world: Contract, table: TableId): Promise | undefined { - const cacheKey = `${world.address}:${table.toHex()}` as const; - return metadataCache[cacheKey]; -} - -export function registerMetadata( - world: Contract, - table: TableId, - metadata?: TableMetadata -): Promise { - const cacheKey = `${world.address}:${table.toHex()}` as const; - - const cachedMetadataPromise = metadataCache[cacheKey]; - if (cachedMetadataPromise) { - if (metadata) { - cachedMetadataPromise.then((cachedMetadata) => { - if (JSON.stringify(cachedMetadata) !== JSON.stringify(metadata)) { - console.warn("different metadata already registered for this table", { - table, - currentMetadata: cachedMetadata, - newMetadata: metadata, - world: world.address, - }); - } - }); - } - return cachedMetadataPromise; - } - - if (metadata) { - console.log("registering metadata for table", { table: table.toString(), metadata, world: world.address }); - const metadataPromise = Promise.resolve(metadata); - metadataCache[cacheKey] = metadataPromise; - return metadataPromise; - } - - // TODO: populate from ECS cache before fetching from RPC - - // Skip lazily fetching internal tables - if (table.toHex() === schemaTableId.toHex() || table.toHex() === metadataTableId.toHex()) { - return Promise.resolve(undefined); - } - - console.log("fetching metadata for table", { table: table.toString(), world: world.address }); - const metadataPromise = Promise.all([ - registerSchema(world, metadataTableId), - // TODO: figure out how to pass in rawSchema, it was giving me "incorrect length" errors before - // we still have to do both calls though, and this is a getter, so this should be fine - (world as IStore)["getRecord(bytes32,bytes32[])"](metadataTableId.toHex(), [table.toHex()]), - ]).then(([{ valueSchema }, metadataRecord]) => { - if (valueSchema.isEmpty) { - console.warn("Metadata schema not found", { table: metadataTableId.toString(), world: world.address }); - } - if (!metadataRecord || metadataRecord === "0x") { - console.warn("Metadata not found for table", { table: table.toString(), world: world.address }); - } - const decoded = decodeData(valueSchema, metadataRecord); - const tableName = decoded[0]; - if (tableName !== table.name) { - console.warn("Metadata table name does not match table ID", { - tableName, - tableId: table.toString(), - world: world.address, - }); - } - const [fieldNames] = utils.defaultAbiCoder.decode(["string[]"], decoded[1]); - return { tableName, fieldNames }; - }); - metadataCache[cacheKey] = metadataPromise; - return metadataPromise; -} diff --git a/packages/network/src/v2/schemas/tableSchemas.ts b/packages/network/src/v2/schemas/tableSchemas.ts deleted file mode 100644 index fe56942974..0000000000 --- a/packages/network/src/v2/schemas/tableSchemas.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Contract } from "ethers"; -import { TableId } from "@latticexyz/common/deprecated"; -import { TableSchema } from "../common"; -import { decodeSchema } from "./decodeSchema"; -import { IStore } from "@latticexyz/store/types/ethers-contracts/IStore.sol/IStore"; - -// worldAddress:tableId => schema -// TODO: add chain ID to namespace? -const schemaCache: Partial>> = {}; - -// the Contract arguments below assume that they're bound to a provider - -export function getSchema(world: IStore, table: TableId): Promise | undefined { - const cacheKey = `${world.address}:${table.toHex()}` as const; - return schemaCache[cacheKey]; -} - -export function registerSchema(world: Contract, table: TableId, rawSchema?: string): Promise { - const cacheKey = `${world.address}:${table.toHex()}` as const; - - const existingSchema = schemaCache[cacheKey]; - if (existingSchema) { - // Warn if a different schema was already registered - if (rawSchema) { - existingSchema.then((schema) => { - if (schema.valueSchema.rawSchema !== rawSchema) { - console.warn("a different schema was already registered for this table", { - table, - currentSchema: schema, - newSchema: rawSchema, - world: world.address, - }); - } - }); - } - return existingSchema; - } - - if (rawSchema) { - console.log("registering schema for table", { table: table.toString(), world: world.address, rawSchema }); - const schema = Promise.resolve(decodeSchema(rawSchema)); - schemaCache[cacheKey] = schema; - return schema; - } - - // TODO: populate from ECS cache before fetching from RPC - - console.log("fetching schema for table", { table: table.toString(), world: world.address }); - const store = world as IStore; - const rawKeySchemaPromise = store.getKeySchema(table.toHex()); - const rawValueSchemaPromise = store.getSchema(table.toHex()); - const rawSchemaPromise = Promise.all([rawKeySchemaPromise, rawValueSchemaPromise]).then( - ([rawKeySchema, rawValueSchema]) => rawValueSchema + rawKeySchema.substring(2) - ); - const schema = rawSchemaPromise.then((rawSchema: string) => { - const decodedSchema = decodeSchema(rawSchema); - if (decodedSchema.valueSchema.isEmpty) { - console.warn("Schema not found for table", { table: table.toString(), world: world.address }); - } - return decodedSchema; - }); - schemaCache[cacheKey] = schema; - return schema; -} diff --git a/packages/network/src/v2/snapSync/getSnapSyncRecords.ts b/packages/network/src/v2/snapSync/getSnapSyncRecords.ts deleted file mode 100644 index 165f6774f0..0000000000 --- a/packages/network/src/v2/snapSync/getSnapSyncRecords.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { TableId } from "@latticexyz/common/deprecated"; -import { Hex } from "viem"; -import snapSyncSystemAbi from "./snapSyncSystemAbi"; -import { Contract, Signer, providers } from "ethers"; -import { RawTableRecord } from "../../types"; - -export async function getSnapSyncRecords( - worldAddress: string, - tables: TableId[], - currentBlockNumber: number, - signerOrProvider: Signer | providers.JsonRpcProvider -) { - const snapSyncContract = new Contract(worldAddress, snapSyncSystemAbi, signerOrProvider); - - const chunkSize = 100; - const tableIds = tables.map((table) => table.toHex()); - const tableRecords = [] as RawTableRecord[]; - for (const tableId of tableIds) { - const numKeys = ( - await snapSyncContract.callStatic["snapSync_system_getNumKeysInTable"](tableId, { - blockTag: currentBlockNumber, - }) - ).toNumber(); - if (numKeys === 0) continue; - - let remainingKeys = numKeys; - const numChunks = Math.ceil(numKeys / chunkSize); - for (let i = 0; i < numChunks; i++) { - const limit = Math.min(remainingKeys, chunkSize); - const offset = i * chunkSize; - remainingKeys -= limit; - - const records = await snapSyncContract.callStatic["snapSync_system_getRecords"](tableId, limit, offset, { - blockTag: currentBlockNumber, - }); - const transformedRecords = records.map((record: [string, string[], string]) => { - return { - tableId: TableId.fromHex(record[0] as Hex), - keyTuple: record[1], - value: record[2], - }; - }); - tableRecords.push(...transformedRecords); - } - } - - return tableRecords; -} diff --git a/packages/network/src/v2/snapSync/index.ts b/packages/network/src/v2/snapSync/index.ts deleted file mode 100644 index 02114ca54b..0000000000 --- a/packages/network/src/v2/snapSync/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./getSnapSyncRecords"; diff --git a/packages/network/src/v2/snapSync/snapSyncSystemAbi.ts b/packages/network/src/v2/snapSync/snapSyncSystemAbi.ts deleted file mode 100644 index 8dd4d712b0..0000000000 --- a/packages/network/src/v2/snapSync/snapSyncSystemAbi.ts +++ /dev/null @@ -1,69 +0,0 @@ -// TODO: autogenerate this and import from the SnapSync module -// (see https://github.com/latticexyz/mud/issues/836) -export default [ - { - inputs: [ - { - internalType: "bytes32", - name: "tableId", - type: "bytes32", - }, - ], - name: "snapSync_system_getNumKeysInTable", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "tableId", - type: "bytes32", - }, - { - internalType: "uint256", - name: "limit", - type: "uint256", - }, - { - internalType: "uint256", - name: "offset", - type: "uint256", - }, - ], - name: "snapSync_system_getRecords", - outputs: [ - { - components: [ - { - internalType: "bytes32", - name: "tableId", - type: "bytes32", - }, - { - internalType: "bytes32[]", - name: "keyTuple", - type: "bytes32[]", - }, - { - internalType: "bytes", - name: "value", - type: "bytes", - }, - ], - internalType: "struct SyncRecord[]", - name: "", - type: "tuple[]", - }, - ], - stateMutability: "view", - type: "function", - }, -]; diff --git a/packages/network/src/v2/syncUtils.ts b/packages/network/src/v2/syncUtils.ts deleted file mode 100644 index 9ccd7f2a5f..0000000000 --- a/packages/network/src/v2/syncUtils.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Observable, concatMap, map, of } from "rxjs"; -import { fetchStoreEvents } from "./fetchStoreEvents"; -import { NetworkComponentUpdate, NetworkEvent } from "../types"; -import { orderBy } from "lodash"; -import debug from "debug"; -import { awaitPromise, range } from "@latticexyz/utils"; - -/** - * Create a RxJS stream of {@link NetworkComponentUpdate}s by listening to new - * blocks from the blockNumber$ stream and fetching the corresponding block - * from the connected RPC. - * - * @dev Only use if {@link createLatestEventStreamService} is not available. - * - * @param blockNumber$ Block number stream - * @param fetchWorldEvents Function to fetch World events in a block range ({@link createFetchWorldEventsInBlockRange}). - * @returns Stream of {@link NetworkComponentUpdate}s. - */ -export function createLatestEventStreamRPC( - blockNumber$: Observable, - boundFetchStoreEvents: (fromBlock: number, toBlock: number) => ReturnType -): Observable { - let lastSyncedBlockNumber: number | undefined; - - return blockNumber$.pipe( - map(async (blockNumber) => { - const from = - lastSyncedBlockNumber == null || lastSyncedBlockNumber >= blockNumber ? blockNumber : lastSyncedBlockNumber + 1; - const to = blockNumber; - lastSyncedBlockNumber = to; - const storeEvents = await boundFetchStoreEvents(from, to); - - const events = orderBy(storeEvents, ["blockNumber", "logIndex"]); - debug(`fetched ${events.length} events from block range ${from} -> ${to}`); - - return events; - }), - awaitPromise(), - concatMap((v) => of(...v)) - ); -} - -/** - * Fetch ECS events from contracts in the given block range. - * - * @param fetchWorldEvents Function to fetch World events in a block range ({@link createFetchWorldEventsInBlockRange}). - * @param fromBlockNumber Start of block range (inclusive). - * @param toBlockNumber End of block range (inclusive). - * @param interval Chunk fetching the blocks in intervals to avoid overwhelming the client. - * @returns Promise resolving with array containing the contract ECS events in the given block range. - */ -export async function fetchEventsInBlockRangeChunked( - boundFetchStoreEvents: (fromBlock: number, toBlock: number) => ReturnType, - fromBlockNumber: number, - toBlockNumber: number, - interval = 50, - setPercentage?: (percentage: number) => void -): Promise { - let events: NetworkComponentUpdate[] = []; - const delta = toBlockNumber - fromBlockNumber; - const numSteps = Math.ceil(delta / interval); - const steps = [...range(numSteps, interval, fromBlockNumber)]; - - for (let i = 0; i < steps.length; i++) { - const from = steps[i]; - const to = i === steps.length - 1 ? toBlockNumber : steps[i + 1] - 1; - const storeEvents = await boundFetchStoreEvents(from, to); - - if (setPercentage) setPercentage(((i * interval) / delta) * 100); - debug(`initial sync fetched ${events.length} events from block range ${from} -> ${to}`); - - events = events.concat(orderBy(storeEvents, ["blockNumber", "logIndex"])); - } - - return events; -} diff --git a/packages/network/src/v2/transformTableRecordsIntoEvents.ts b/packages/network/src/v2/transformTableRecordsIntoEvents.ts deleted file mode 100644 index 71ac0aa692..0000000000 --- a/packages/network/src/v2/transformTableRecordsIntoEvents.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Entity } from "@latticexyz/recs"; -import { NetworkEvents, RawTableRecord } from "../types"; -import { decodeStoreSetRecord } from "./decodeStoreSetRecord"; -import { Contract } from "ethers"; -import { keyTupleToEntityID } from "./keyTupleToEntityID"; -import { NetworkComponentUpdate } from "../types"; - -export async function transformTableRecordsIntoEvents( - storeContract: Contract, - records: RawTableRecord[], - blockNumber: number -): Promise { - const events = [] as NetworkComponentUpdate[]; - - for (const record of records) { - const { tableId, keyTuple, value } = record; - const { indexedValues, namedValues, indexedKey, namedKey } = await decodeStoreSetRecord( - storeContract, - tableId, - keyTuple, - value - ); - const key = { ...indexedKey, ...namedKey }; - const component = tableId.toString(); - const entityId = keyTupleToEntityID(keyTuple); - - const ecsEvent = { - type: NetworkEvents.NetworkComponentUpdate, - component, - entity: entityId as Entity, - key, - value: { ...indexedValues, ...namedValues }, - lastEventInTx: false, - txHash: "cache", - blockNumber, - namespace: tableId.namespace, - table: tableId.name, - } satisfies NetworkComponentUpdate; - - events.push(ecsEvent); - } - - return events; -} diff --git a/packages/network/src/workers/CacheStore.spec.ts b/packages/network/src/workers/CacheStore.spec.ts deleted file mode 100644 index 0e7bcb0df2..0000000000 --- a/packages/network/src/workers/CacheStore.spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { Entity } from "@latticexyz/recs"; -import { packTuple } from "@latticexyz/utils"; -import { NetworkComponentUpdate, NetworkEvents } from "../types"; -import { - createCacheStore, - getCacheStoreEntries, - getIndexDbECSCache, - loadIndexDbCacheStore, - mergeCacheStores, - saveCacheStoreToIndexDb, - storeEvent, -} from "./CacheStore"; - -import "fake-indexeddb/auto"; - -describe("CacheStore", () => { - describe("createCacheStore", () => { - it("should return a new cache store object", () => { - const cacheStore = createCacheStore(); - expect(cacheStore.components.length).toBe(0); - expect(cacheStore.entities.length).toBe(0); - expect(cacheStore.componentToIndex.size).toBe(0); - expect(cacheStore.entityToIndex.size).toBe(0); - expect(cacheStore.state.size).toBe(0); - expect(cacheStore.blockNumber).toBe(0); - expect(cacheStore.tables).toEqual({}); - expect(cacheStore.keys).toEqual({}); - }); - }); - - describe("storeEvent", () => { - it("should store events to the cacheStore", () => { - const event: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: false, - blockNumber: 1, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - const cacheStore = createCacheStore(); - storeEvent(cacheStore, event); - - expect(cacheStore.components).toEqual(["Position"]); - expect(cacheStore.entities).toEqual(["0x00"]); - expect(cacheStore.componentToIndex.get("Position")).toBe(0); - expect(cacheStore.entityToIndex.get("0x00")).toBe(0); - expect(cacheStore.state.size).toBe(1); - expect(cacheStore.blockNumber).toBe(0); - expect([...cacheStore.state.entries()]).toEqual([[packTuple([0, 0]), { x: 1, y: 2 }]]); - expect(cacheStore.tables).toEqual({ 0: { namespace: "namespace", table: "Position" } }); - expect(cacheStore.keys).toEqual({ 0: { key: "0x00" } }); - - const event2: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01" as Entity, - key: { key: "0x01" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: true, - blockNumber: 1, - txHash: "", - namespace: "namespace", - table: "Position", - }; - storeEvent(cacheStore, event2); - - expect(cacheStore.components).toEqual(["Position"]); - expect(cacheStore.entities).toEqual(["0x00", "0x01"]); - expect(cacheStore.componentToIndex.get("Position")).toBe(0); - expect(cacheStore.entityToIndex.get("0x00")).toBe(0); - expect(cacheStore.entityToIndex.get("0x01")).toBe(1); - expect(cacheStore.state.size).toBe(2); - expect(cacheStore.blockNumber).toBe(0); - expect([...cacheStore.state.entries()]).toEqual([ - [packTuple([0, 0]), { x: 1, y: 2 }], - [packTuple([0, 1]), { x: 1, y: 2 }], - ]); - expect(cacheStore.tables).toEqual({ 0: { namespace: "namespace", table: "Position" } }); - expect(cacheStore.keys).toEqual({ - 0: { key: "0x00" }, - 1: { key: "0x01" }, - }); - }); - - it("should set block number to one less than the last received event", () => { - const event: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: false, - blockNumber: 1, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - const cacheStore = createCacheStore(); - storeEvent(cacheStore, event); - expect(cacheStore.blockNumber).toBe(0); - - storeEvent(cacheStore, { ...event, blockNumber: 2 }); - expect(cacheStore.blockNumber).toBe(1); - }); - }); - - describe("getCacheStoreEntries", () => { - it("should return an interator of NetworkComponentUpdates representing the current state", () => { - const cacheStore = createCacheStore(); - - const event: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: false, - blockNumber: 1, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - storeEvent(cacheStore, event); - - expect([...getCacheStoreEntries(cacheStore)]).toEqual([ - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00", - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: false, - blockNumber: 0, - txHash: "cache", - namespace: "namespace", - table: "Position", - }, - ]); - - const event2: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 2, y: 2 }, - lastEventInTx: false, - blockNumber: 2, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - storeEvent(cacheStore, event2); - - expect([...getCacheStoreEntries(cacheStore)]).toEqual([ - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00", - key: { key: "0x00" }, - component: "Position", - value: { x: 2, y: 2 }, - lastEventInTx: false, - blockNumber: 1, - txHash: "cache", - namespace: "namespace", - table: "Position", - }, - ]); - - const event3: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01" as Entity, - key: { key: "0x01" }, - component: "Position", - value: { x: -1, y: 2 }, - lastEventInTx: false, - blockNumber: 3, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - storeEvent(cacheStore, event3); - - expect([...getCacheStoreEntries(cacheStore)]).toEqual([ - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00", - key: { key: "0x00" }, - component: "Position", - value: { x: 2, y: 2 }, - lastEventInTx: false, - blockNumber: 2, - txHash: "cache", - namespace: "namespace", - table: "Position", - }, - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01", - key: { key: "0x01" }, - component: "Position", - value: { x: -1, y: 2 }, - lastEventInTx: false, - blockNumber: 2, - txHash: "cache", - namespace: "namespace", - table: "Position", - }, - ]); - }); - }); - - describe("mergeCacheStores", () => { - it("should return a new cache store including the state of all input cache stores", () => { - const cacheStore1 = createCacheStore(); - const cacheStore2 = createCacheStore(); - - const event1: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - lastEventInTx: false, - blockNumber: 1, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - const event2: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01" as Entity, - key: { key: "0x01" }, - component: "Health", - value: { value: 1 }, - lastEventInTx: false, - blockNumber: 2, - txHash: "", - namespace: "namespace", - table: "Health", - }; - - storeEvent(cacheStore1, event1); - storeEvent(cacheStore1, event2); - - const event3: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 3, y: 2 }, - lastEventInTx: false, - blockNumber: 3, - txHash: "", - namespace: "namespace", - table: "Position", - }; - - const event4: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Speed", - value: { value: 10 }, - lastEventInTx: true, - blockNumber: 4, - txHash: "", - namespace: "namespace", - table: "Speed", - }; - - storeEvent(cacheStore2, event3); - storeEvent(cacheStore2, event4); - - const mergedCacheStore = mergeCacheStores([cacheStore1, cacheStore2]); - - const entries = [...getCacheStoreEntries(mergedCacheStore)]; - - expect(entries).toEqual([ - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00", - key: { key: "0x00" }, - component: "Position", - value: { x: 3, y: 2 }, - lastEventInTx: false, - blockNumber: 3, - txHash: "cache", - namespace: "namespace", - table: "Position", - }, - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01", - key: { key: "0x01" }, - component: "Health", - value: { value: 1 }, - lastEventInTx: false, - blockNumber: 3, - txHash: "cache", - namespace: "namespace", - table: "Health", - }, - { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00", - key: { key: "0x00" }, - component: "Speed", - value: { value: 10 }, - lastEventInTx: false, - blockNumber: 3, - txHash: "cache", - namespace: "namespace", - table: "Speed", - }, - ]); - }); - }); - - describe("indexDB", () => { - it("should store and load a cacheStore to/from indexDB", async () => { - const cache = await getIndexDbECSCache(4242, "0x0", 1, indexedDB); - - const cacheStore = createCacheStore(); - - storeEvent(cacheStore, { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 1, y: 2 }, - blockNumber: 1, - namespace: "namespace", - table: "Position", - }); - - storeEvent(cacheStore, { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x01" as Entity, - key: { key: "0x01" }, - component: "Health", - value: { value: 1 }, - blockNumber: 2, - namespace: "namespace", - table: "Health", - }); - - storeEvent(cacheStore, { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Position", - value: { x: 3, y: 2 }, - blockNumber: 3, - namespace: "namespace", - table: "Position", - }); - - storeEvent(cacheStore, { - type: NetworkEvents.NetworkComponentUpdate, - entity: "0x00" as Entity, - key: { key: "0x00" }, - component: "Speed", - value: { value: 10 }, - blockNumber: 4, - namespace: "namespace", - table: "Speed", - }); - - await saveCacheStoreToIndexDb(cache, cacheStore); - const loadedCacheStore = await loadIndexDbCacheStore(cache); - - expect([...getCacheStoreEntries(loadedCacheStore)]).toEqual([...getCacheStoreEntries(cacheStore)]); - }); - }); -}); diff --git a/packages/network/src/workers/CacheStore.ts b/packages/network/src/workers/CacheStore.ts deleted file mode 100644 index 0dd8a2c580..0000000000 --- a/packages/network/src/workers/CacheStore.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Components, ComponentValue, Entity, SchemaOf } from "@latticexyz/recs"; -import { packTuple, transformIterator, unpackTuple } from "@latticexyz/utils"; -import { initCache } from "../initCache"; -import { ECSStateReply } from "@latticexyz/services/ecs-snapshot"; -import { NetworkComponentUpdate, NetworkEvents } from "../types"; -import { debug as parentDebug } from "./debug"; -import { Subject } from "rxjs"; - -const debug = parentDebug.extend("CacheStore"); - -export type State = Map; -export type CacheStore = ReturnType; -export type ECSCache = Awaited>; - -export function getCacheId(namespace: string, chainId: number, worldAddress: string) { - return `${namespace}-${chainId}-${worldAddress}`; -} - -export function createCacheStore() { - const components: string[] = []; - const componentToIndex = new Map(); - const entities: string[] = []; - const entityToIndex = new Map(); - const blockNumber = 0; - const state: State = new Map(); - const componentUpdate$ = new Subject<{ component: string; entity: Entity; blockNumber: number }>(); - const keys: Record> = {}; // Mapping from entity index to key tuple - const tables: Record = {}; // Mapping from component index to namespace/table - - return { components, componentToIndex, entities, entityToIndex, blockNumber, state, componentUpdate$, keys, tables }; -} - -export function storeEvent( - cacheStore: CacheStore, - { - component, - entity, - value, - partialValue, - initialValue, - blockNumber, - key, - namespace, - table, - }: Omit, "lastEventInTx" | "txHash"> -) { - const { components, entities, componentToIndex, entityToIndex, state, keys, tables } = cacheStore; - - // Get component index - let componentIndex = componentToIndex.get(component); - if (componentIndex == null) { - componentIndex = components.push(component) - 1; - componentToIndex.set(component as string, componentIndex); - } - - // Get entity index - let entityIndex = entityToIndex.get(entity); - if (entityIndex == null) { - entityIndex = entities.push(entity) - 1; - entityToIndex.set(entity, entityIndex); - } - - // Store the key - if (key) { - keys[entityIndex] = key; - } - - // Store the namespace/table - if (namespace != null && table != null) { - tables[componentIndex] = { namespace, table }; - } - - // Entity index gets the right 24 bits, component index the left 8 bits - const cacheKey = packTuple([componentIndex, entityIndex]); - - // keep this logic aligned with applyNetworkUpdates - if (partialValue !== undefined) { - const currentValue = state.get(cacheKey); - state.set(cacheKey, { ...initialValue, ...currentValue, ...partialValue }); - } else if (value === undefined) { - console.log("deleting key", cacheKey); - state.delete(cacheKey); - } else { - state.set(cacheKey, value); - } - - // Set block number to one less than the last received event's block number - // (Events are expected to be ordered, so once a new block number appears, - // the previous block number is done processing) - cacheStore.blockNumber = blockNumber - 1; - - cacheStore.componentUpdate$.next({ component, entity, blockNumber }); -} - -export function storeEvents( - cacheStore: CacheStore, - events: Omit, "lastEventInTx" | "txHash">[] -) { - for (const event of events) { - storeEvent(cacheStore, event); - } -} - -export function getCacheStoreEntries({ - blockNumber, - state, - components, - entities, - keys, - tables, -}: CacheStore): IterableIterator> { - return transformIterator(state.entries(), ([cacheKey, value]) => { - const [componentIndex, entityIndex] = unpackTuple(cacheKey); - const component = components[componentIndex]; - const entity = entities[entityIndex]; - const key = keys[entityIndex]; - const { namespace, table } = tables[componentIndex]; - - if (component == null || entity == null) { - throw new Error(`Unknown component / entity: ${component}, ${entity}`); - } - - const ecsEvent: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component, - entity: entity as Entity, - value: value as ComponentValue>, - namespace, - table, - key, - lastEventInTx: false, - txHash: "cache", - blockNumber: blockNumber, - }; - - return ecsEvent; - }); -} - -export function mergeCacheStores(stores: CacheStore[]): CacheStore { - const result = createCacheStore(); - - // Sort by block number (increasing) - const sortedStores = [...stores].sort((a, b) => a.blockNumber - b.blockNumber); - - // Insert the cached events into the result store (from stores with low block number to high number) - for (const store of sortedStores) { - for (const updateEvent of getCacheStoreEntries(store)) { - storeEvent(result, updateEvent); - } - } - - result.blockNumber = sortedStores[sortedStores.length - 1].blockNumber; - - return result; -} - -export async function saveCacheStoreToIndexDb(cache: ECSCache, store: CacheStore) { - debug("store cache with size", store.state.size, "at block", store.blockNumber); - await cache.set("ComponentValues", "current", store.state); - await cache.set("Mappings", "components", store.components); - await cache.set("Mappings", "entities", store.entities); - await cache.set("BlockNumber", "current", store.blockNumber); - await cache.set("Keys", "current", store.keys); - await cache.set("Tables", "current", store.tables); -} - -export async function loadIndexDbCacheStore(cache: ECSCache): Promise { - const state = (await cache.get("ComponentValues", "current")) ?? new Map(); - const blockNumber = (await cache.get("BlockNumber", "current")) ?? 0; - const components = (await cache.get("Mappings", "components")) ?? []; - const entities = (await cache.get("Mappings", "entities")) ?? []; - const keys = (await cache.get("Keys", "current")) ?? {}; - const tables = (await cache.get("Tables", "current")) ?? {}; - const componentToIndex = new Map(); - const entityToIndex = new Map(); - const componentUpdate$ = new Subject<{ component: string; entity: Entity; blockNumber: number }>(); - - // Init componentToIndex map - for (let i = 0; i < components.length; i++) { - componentToIndex.set(components[i], i); - } - - // Init entityToIndex map - for (let i = 0; i < entities.length; i++) { - entityToIndex.set(entities[i], i); - } - - return { state, blockNumber, components, entities, componentToIndex, entityToIndex, componentUpdate$, keys, tables }; -} - -export async function getIndexDBCacheStoreBlockNumber(cache: ECSCache): Promise { - return (await cache.get("BlockNumber", "current")) ?? 0; -} - -export function getIndexDbECSCache(chainId: number, worldAddress: string, version?: number, idb?: IDBFactory) { - return initCache<{ - ComponentValues: State; - BlockNumber: number; - Mappings: string[]; - Snapshot: ECSStateReply; - Keys: Record>; - Tables: Record; - }>( - getCacheId("ECSCache", chainId, worldAddress), // Store a separate cache for each World contract address - ["ComponentValues", "BlockNumber", "Mappings", "Snapshot", "Keys", "Tables"], - version, - idb - ); -} diff --git a/packages/network/src/workers/Recover.worker.ts b/packages/network/src/workers/Recover.worker.ts deleted file mode 100644 index 285982a01d..0000000000 --- a/packages/network/src/workers/Recover.worker.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Message } from "@latticexyz/services/ecs-relay"; -import { expose } from "threads/worker"; -import { verifyMessage } from "ethers/lib/utils.js"; -import { messagePayload } from "../utils"; - -function recoverAddress(msg: Message) { - return verifyMessage(messagePayload(msg), msg.signature); -} - -expose({ recoverAddress }); diff --git a/packages/network/src/workers/Sync.worker.ts b/packages/network/src/workers/Sync.worker.ts deleted file mode 100644 index b1763573ae..0000000000 --- a/packages/network/src/workers/Sync.worker.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { runWorker } from "@latticexyz/utils"; -import { SyncWorker } from "./SyncWorker"; - -runWorker(new SyncWorker()); diff --git a/packages/network/src/workers/SyncWorker.spec.ts b/packages/network/src/workers/SyncWorker.spec.ts deleted file mode 100644 index 4bf5c51a6b..0000000000 --- a/packages/network/src/workers/SyncWorker.spec.ts +++ /dev/null @@ -1,425 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { JsonRpcProvider } from "@ethersproject/providers"; -import { keccak256, sleep } from "@latticexyz/utils"; -import { computed } from "mobx"; -import { ack, Input, InputType, SyncWorker } from "./SyncWorker"; -import { concatMap, from, map, Subject, Subscription, timer } from "rxjs"; -import { isNetworkComponentUpdateEvent, NetworkComponentUpdate, NetworkEvents } from "../types"; -import { Components, Entity } from "@latticexyz/recs"; -import { createCacheStore, storeEvent } from "./CacheStore"; -import "fake-indexeddb/auto"; -import { SingletonID, SyncState } from "./constants"; -import * as syncUtils from "../v2/syncUtils"; -import { createLatestEventStreamRPC } from "../v2/syncUtils"; - -// Test constants -const cacheBlockNumber = 99; -const cacheEvent = { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x10", - entity: "0x11" as Entity, - key: { key: "0x11" }, - value: {}, - txHash: "0x12", - lastEventInTx: true, - blockNumber: cacheBlockNumber + 1, - namespace: "namespace", - table: "table", -} as NetworkComponentUpdate; -const blockNumber$ = new Subject(); -const latestEvent$ = new Subject(); -const lastGapStateEventBlockNumber = 999; -const gapStateEvents = [ - { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x20", - entity: "0x21" as Entity, - key: { key: "0x21" }, - value: {}, - txHash: "0x22", - lastEventInTx: true, - blockNumber: lastGapStateEventBlockNumber, - namespace: "namespace", - table: "table2", - }, -] as NetworkComponentUpdate[]; - -// Mocks -jest.mock("../createProvider", () => ({ - ...jest.requireActual("../createProvider"), - createReconnectingProvider: () => ({ - providers: computed(() => ({ - json: new JsonRpcProvider(""), - })), - }), -})); - -jest.mock("./CacheStore", () => ({ - ...jest.requireActual("./CacheStore"), - getIndexDbECSCache: () => ({ - get: (store: string, key: string) => { - if (store === "BlockNumber" && key === "current") return cacheBlockNumber; - }, - }), - loadIndexDbCacheStore: () => { - const cache = createCacheStore(); - - storeEvent(cache, cacheEvent); - - return cache; - }, - saveCacheStoreToIndexDb: jest.fn(), -})); - -jest.mock("../createBlockNumberStream", () => ({ - ...jest.requireActual("../createBlockNumberStream"), - createBlockNumberStream: () => ({ blockNumber$ }), -})); - -jest.mock("../v2/syncUtils", () => ({ - ...jest.requireActual("../v2/syncUtils"), - createFetchWorldEventsInBlockRange: () => () => Promise.resolve([]), - createLatestEventStreamRPC: jest.fn(() => latestEvent$), - createLatestEventStreamService: jest.fn(() => latestEvent$), - fetchStateInBlockRange: jest.fn((fetchWorldEvents: any, boundFetchStoreEvents: any, from: number, to: number) => { - const store = createCacheStore(); - if (to > 1000) { - for (const event of gapStateEvents) storeEvent(store, event); - } - return store; - }), - fetchEventsInBlockRangeChunked: jest.fn( - (fetchWorldEvents: any, boundFetchStoreEvents: any, from: number, to: number) => { - if (to > 1000) { - return gapStateEvents; - } - return []; - } - ), -})); - -// Tests -describe("Sync.worker", () => { - let input$: Subject; - let output: jest.Mock; - let subscription: Subscription; - let ackSubscription: Subscription; - let worker: SyncWorker; - - beforeEach(async () => { - input$ = new Subject(); - worker = new SyncWorker(); - - // "ack" stream - ackSubscription = timer(0, 1) - .pipe(map(() => ack)) - .subscribe(input$); - - output = jest.fn(); - subscription = worker - .work(input$) - .pipe(concatMap((updates) => from(updates))) - .subscribe((e) => { - if (isNetworkComponentUpdateEvent(e) && e.component !== keccak256("component.LoadingState")) { - output(e); - } - }); - }); - - afterEach(() => { - subscription?.unsubscribe(); - ackSubscription?.unsubscribe(); - jest.clearAllMocks(); - }); - - it.only("should report the current loading state via the `component.LoadingState` component", async () => { - const freshOutput = jest.fn(); - const freshWorker = new SyncWorker(); - const freshInput$ = new Subject(); - - const sub = (subscription = freshWorker.work(freshInput$).subscribe(freshOutput)); - - freshInput$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - chainId: 4242, - }, - initialBlockNumber: 0, - }, - }); - - const finalUpdate: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: keccak256("component.LoadingState"), - value: { state: SyncState.LIVE, msg: "Streaming live events", percentage: 100 }, - entity: SingletonID, - key: {}, - txHash: "worker", - lastEventInTx: false, - blockNumber: 99, - namespace: "mudsync", - table: "LoadingState", - }; - - await sleep(0); - blockNumber$.next(101); - await sleep(50); - expect(freshOutput).toHaveBeenCalledWith(expect.arrayContaining([finalUpdate])); - - sub?.unsubscribe(); - }); - - it("should pass live events to the output", async () => { - input$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - streamServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - chainId: 4242, - }, - initialBlockNumber: 0, - }, - }); - - await sleep(0); - blockNumber$.next(101); - await sleep(0); - - const event: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x00", - entity: "0x01" as Entity, - key: { key: "0x01" }, - value: {}, - txHash: "0x02", - lastEventInTx: true, - blockNumber: 111, - namespace: "namespace", - table: "table", - }; - - latestEvent$.next(event); - await sleep(50); - - // Expect output to contain live event - expect(output).toHaveBeenCalledWith(event); - }); - - it("should sync live events from rpc if streaming service is not available", async () => { - input$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - streamServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - chainId: 4242, - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - }, - initialBlockNumber: 0, - }, - }); - await sleep(0); - expect(createLatestEventStreamRPC).toHaveBeenCalled(); - }); - - it("should sync from the cache if the snapshot service is not available", async () => { - input$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - streamServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - chainId: 4242, - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - }, - initialBlockNumber: 0, - }, - }); - - await sleep(0); - blockNumber$.next(101); - await sleep(50); - - // Expect output to contain the events from the cache - expect(output).toHaveBeenCalledTimes(1); - expect(output).toHaveBeenCalledWith({ - ...cacheEvent, - blockNumber: cacheBlockNumber, - lastEventInTx: false, - txHash: "cache", - }); - }); - - it("should fetch the state diff between cache/snapshot and current block number", async () => { - input$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - streamServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - chainId: 4242, - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - }, - initialBlockNumber: 0, - }, - }); - - const currentBlockNumber = 1001; - - await sleep(0); - blockNumber$.next(currentBlockNumber); - await sleep(50); - - // Expect state between cache block number and current block number to have been fetched - expect(syncUtils.fetchEventsInBlockRangeChunked).toHaveBeenLastCalledWith( - expect.anything(), - expect.anything(), - cacheBlockNumber, - currentBlockNumber, - expect.anything(), - expect.anything() - ); - - // Expect output to contain the events from the the gap state - expect(output).toHaveBeenCalledWith({ - ...gapStateEvents[0], - blockNumber: lastGapStateEventBlockNumber - 1, - lastEventInTx: false, - txHash: "cache", - }); - }); - - it("should first load from cache, then fetch the state gap, then pass live events", async () => { - input$.next({ - type: InputType.Config, - data: { - snapshotServiceUrl: "", - streamServiceUrl: "", - chainId: 4242, - worldContract: { address: "0x00", abi: [] }, - provider: { - chainId: 4242, - jsonRpcUrl: "", - options: { batch: false, pollingInterval: 1000, skipNetworkCheck: true }, - }, - initialBlockNumber: 0, - }, - }); - input$.next(ack); - - const firstLiveBlockNumber = 1001; - const secondLiveBlockNumber = 1002; - - const event1: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x99", - entity: "0x01" as Entity, - key: { key: "0x01" }, - value: {}, - txHash: "0x02", - lastEventInTx: true, - blockNumber: firstLiveBlockNumber, - namespace: "namespace", - table: "table", - }; - - const event2: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x0999", - entity: "0x01" as Entity, - key: { key: "0x00" }, - value: {}, - txHash: "0x02", - lastEventInTx: true, - blockNumber: secondLiveBlockNumber, - namespace: "namespace", - table: "table", - }; - - const event3: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: "0x9999", - entity: "0x01" as Entity, - key: { key: "0x01" }, - value: {}, - txHash: "0x02", - lastEventInTx: true, - blockNumber: 1003, - namespace: "namespace", - table: "table", - }; - - await sleep(0); - // Event 1 and 2 arrive while the initial sync is in progress - latestEvent$.next(event1); - blockNumber$.next(firstLiveBlockNumber); - latestEvent$.next(event2); - blockNumber$.next(secondLiveBlockNumber); - await sleep(0); - - // Event 3 arrives after the initial sync - latestEvent$.next(event3); - await sleep(50); - - // Expect output to contain all events (cache, gap state, live events) - expect(output).toHaveBeenCalledTimes(5); - - // Expect output to contain cache events - expect(output).toHaveBeenNthCalledWith(1, { - ...cacheEvent, - blockNumber: secondLiveBlockNumber - 1, - lastEventInTx: false, - txHash: "cache", - }); - - // Expect state between cache block number and current block number to have been fetched - expect(syncUtils.fetchEventsInBlockRangeChunked).toHaveBeenLastCalledWith( - expect.anything(), - expect.anything(), - cacheBlockNumber, - firstLiveBlockNumber, - expect.anything(), - expect.anything() - ); - - // Expect output to contain the events from the cache and the gap state - expect(output).toHaveBeenNthCalledWith(2, { - ...gapStateEvents[0], - blockNumber: secondLiveBlockNumber - 1, - lastEventInTx: false, - txHash: "cache", - }); - - // Expect output to contain live events that arrived before the initial sync was complete - expect(output).toHaveBeenNthCalledWith(3, { ...event1, lastEventInTx: false, txHash: "cache" }); - expect(output).toHaveBeenNthCalledWith(4, { - ...event2, - lastEventInTx: false, - txHash: "cache", - blockNumber: secondLiveBlockNumber - 1, - }); - - // Expect output to contain live events that arrived after the initial sync - expect(output).toHaveBeenNthCalledWith(5, event3); - }); -}); diff --git a/packages/network/src/workers/SyncWorker.ts b/packages/network/src/workers/SyncWorker.ts deleted file mode 100644 index ed1b271cd8..0000000000 --- a/packages/network/src/workers/SyncWorker.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { awaitStreamValue, DoWork, filterNullish, keccak256, streamToDefinedComputed } from "@latticexyz/utils"; -import { bufferTime, concat, concatMap, filter, ignoreElements, map, Observable, of, Subject, take } from "rxjs"; -import { - isNetworkComponentUpdateEvent, - NetworkComponentUpdate, - NetworkEvent, - NetworkEvents, - SyncStateStruct, - SyncWorkerConfig, -} from "../types"; -import { Components, ComponentValue, SchemaOf } from "@latticexyz/recs"; -import { - createCacheStore, - getCacheStoreEntries, - getIndexDBCacheStoreBlockNumber, - getIndexDbECSCache, - loadIndexDbCacheStore, - saveCacheStoreToIndexDb, - storeEvent, - storeEvents, -} from "./CacheStore"; -import { createReconnectingProvider } from "../createProvider"; -import { computed } from "mobx"; -import { createBlockNumberStream } from "../createBlockNumberStream"; -import { SingletonID, SyncState } from "./constants"; -import { debug as parentDebug } from "./debug"; -import { fetchStoreEvents } from "../v2/fetchStoreEvents"; -import IStoreAbi from "@latticexyz/store/abi/IStore.sol/IStore.abi.json"; -import { Contract } from "ethers"; -import { createModeClient } from "../v2/mode/createModeClient"; -import { syncTablesFromMode } from "../v2/mode/syncTablesFromMode"; -import { getModeBlockNumber } from "../v2/mode/getModeBlockNumber"; -import { transformTableRecordsIntoEvents } from "../v2/transformTableRecordsIntoEvents"; -import * as devObservables from "../dev/observables"; -import { getEventSelector } from "viem"; -import { createLatestEventStreamRPC, fetchEventsInBlockRangeChunked } from "../v2/syncUtils"; - -const debug = parentDebug.extend("SyncWorker"); - -const VERSION = 3; - -export enum InputType { - Ack, - Config, -} -export type Config = { type: InputType.Config; data: SyncWorkerConfig }; -export type Ack = { type: InputType.Ack }; -export const ack = { type: InputType.Ack as const }; -export type Input = Config | Ack; - -export class SyncWorker implements DoWork[]> { - private input$ = new Subject(); - private output$ = new Subject>(); - private syncState: SyncStateStruct = { state: SyncState.CONNECTING, msg: "", percentage: 0 }; - - constructor() { - debug("creating SyncWorker"); - this.init(); - } - - /** - * Pass a loading state component update to the main thread. - * Can be used to indicate the initial loading state on a loading screen. - * @param loadingState { - * state: {@link SyncState}, - * msg: Message to describe the current loading step. - * percentage: Number between 0 and 100 to describe the loading progress. - * } - * @param blockNumber Optional: block number to pass in the component update. - */ - private setLoadingState( - loadingState: Partial<{ state: SyncState; msg: string; percentage: number }>, - blockNumber = 0 - ) { - const newLoadingState = { ...this.syncState, ...loadingState }; - this.syncState = newLoadingState; - const update: NetworkComponentUpdate = { - type: NetworkEvents.NetworkComponentUpdate, - component: keccak256("component.LoadingState"), - value: newLoadingState as unknown as ComponentValue>, - entity: SingletonID, - key: {}, - namespace: "mudsync", - table: "LoadingState", - txHash: "worker", - lastEventInTx: false, - blockNumber, - }; - - this.output$.next(update); - } - - /** - * Start the sync process. - * - * 1. Get config - * 2. Load initial state - * 2.1 Get cache block number - * 2.2 Get mode block number - * 2.3 Load from more recent source - * 3. Cach up to current block number by requesting events from RPC ( -> TODO: Replace with own service) - * 4. Keep in sync - * 4.1 If available keep in sync with mode - * 4.2 Else keep in sync with RPC - */ - private async init() { - this.setLoadingState({ state: SyncState.CONNECTING, msg: "Connecting...", percentage: 0 }); - - // Turn config into variable accessible outside the stream - const computedConfig = await streamToDefinedComputed( - this.input$.pipe( - map((e) => (e.type === InputType.Config ? e.data : undefined)), - filterNullish() - ) - ); - const config = computedConfig.get(); - const { modeUrl, chainId, worldContract, disableCache, initialRecords } = config; - devObservables.worldAddress$.next(worldContract.address); - - // Set default values for cacheAgeThreshold and cacheInterval - const cacheAgeThreshold = config.cacheAgeThreshold || 100; - const cacheInterval = config.cacheInterval || 1; - - // Set up - const { providers } = await createReconnectingProvider(computed(() => computedConfig.get().provider)); - const provider = providers.get().json; - const modeClient = modeUrl ? createModeClient(modeUrl) : undefined; - const indexDbCache = await getIndexDbECSCache(chainId, worldContract.address, VERSION); - - // Start syncing current events, but only start streaming to output once gap between initial state and current block is closed - - debug("start initial sync"); - this.setLoadingState({ state: SyncState.INITIAL, msg: "Starting initial sync", percentage: 0 }); - let passLiveEventsToOutput = false; - const cacheStore = { current: createCacheStore() }; - devObservables.cacheStore$.next(cacheStore.current); - const { blockNumber$ } = createBlockNumberStream(providers); - // The RPC is only queried if this stream is subscribed to - - const storeContract = new Contract(worldContract.address, IStoreAbi, provider); - const boundFetchStoreEvents = (fromBlock: number, toBlock: number) => - fetchStoreEvents(storeContract, fromBlock, toBlock); - - const latestEvent$ = createLatestEventStreamRPC(blockNumber$, boundFetchStoreEvents); - - const initialLiveEvents: NetworkComponentUpdate[] = []; - latestEvent$.subscribe((event) => { - // If initial sync is in progress, temporary store the events to apply later - // Ignore system calls during initial sync - if (!passLiveEventsToOutput) { - if (isNetworkComponentUpdateEvent(event)) { - initialLiveEvents.push(event); - } - return; - } - - if (isNetworkComponentUpdateEvent(event)) { - storeEvent(cacheStore.current, event); - // Store cache to indexdb every block - if (event.blockNumber > cacheStore.current.blockNumber + 1 && event.blockNumber % cacheInterval === 0) { - saveCacheStoreToIndexDb(indexDbCache, cacheStore.current); - } - } - - const networkEvent = event as NetworkEvent; - if (isNetworkComponentUpdateEvent(networkEvent)) { - // Remove devEmit from event before passing it to the main thread - // because it is not serializable - delete networkEvent.devEmit; - } - - this.output$.next(networkEvent); - }); - const streamStartBlockNumberPromise = awaitStreamValue(blockNumber$); - - // Load initial state from cache or snapshot service - this.setLoadingState({ state: SyncState.INITIAL, msg: "Fetching cache block number", percentage: 0 }); - const cacheBlockNumber = !disableCache ? await getIndexDBCacheStoreBlockNumber(indexDbCache) : -1; - this.setLoadingState({ percentage: 50 }); - const modeBlockNumber = modeClient ? await getModeBlockNumber(modeClient, chainId) : -1; - - let initialBlockNumber = config.initialBlockNumber; - if (initialBlockNumber < 0) { - const worldDeployLogs = await provider.getLogs({ - address: worldContract.address, - topics: [getEventSelector("event HelloWorld()")], - fromBlock: "earliest", - }); - initialBlockNumber = worldDeployLogs.length > 0 ? worldDeployLogs[0].blockNumber : 0; - } - - this.setLoadingState({ percentage: 100 }); - debug(`cache block: ${cacheBlockNumber}, start sync at ${initialBlockNumber}`); - - let initialState = createCacheStore(); - - if (initialRecords) { - console.log("Initial state from pre-loaded records"); - this.setLoadingState({ state: SyncState.INITIAL, msg: "Loading initial state", percentage: 0 }); - const events = await transformTableRecordsIntoEvents(storeContract, initialRecords, initialBlockNumber); - storeEvents(initialState, events); - initialState.blockNumber = initialBlockNumber; - } else if (initialBlockNumber > Math.max(cacheBlockNumber, modeBlockNumber)) { - // Skip initializing from cache/mode if the initial block number is newer than all of them - initialState.blockNumber = initialBlockNumber; - } else { - // Load from cache if the mode is less than blocks newer than the cache - const syncFromMode = modeClient && modeBlockNumber > cacheBlockNumber + cacheAgeThreshold; - console.log("syncFromMode", syncFromMode); - - if (syncFromMode) { - console.log("Initial sync from MODE"); - this.setLoadingState({ state: SyncState.INITIAL, msg: "Fetching initial state from MODE", percentage: 0 }); - initialState = await syncTablesFromMode(modeClient, chainId, storeContract, (percentage: number) => - this.setLoadingState({ percentage }) - ); - this.setLoadingState({ percentage: 100 }); - } else if (!disableCache) { - this.setLoadingState({ state: SyncState.INITIAL, msg: "Fetching initial state from cache", percentage: 0 }); - initialState = await loadIndexDbCacheStore(indexDbCache); - this.setLoadingState({ percentage: 100 }); - } - - debug(`got ${initialState.state.size} items from ${syncFromMode ? "mode" : "cache"}`); - } - - // Load events from gap between initial state and current block number from RPC - const streamStartBlockNumber = await streamStartBlockNumberPromise; - this.setLoadingState({ - state: SyncState.INITIAL, - msg: `Fetching state from block ${initialState.blockNumber} to ${streamStartBlockNumber}`, - percentage: 0, - }); - - const gapStateEvents = await fetchEventsInBlockRangeChunked( - boundFetchStoreEvents, - initialState.blockNumber, - streamStartBlockNumber, - 50, - (percentage: number) => this.setLoadingState({ percentage }) - ); - - debug( - `got ${gapStateEvents.length} items from block range ${initialState.blockNumber} -> ${streamStartBlockNumber}` - ); - - // Merge initial state, gap state and live events since initial sync started - storeEvents(initialState, [...gapStateEvents, ...initialLiveEvents]); - cacheStore.current = initialState; - devObservables.cacheStore$.next(cacheStore.current); - debug(`initial sync state size: ${cacheStore.current.state.size}`); - - this.setLoadingState({ - state: SyncState.INITIAL, - msg: `Initializing with ${cacheStore.current.state.size} state entries`, - percentage: 0, - }); - - // Pass current cacheStore to output and start passing live events - let i = 0; - for (const update of getCacheStoreEntries(cacheStore.current)) { - i++; - this.output$.next(update as NetworkEvent); - if (i % 5000 === 0) { - const percentage = Math.floor((i / cacheStore.current.state.size) * 100); - this.setLoadingState({ percentage }); - } - } - - // Save initial state to cache - saveCacheStoreToIndexDb(indexDbCache, cacheStore.current); - - // Let the client know loading is complete - this.setLoadingState( - { state: SyncState.LIVE, msg: `Streaming live events`, percentage: 100 }, - cacheStore.current.blockNumber - ); - passLiveEventsToOutput = true; - } - - public work(input$: Observable): Observable[]> { - input$.subscribe(this.input$); - const throttledOutput$ = new Subject[]>(); - - this.output$ - .pipe( - bufferTime(16, null, 50), - filter((updates) => updates.length > 0), - concatMap((updates) => - concat( - of(updates), - input$.pipe( - filter((e) => e.type === InputType.Ack), - take(1), - ignoreElements() - ) - ) - ) - ) - .subscribe(throttledOutput$); - - return throttledOutput$; - } -} diff --git a/packages/network/src/workers/constants.ts b/packages/network/src/workers/constants.ts deleted file mode 100644 index 2bc759272c..0000000000 --- a/packages/network/src/workers/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Entity } from "@latticexyz/recs"; -import { Hex, pad } from "viem"; - -export enum SyncState { - CONNECTING, - INITIAL, - LIVE, -} - -export const SingletonID = pad("0x060d" as Hex, { size: 32 }) as Entity; - -/** @deprecated Import SingletonID instead */ -export const GodID = SingletonID; diff --git a/packages/network/src/workers/debug.ts b/packages/network/src/workers/debug.ts deleted file mode 100644 index 54e4656a16..0000000000 --- a/packages/network/src/workers/debug.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { debug as parentDebug } from "../debug"; - -export const debug = parentDebug.extend("workers"); diff --git a/packages/network/src/workers/index.ts b/packages/network/src/workers/index.ts deleted file mode 100644 index a0372de20f..0000000000 --- a/packages/network/src/workers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./SyncWorker"; -export * from "./CacheStore"; -export * from "./constants"; diff --git a/packages/network/tsconfig.json b/packages/network/tsconfig.json deleted file mode 100644 index 6adb64d710..0000000000 --- a/packages/network/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "declaration": true, - "sourceMap": true, - "outDir": "dist", - "isolatedModules": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - }, - "include": ["src"] -} diff --git a/packages/network/tsup.config.ts b/packages/network/tsup.config.ts deleted file mode 100644 index 0d07eae09f..0000000000 --- a/packages/network/tsup.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ["src/index.ts", "src/dev/index.ts", "src/workers/Sync.worker.ts", "src/workers/Recover.worker.ts"], - target: "esnext", - format: ["esm"], - dts: false, - sourcemap: true, - clean: true, - minify: true, -}); diff --git a/packages/react/package.json b/packages/react/package.json index 4986df0104..0e14374214 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -36,6 +36,7 @@ "@vitejs/plugin-react": "^4.0.0", "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", + "jsdom": "^22.1.0", "react-test-renderer": "^18.2.0", "tsup": "^6.7.0", "vite": "^4.3.6", diff --git a/packages/services/package.json b/packages/services/package.json index de0a50bfc0..0e39680cc7 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -28,7 +28,7 @@ "./protobuf/ts/ecs-stream/ecs-stream.ts" ], "faucet": [ - "./protobuf/ts/faucet/faucet.ts" + "./ts/faucet/index.ts" ], "mode": [ "./protobuf/ts/mode/mode.ts" @@ -47,6 +47,7 @@ "dependencies": { "long": "^5.2.1", "nice-grpc-common": "^2.0.2", + "nice-grpc-web": "^2.0.1", "protobufjs": "^7.2.3" }, "devDependencies": { diff --git a/packages/network/src/createFaucetService.ts b/packages/services/ts/faucet/createFaucetService.ts similarity index 86% rename from packages/network/src/createFaucetService.ts rename to packages/services/ts/faucet/createFaucetService.ts index 19ec92c215..f82eb82c22 100644 --- a/packages/network/src/createFaucetService.ts +++ b/packages/services/ts/faucet/createFaucetService.ts @@ -1,4 +1,4 @@ -import { FaucetServiceDefinition } from "@latticexyz/services/faucet"; +import { FaucetServiceDefinition } from "../../protobuf/ts/faucet/faucet"; import { createChannel, createClient, RawClient } from "nice-grpc-web"; import { FromTsProtoServiceDefinition } from "nice-grpc-web/lib/service-definitions/ts-proto"; diff --git a/packages/services/ts/faucet/index.ts b/packages/services/ts/faucet/index.ts new file mode 100644 index 0000000000..7eabecd978 --- /dev/null +++ b/packages/services/ts/faucet/index.ts @@ -0,0 +1,2 @@ +export * from "../../protobuf/ts/faucet/faucet"; +export { createFaucetService } from "./createFaucetService"; diff --git a/packages/services/tsup.config.ts b/packages/services/tsup.config.ts index 9107e598b5..a5783e7937 100644 --- a/packages/services/tsup.config.ts +++ b/packages/services/tsup.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ "ecs-relay": "protobuf/ts/ecs-relay/ecs-relay.ts", "ecs-snapshot": "protobuf/ts/ecs-snapshot/ecs-snapshot.ts", "ecs-stream": "protobuf/ts/ecs-stream/ecs-stream.ts", - faucet: "protobuf/ts/faucet/faucet.ts", + faucet: "ts/faucet/index.ts", mode: "protobuf/ts/mode/mode.ts", }, target: "esnext", diff --git a/packages/std-client/package.json b/packages/std-client/package.json index 08aed526af..f8969399d7 100644 --- a/packages/std-client/package.json +++ b/packages/std-client/package.json @@ -10,16 +10,12 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./dev": "./dist/dev/index.js" + "./deprecated": "./dist/deprecated.js" }, "typesVersions": { "*": { - "index": [ - "./src/index.ts" - ], - "dev": [ - "./src/dev/index.ts" + "deprecated": [ + "./src/deprecated/index.ts" ] } }, @@ -36,7 +32,6 @@ "@latticexyz/cli": "workspace:*", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", - "@latticexyz/network": "workspace:*", "@latticexyz/recs": "workspace:*", "@latticexyz/solecs": "workspace:*", "@latticexyz/store": "workspace:*", diff --git a/packages/std-client/src/components/ActionComponent.ts b/packages/std-client/src/deprecated/components/ActionComponent.ts similarity index 100% rename from packages/std-client/src/components/ActionComponent.ts rename to packages/std-client/src/deprecated/components/ActionComponent.ts diff --git a/packages/std-client/src/components/BoolComponent.ts b/packages/std-client/src/deprecated/components/BoolComponent.ts similarity index 100% rename from packages/std-client/src/components/BoolComponent.ts rename to packages/std-client/src/deprecated/components/BoolComponent.ts diff --git a/packages/std-client/src/components/CoordComponent.ts b/packages/std-client/src/deprecated/components/CoordComponent.ts similarity index 100% rename from packages/std-client/src/components/CoordComponent.ts rename to packages/std-client/src/deprecated/components/CoordComponent.ts diff --git a/packages/std-client/src/components/DevHighlight.ts b/packages/std-client/src/deprecated/components/DevHighlight.ts similarity index 100% rename from packages/std-client/src/components/DevHighlight.ts rename to packages/std-client/src/deprecated/components/DevHighlight.ts diff --git a/packages/std-client/src/components/NumberComponent.ts b/packages/std-client/src/deprecated/components/NumberComponent.ts similarity index 100% rename from packages/std-client/src/components/NumberComponent.ts rename to packages/std-client/src/deprecated/components/NumberComponent.ts diff --git a/packages/std-client/src/components/StringComponent.ts b/packages/std-client/src/deprecated/components/StringComponent.ts similarity index 100% rename from packages/std-client/src/components/StringComponent.ts rename to packages/std-client/src/deprecated/components/StringComponent.ts diff --git a/packages/std-client/src/components/VoxelCoordComponent.ts b/packages/std-client/src/deprecated/components/VoxelCoordComponent.ts similarity index 100% rename from packages/std-client/src/components/VoxelCoordComponent.ts rename to packages/std-client/src/deprecated/components/VoxelCoordComponent.ts diff --git a/packages/std-client/src/components/index.ts b/packages/std-client/src/deprecated/components/index.ts similarity index 100% rename from packages/std-client/src/components/index.ts rename to packages/std-client/src/deprecated/components/index.ts diff --git a/packages/std-client/src/getBurnerWallet.ts b/packages/std-client/src/deprecated/getBurnerWallet.ts similarity index 95% rename from packages/std-client/src/getBurnerWallet.ts rename to packages/std-client/src/deprecated/getBurnerWallet.ts index 5fecbdf823..5b5c88124e 100644 --- a/packages/std-client/src/getBurnerWallet.ts +++ b/packages/std-client/src/deprecated/getBurnerWallet.ts @@ -12,6 +12,9 @@ function assertPrivateKey(privateKey: string, cacheKey: string): asserts private privateKeyToAccount(privateKey); } +/** + * @deprecated use `getBurnerPrivateKey` from `@latticexyz/common` instead + */ export function getBurnerWallet(cacheKey = "mud:burnerWallet"): BehaviorSubject { const cachedPrivateKey = localStorage.getItem(cacheKey); diff --git a/packages/std-client/src/hooks.ts b/packages/std-client/src/deprecated/hooks.ts similarity index 100% rename from packages/std-client/src/hooks.ts rename to packages/std-client/src/deprecated/hooks.ts diff --git a/packages/std-client/src/index.ts b/packages/std-client/src/deprecated/index.ts similarity index 86% rename from packages/std-client/src/index.ts rename to packages/std-client/src/deprecated/index.ts index 28dbc827d5..285fb284ae 100644 --- a/packages/std-client/src/index.ts +++ b/packages/std-client/src/deprecated/index.ts @@ -2,5 +2,4 @@ export * from "./components"; export * from "./utils"; export * from "./hooks"; export * from "./systems"; -export * from "./setup"; export { getBurnerWallet } from "./getBurnerWallet"; diff --git a/packages/std-client/src/systems/ActionSystem/constants.ts b/packages/std-client/src/deprecated/systems/ActionSystem/constants.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/constants.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/constants.ts diff --git a/packages/std-client/src/systems/ActionSystem/createActionSystem.spec.ts b/packages/std-client/src/deprecated/systems/ActionSystem/createActionSystem.spec.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/createActionSystem.spec.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/createActionSystem.spec.ts diff --git a/packages/std-client/src/systems/ActionSystem/createActionSystem.ts b/packages/std-client/src/deprecated/systems/ActionSystem/createActionSystem.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/createActionSystem.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/createActionSystem.ts diff --git a/packages/std-client/src/systems/ActionSystem/index.ts b/packages/std-client/src/deprecated/systems/ActionSystem/index.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/index.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/index.ts diff --git a/packages/std-client/src/systems/ActionSystem/types.ts b/packages/std-client/src/deprecated/systems/ActionSystem/types.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/types.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/types.ts diff --git a/packages/std-client/src/systems/ActionSystem/utils/index.ts b/packages/std-client/src/deprecated/systems/ActionSystem/utils/index.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/utils/index.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/utils/index.ts diff --git a/packages/std-client/src/systems/ActionSystem/utils/waitForActionCompletion.ts b/packages/std-client/src/deprecated/systems/ActionSystem/utils/waitForActionCompletion.ts similarity index 100% rename from packages/std-client/src/systems/ActionSystem/utils/waitForActionCompletion.ts rename to packages/std-client/src/deprecated/systems/ActionSystem/utils/waitForActionCompletion.ts diff --git a/packages/std-client/src/systems/index.ts b/packages/std-client/src/deprecated/systems/index.ts similarity index 100% rename from packages/std-client/src/systems/index.ts rename to packages/std-client/src/deprecated/systems/index.ts diff --git a/packages/std-client/src/deprecated/utils.ts b/packages/std-client/src/deprecated/utils.ts new file mode 100644 index 0000000000..314d324f34 --- /dev/null +++ b/packages/std-client/src/deprecated/utils.ts @@ -0,0 +1,113 @@ +import { Component, Schema, ComponentValue, componentValueEquals, Metadata, Entity } from "@latticexyz/recs"; +import { keccak256, deferred } from "@latticexyz/utils"; +import { filter } from "rxjs"; + +/** + * Generate a random color based on the given id. + * @param id Any string + * @returns A color in the range 0x000000 - 0xFFFFFF + */ +export function randomColor(id: string): number { + const randSeed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values + function seedRand(seed: string) { + for (let i = 0; i < randSeed.length; i++) { + randSeed[i] = 0; + } + for (let i = 0; i < seed.length; i++) { + randSeed[i % 4] = (randSeed[i % 4] << 5) - randSeed[i % 4] + seed.charCodeAt(i); + } + } + + function rand() { + const t = randSeed[0] ^ (randSeed[0] << 11); + randSeed[0] = randSeed[1]; + randSeed[1] = randSeed[2]; + randSeed[2] = randSeed[3]; + randSeed[3] = randSeed[3] ^ (randSeed[3] >> 19) ^ t ^ (t >> 8); + return (randSeed[3] >>> 0) / ((1 << 31) >>> 0); + } + + function createColor(): [number, number, number] { + // hue is the whole color spectrum + const h = Math.floor(rand() * 360) / 360; + //saturation goes from 40 to 100, it avoids greyish colors + // --> Multiply by 0.75 to limit saturation + // const s = ((rand() * 60 + 40) / 100) * 0.75; + const s = 80 / 100; + // lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% + // --> Multiply by 0.65 to shift + // const l = (((rand() + rand() + rand() + rand()) * 25) / 100) * 0.65; + const l = 70 / 100; + return [h, s, l]; + } + + function RgbToHex(red: number, green: number, blue: number): number { + return (red << 16) | (green << 8) | blue; + } + + function hue2rgb(p: number, q: number, t: number) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + function HSLToRGB(h: number, s: number, l: number): [number, number, number] { + let r: number; + let g: number; + let b: number; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r * 255, g * 255, b * 255]; + } + + seedRand(id); + + return RgbToHex(...HSLToRGB(...createColor())); +} + +export function getStringColor(address: string) { + return randomColor(keccak256(address).substring(2)); +} + +export function waitForComponentValueIn( + component: Component, + entity: Entity, + values: Partial>[] +): Promise { + const [resolve, , promise] = deferred(); + + let dispose = resolve; + const subscription = component.update$ + .pipe( + filter((e) => e.entity === entity && Boolean(values.find((value) => componentValueEquals(value, e.value[0])))) + ) + .subscribe(() => { + resolve(); + dispose(); + }); + + dispose = () => subscription?.unsubscribe(); + + return promise; +} + +export async function waitForComponentValue( + component: Component, + entity: Entity, + value: Partial> +): Promise { + await waitForComponentValueIn(component, entity, [value]); +} diff --git a/packages/std-client/src/dev/index.ts b/packages/std-client/src/dev/index.ts deleted file mode 100644 index 39dd88fa2d..0000000000 --- a/packages/std-client/src/dev/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./observables"; diff --git a/packages/std-client/src/dev/observables.ts b/packages/std-client/src/dev/observables.ts deleted file mode 100644 index 14abbdabdd..0000000000 --- a/packages/std-client/src/dev/observables.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Abi } from "abitype"; -import { BehaviorSubject } from "rxjs"; - -export const worldAbi$ = new BehaviorSubject(null); diff --git a/packages/std-client/src/setup/index.ts b/packages/std-client/src/setup/index.ts deleted file mode 100644 index 454ff85a20..0000000000 --- a/packages/std-client/src/setup/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./setupMUDV2Network"; -export * from "./utils"; -export * from "./types"; diff --git a/packages/std-client/src/setup/setupMUDV2Network.ts b/packages/std-client/src/setup/setupMUDV2Network.ts deleted file mode 100644 index ef21773859..0000000000 --- a/packages/std-client/src/setup/setupMUDV2Network.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { - createNetwork, - createContracts, - Mappings, - createTxQueue, - createSyncWorker, - Ack, - InputType, - SingletonID, - RawTableRecord, - keyTupleToEntityID, -} from "@latticexyz/network"; -import { BehaviorSubject, concatMap, from, Subject } from "rxjs"; -import { Components, defineComponent, Type, World } from "@latticexyz/recs"; -import { computed } from "mobx"; -import { keccak256 } from "@latticexyz/utils"; -import { TableId } from "@latticexyz/common/deprecated"; -import { World as WorldContract } from "@latticexyz/world/types/ethers-contracts/World"; -import { IWorldKernel__factory } from "@latticexyz/world/types/ethers-contracts/factories/IWorldKernel.sol/IWorldKernel__factory"; -import { defineStringComponent } from "../components"; -import { ContractComponent, ContractComponents, SetupContractConfig } from "./types"; -import { applyNetworkUpdates, createEncoders } from "./utils"; -import * as devObservables from "../dev/observables"; -import { Abi } from "abitype"; -import { createDatabase, createDatabaseClient } from "@latticexyz/store-cache"; -import { StoreConfig } from "@latticexyz/store"; - -type SetupMUDV2NetworkOptions = { - networkConfig: SetupContractConfig; - world: World; - contractComponents: C; - initialGasPrice?: number; - fetchSystemCalls?: boolean; - syncThread?: "main" | "worker"; - syncStoreCache?: boolean; - storeConfig: S; - worldAbi: Abi; // TODO: should this extend IWorldKernel ABI or a subset of? -}; - -export async function setupMUDV2Network({ - networkConfig, - world, - contractComponents, - initialGasPrice, - fetchSystemCalls, - syncThread, - storeConfig, - syncStoreCache = true, - worldAbi = IWorldKernel__factory.abi, -}: SetupMUDV2NetworkOptions) { - devObservables.worldAbi$.next(worldAbi); - - const SystemsRegistry = defineStringComponent(world, { - id: "SystemsRegistry", - metadata: { contractId: "world.component.systems" }, - }); - const ComponentsRegistry = defineStringComponent(world, { - id: "ComponentsRegistry", - metadata: { contractId: "world.component.components" }, - }); - - // used by SyncWorker to notify client of sync progress - const LoadingState = defineComponent( - world, - { - state: Type.Number, - msg: Type.String, - percentage: Type.Number, - }, - { - id: "LoadingState", - metadata: { contractId: "component.LoadingState" }, - } - ); - - const storeSchemaTableId = new TableId("mudstore", "schema"); - const storeSchemaComponent = defineComponent( - world, - { valueSchema: Type.String, keySchema: Type.String }, - { - metadata: { - contractId: storeSchemaTableId.toHex(), - tableId: storeSchemaTableId.toString(), - }, - } - ); - - const components = { - // v2 components - storeSchemaComponent, - ...contractComponents, - // v1 components - SystemsRegistry, - ComponentsRegistry, - LoadingState, - } satisfies Components; - - // Mapping from component contract id to key in components object - const mappings: Mappings = {}; - - // Function to register new components in mappings object - function registerComponent(key: string, component: ContractComponent) { - if (typeof component.metadata?.tableId === "string") { - mappings[component.metadata.tableId] = key; - } else { - mappings[ - keccak256(typeof component.metadata?.contractId === "string" ? component.metadata.contractId : component.id) - ] = key; - } - } - - // Register initial components in mappings object - for (const key of Object.keys(components)) { - registerComponent(key, components[key]); - } - - const network = await createNetwork(networkConfig); - world.registerDisposer(network.dispose); - - const signerOrProvider = computed(() => network.signer.get() || network.providers.get().json); - - const { contracts, config: contractsConfig } = await createContracts<{ World: WorldContract }>({ - config: { World: { abi: IWorldKernel__factory.abi, address: networkConfig.worldAddress } }, - signerOrProvider, - }); - - const gasPriceInput$ = new BehaviorSubject( - // If no initial gas price is provided, check the gas price once and add a 30% buffer - initialGasPrice || Math.ceil((await signerOrProvider.get().getGasPrice()).toNumber() * 1.3) - ); - - // TODO: replace this with `fastTxExecutor` - const { txQueue, dispose: disposeTxQueue } = createTxQueue(contracts, network, gasPriceInput$, { - devMode: networkConfig.devMode, - }); - world.registerDisposer(disposeTxQueue); - - // For LoadingState updates - const singletonEntity = world.registerEntity({ id: SingletonID }); - // Register player entity - const address = network.connectedAddress.get(); - const playerEntity = address ? world.registerEntity({ id: keyTupleToEntityID([address]) }) : undefined; - - // Create sync worker - const ack$ = new Subject(); - // Avoid passing externalProvider to sync worker (too complex to copy) - const { - provider: { externalProvider: _, ...providerConfig }, - ...syncWorkerConfig - } = networkConfig; - const { ecsEvents$, input$, dispose } = createSyncWorker(ack$, { thread: syncThread }); - world.registerDisposer(dispose); - - function startSync(initialRecords?: RawTableRecord[], initialBlockNumber?: number) { - input$.next({ - type: InputType.Config, - data: { - ...syncWorkerConfig, - provider: providerConfig, - worldContract: contractsConfig.World, - initialBlockNumber: initialBlockNumber ?? networkConfig.initialBlockNumber, - disableCache: networkConfig.disableCache || [31337, 1337].includes(networkConfig.chainId), // Disable cache on local networks (hardhat / anvil) - fetchSystemCalls, - initialRecords, - }, - }); - } - - const db = createDatabase(); - const storeCache = createDatabaseClient(db, storeConfig); - - const { txReduced$ } = applyNetworkUpdates( - world, - components, - ecsEvents$, - mappings, - ack$, - syncStoreCache ? storeConfig : undefined, - syncStoreCache ? storeCache : undefined - ); - - const encoders = networkConfig.encoders - ? createEncoders(world, ComponentsRegistry, signerOrProvider) - : new Promise((resolve) => resolve({})); - - return { - txQueue, - txReduced$, - encoders, - network, - startSync, - gasPriceInput$, - ecsEvent$: ecsEvents$.pipe(concatMap((updates) => from(updates))), - mappings, - registerComponent, - networkConfig, - world, - components, - singletonEntityId: SingletonID, - singletonEntity, - playerEntity, - storeCache, - }; -} diff --git a/packages/std-client/src/setup/types.ts b/packages/std-client/src/setup/types.ts deleted file mode 100644 index 73001741cd..0000000000 --- a/packages/std-client/src/setup/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NetworkConfig, SyncWorkerConfig, NetworkComponentUpdate, SystemCall } from "@latticexyz/network"; -import { Schema, Components, Type, Component, Entity } from "@latticexyz/recs"; -import { Contract } from "ethers"; - -export type SetupContractConfig = NetworkConfig & - Omit & { - worldAddress: string; - /** - * @deprecated: use `disableCache` to disable the cache, use `fastTxExecutor` to send tx with 0 gas on dev chains - */ - devMode?: boolean; - disableCache?: boolean; - }; - -export type DecodedNetworkComponentUpdate = Omit, "component"> & { - entity: Entity; - component: Component; -}; - -export type DecodedSystemCall< - T extends { [key: string]: Contract } = { [key: string]: Contract }, - C extends Components = Components -> = Omit, "updates"> & { - systemId: keyof T; - args: Record; - updates: DecodedNetworkComponentUpdate[]; -}; - -export type ContractComponent = Component; - -export type ContractComponents = { - [key: string]: ContractComponent; -}; - -export type NetworkComponents = C & { - SystemsRegistry: Component<{ value: Type.String }>; - ComponentsRegistry: Component<{ value: Type.String }>; - LoadingState: Component<{ - state: Type.Number; - msg: Type.String; - percentage: Type.Number; - }>; -}; diff --git a/packages/std-client/src/setup/utils.ts b/packages/std-client/src/setup/utils.ts deleted file mode 100644 index b9ac4d5289..0000000000 --- a/packages/std-client/src/setup/utils.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { - Ack, - ack, - createEncoder, - isNetworkComponentUpdateEvent, - isSystemCallEvent, - Mappings, - NetworkComponentUpdate, - NetworkEvent, - SystemCall, -} from "@latticexyz/network"; -import { - Components, - World, - Schema, - Type, - getComponentValue, - removeComponent, - setComponent, - getComponentEntities, - getComponentValueStrict, - Component, - updateComponent, - Entity, -} from "@latticexyz/recs"; -import { isDefined } from "@latticexyz/common/utils"; -import { toEthAddress } from "@latticexyz/utils"; -import { Component as SolecsComponent } from "@latticexyz/solecs"; -import ComponentAbi from "@latticexyz/solecs/abi/Component.sol/Component.json"; -import { Contract, BigNumber, Signer } from "ethers"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import { IComputedValue } from "mobx"; -import { filter, map, Observable, Subject, timer } from "rxjs"; -import { DecodedNetworkComponentUpdate, DecodedSystemCall } from "./types"; -import { StoreConfig } from "@latticexyz/store"; -import { createDatabaseClient } from "@latticexyz/store-cache"; - -export function createDecodeNetworkComponentUpdate( - world: World, - components: C, - mappings: Mappings -): (update: NetworkComponentUpdate) => DecodedNetworkComponentUpdate | undefined { - return (update: NetworkComponentUpdate) => { - const entity = update.entity ?? world.registerEntity({ id: update.entity }); - const componentKey = mappings[update.component]; - const component = components[componentKey] as Component; - - if (!componentKey) { - console.error(`Component mapping not found for component ID ${update.component} ${JSON.stringify(update.value)}`); - return undefined; - } - - return { - ...update, - entity, - component, - }; - }; -} - -export function createSystemCallStreams( - world: World, - systemNames: string[], - systemsRegistry: Component<{ value: Type.String }>, - getSystemContract: (systemId: string) => { name: string; contract: Contract }, - decodeNetworkComponentUpdate: ReturnType -) { - const systemCallStreams = systemNames.reduce( - (streams, systemId) => ({ ...streams, [systemId]: new Subject>() }), - {} as Record>> - ); - - return { - systemCallStreams, - decodeAndEmitSystemCall: (systemCall: SystemCall) => { - const { tx } = systemCall; - - const systemEntity = BigNumber.from(tx.to).toHexString().toLowerCase() as Entity; - if (!systemEntity) return; - - const hashedSystemId = getComponentValue(systemsRegistry, systemEntity)?.value; - if (!hashedSystemId) return; - - const { name, contract } = getSystemContract(hashedSystemId); - - const decodedTx = contract.interface.parseTransaction({ data: tx.data, value: tx.value }); - - // If this is a newly registered System make a new Subject - if (!systemCallStreams[name]) { - systemCallStreams[name] = new Subject>(); - } - - systemCallStreams[name].next({ - ...systemCall, - updates: systemCall.updates.map(decodeNetworkComponentUpdate).filter(isDefined), - systemId: name, - args: decodedTx.args, - }); - }, - }; -} - -export async function createEncoders( - world: World, - components: Component<{ value: Type.String }>, - signerOrProvider: IComputedValue -) { - const encoders = {} as Record>; - - async function fetchAndCreateEncoder(entity: Entity) { - const componentAddress = toEthAddress(entity); - const componentId = getComponentValueStrict(components, entity).value; - console.info("[SyncUtils] Creating encoder for " + componentAddress); - const componentContract = new Contract( - componentAddress, - ComponentAbi.abi, - signerOrProvider.get() - ) as SolecsComponent; - const [componentSchemaPropNames, componentSchemaTypes] = await componentContract.getSchema(); - encoders[componentId] = createEncoder(componentSchemaPropNames, componentSchemaTypes); - } - - // Initial setup - for (const entity of getComponentEntities(components)) fetchAndCreateEncoder(entity); - - // Keep up to date - const subscription = components.update$.subscribe((update) => fetchAndCreateEncoder(update.entity)); - world.registerDisposer(() => subscription?.unsubscribe()); - - return encoders; -} - -/** - * Sets up synchronization between contract components and client components - */ -export function applyNetworkUpdates( - world: World, - components: C, - ecsEvents$: Observable[]>, - mappings: Mappings, - ack$: Subject, - storeConfig?: S, - storeCache?: ReturnType>, - decodeAndEmitSystemCall?: (event: SystemCall) => void -) { - const txReduced$ = new Subject(); - - // Send "ack" to tell the sync worker we're ready to receive events while not processing - let processing = false; - const ackSub = timer(0, 100) - .pipe( - filter(() => !processing), - map(() => ack) - ) - .subscribe(ack$); - - const delayQueueSub = ecsEvents$.subscribe((updates) => { - processing = true; - for (const update of updates) { - if (isNetworkComponentUpdateEvent(update)) { - if (update.lastEventInTx) txReduced$.next(update.txHash); - - if (storeCache && storeConfig) { - // Apply network updates to store cache - const { namespace, table, key } = update; - - const tableConfig = storeConfig.tables[table]; - - // Apply network updates to cache store - if (!tableConfig || namespace !== storeConfig.namespace) { - // console.warn("Ignoring table config outside own mud config", update, storeConfig.namespace); - } else { - // TODO: key names are not yet part of the on-chain table metadata, so we have to - // load them from the local mud.config (and have to ignore all tables that are not - // defined in the local mud config) - // (see https://github.com/latticexyz/mud/issues/824) - - // `Object.getOwnPropertyNames` guarantees key order, `Object.keys` does not - const namedKey = nameKeys(key, Object.getOwnPropertyNames(tableConfig.keySchema)); - - // StoreCache handles setting partial value and initializing with default values - const value = update.value ?? update.partialValue; - if (value) { - const namedValue = nameKeys(value, Object.getOwnPropertyNames(tableConfig.schema)); - storeCache.set(namespace, table, namedKey as any, namedValue as any); - } else { - storeCache.remove(namespace, table, namedKey as any); - } - } - } - - const entity = update.entity ?? world.registerEntity({ id: update.entity }); - const componentKey = mappings[update.component]; - if (!componentKey) { - console.warn("Unknown component:", update); - continue; - } - const component = components[componentKey] as Component; - - // keep this logic aligned with CacheStore's storeEvent - if (update.partialValue !== undefined) { - updateComponent(component, entity, update.partialValue, update.initialValue); - } else if (update.value === undefined) { - // undefined value means component removed - removeComponent(component, entity); - } else { - setComponent(component, entity, update.value); - } - } else if (decodeAndEmitSystemCall && isSystemCallEvent(update)) { - decodeAndEmitSystemCall(update); - } - } - // Send "ack" after every processed batch of events to process faster than ever 100ms - ack$.next(ack); - processing = false; - }); - - world.registerDisposer(() => { - delayQueueSub?.unsubscribe(); - ackSub?.unsubscribe(); - }); - return { txReduced$: txReduced$.asObservable() }; -} - -function nameKeys(indexedRecord: Record, keyNames: string[]) { - const namedRecord: Record = {}; - keyNames.forEach((key, index) => { - if (index in indexedRecord) { - namedRecord[key] = indexedRecord[index]; - } - }); - return namedRecord; -} diff --git a/packages/std-client/src/utils.ts b/packages/std-client/src/utils.ts deleted file mode 100644 index d5db14f0e3..0000000000 --- a/packages/std-client/src/utils.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { - Component, - getComponentValue, - getComponentValueStrict, - Has, - hasComponent, - HasValue, - runQuery, - Type, - Schema, - ComponentValue, - componentValueEquals, - Metadata, - Entity, -} from "@latticexyz/recs"; -import { Coord, keccak256 } from "@latticexyz/utils"; -import { BigNumber } from "ethers"; -import { Clock, SingletonID } from "@latticexyz/network"; -import { deferred } from "@latticexyz/utils"; -import { filter } from "rxjs"; - -export function getCurrentTurn( - gameConfigComponent: Component<{ - startTime: Type.String; - turnLength: Type.String; - actionCooldownLength: Type.String; - }>, - clock: Clock -) { - return getTurnAtTime(gameConfigComponent, clock.currentTime / 1000); -} - -export function getTurnAtTime( - gameConfigComponent: Component<{ - startTime: Type.String; - turnLength: Type.String; - actionCooldownLength: Type.String; - }>, - time: number -) { - const gameConfig = getGameConfig(gameConfigComponent); - if (!gameConfig) return -1; - - const startTime = BigNumber.from(gameConfig.startTime); - const turnLength = BigNumber.from(gameConfig.turnLength); - - return BigNumber.from(Math.floor(time)).sub(startTime).div(turnLength).toNumber(); -} - -export function getGameConfig( - gameConfigComponent: Component<{ startTime: Type.String; turnLength: Type.String; actionCooldownLength: Type.String }> -) { - return getComponentValue(gameConfigComponent, SingletonID); -} - -export function isUntraversable( - untraversableComponent: Component<{ value: Type.Boolean }>, - positionComponent: Component<{ x: Type.Number; y: Type.Number }>, - targetPosition: Coord -): boolean { - const untraversableEntitiesAtPosition = runQuery([ - HasValue(positionComponent, targetPosition), - Has(untraversableComponent), - ]); - return untraversableEntitiesAtPosition.size > 0; -} - -export function getPlayerEntity( - address: string | undefined, - Player: Component<{ value: Type.Boolean }> -): Entity | undefined { - if (!address) return; - - const playerEntity = address as Entity; - if (playerEntity == null || !hasComponent(Player, playerEntity)) return; - - return playerEntity; -} - -/** - * Starting with the given entity, traverse the relationship chain until the end is reached. - * @param relationshipComponent The component that will be used to traverse the relationship chain. - * @param entity Starting entity - * @returns The last entity in the relationship chain or undefined if the relationship chain is broken. - */ -export function resolveRelationshipChain( - relationshipComponent: Component<{ value: Type.Entity }>, - entity: Entity -): Entity | undefined { - while (hasComponent(relationshipComponent, entity)) { - const entityValue = getComponentValueStrict(relationshipComponent, entity).value; - if (!entityValue) return; - entity = entityValue; - } - return entity; -} - -/** - * Starting with the given entity, traverse the relationship chain until you find an entity that has the given component. - * @param relationshipComponent The component that will be used to traverse the relationship chain. - * @param entity Starting entity - * @param searchComponent The component to search for. - * @returns The first entity in the relationship chain that has the given component or undefined if the relationship chain is broken. - */ -export function findEntityWithComponentInRelationshipChain( - relationshipComponent: Component<{ value: Type.Entity }>, - entity: Entity, - searchComponent: Component -): Entity | undefined { - if (hasComponent(searchComponent, entity)) return entity; - - while (hasComponent(relationshipComponent, entity)) { - const entityValue = getComponentValueStrict(relationshipComponent, entity).value; - if (entityValue == null) return; - entity = entityValue; - - if (hasComponent(searchComponent, entity)) return entity; - } - - return; -} - -/** - * Find a specific entity in a relationship chain. - * @param entity Starting entity - * @param searchEntity Entity to search for - * @param relationshipComponent The component that will be used to traverse the relationship chain. - * @returns True if the entity is found in the relationship chain, false otherwise. - */ -export function findInRelationshipChain( - relationshipComponent: Component<{ value: Type.Entity }>, - entity: Entity, - searchEntity: Entity -): boolean { - if (entity === searchEntity) return true; - - while (hasComponent(relationshipComponent, entity)) { - const entityValue = getComponentValueStrict(relationshipComponent, entity).value; - if (entityValue == null) return false; - entity = entityValue; - - if (entity === searchEntity) return true; - } - - return false; -} - -/** - * Generate a random color based on the given id. - * @param id Any string - * @returns A color in the range 0x000000 - 0xFFFFFF - */ -export function randomColor(id: string): number { - const randSeed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values - function seedRand(seed: string) { - for (let i = 0; i < randSeed.length; i++) { - randSeed[i] = 0; - } - for (let i = 0; i < seed.length; i++) { - randSeed[i % 4] = (randSeed[i % 4] << 5) - randSeed[i % 4] + seed.charCodeAt(i); - } - } - - function rand() { - const t = randSeed[0] ^ (randSeed[0] << 11); - randSeed[0] = randSeed[1]; - randSeed[1] = randSeed[2]; - randSeed[2] = randSeed[3]; - randSeed[3] = randSeed[3] ^ (randSeed[3] >> 19) ^ t ^ (t >> 8); - return (randSeed[3] >>> 0) / ((1 << 31) >>> 0); - } - - function createColor(): [number, number, number] { - // hue is the whole color spectrum - const h = Math.floor(rand() * 360) / 360; - //saturation goes from 40 to 100, it avoids greyish colors - // --> Multiply by 0.75 to limit saturation - // const s = ((rand() * 60 + 40) / 100) * 0.75; - const s = 80 / 100; - // lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% - // --> Multiply by 0.65 to shift - // const l = (((rand() + rand() + rand() + rand()) * 25) / 100) * 0.65; - const l = 70 / 100; - return [h, s, l]; - } - - function RgbToHex(red: number, green: number, blue: number): number { - return (red << 16) | (green << 8) | blue; - } - - function hue2rgb(p: number, q: number, t: number) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - } - - function HSLToRGB(h: number, s: number, l: number): [number, number, number] { - let r: number; - let g: number; - let b: number; - - if (s === 0) { - r = g = b = l; // achromatic - } else { - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return [r * 255, g * 255, b * 255]; - } - - seedRand(id); - - return RgbToHex(...HSLToRGB(...createColor())); -} - -export function getStringColor(address: string) { - return randomColor(keccak256(address).substring(2)); -} - -export function waitForComponentValueIn( - component: Component, - entity: Entity, - values: Partial>[] -): Promise { - const [resolve, , promise] = deferred(); - - let dispose = resolve; - const subscription = component.update$ - .pipe( - filter((e) => e.entity === entity && Boolean(values.find((value) => componentValueEquals(value, e.value[0])))) - ) - .subscribe(() => { - resolve(); - dispose(); - }); - - dispose = () => subscription?.unsubscribe(); - - return promise; -} - -export async function waitForComponentValue( - component: Component, - entity: Entity, - value: Partial> -): Promise { - await waitForComponentValueIn(component, entity, [value]); -} diff --git a/packages/std-client/tsup.config.ts b/packages/std-client/tsup.config.ts index fb45c3937c..d6fdc6ab7f 100644 --- a/packages/std-client/tsup.config.ts +++ b/packages/std-client/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/index.ts", "src/dev/index.ts"], + entry: { deprecated: "src/deprecated/index.ts" }, target: "esnext", format: ["esm"], dts: false, diff --git a/packages/world/abi/SnapSyncModule.sol/SnapSyncModule.abi.json b/packages/world/abi/SnapSyncModule.sol/SnapSyncModule.abi.json deleted file mode 100644 index 32a46a1787..0000000000 --- a/packages/world/abi/SnapSyncModule.sol/SnapSyncModule.abi.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "resourceSelector", - "type": "string" - } - ], - "name": "RequiredModuleNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "getName", - "outputs": [ - { - "internalType": "bytes16", - "name": "", - "type": "bytes16" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "install", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/world/abi/SnapSyncSystem.sol/SnapSyncSystem.abi.json b/packages/world/abi/SnapSyncSystem.sol/SnapSyncSystem.abi.json deleted file mode 100644 index fe0862a126..0000000000 --- a/packages/world/abi/SnapSyncSystem.sol/SnapSyncSystem.abi.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "uint256", - "name": "length", - "type": "uint256" - } - ], - "name": "SchemaLib_InvalidLength", - "type": "error" - }, - { - "inputs": [], - "name": "SchemaLib_StaticTypeAfterDynamicType", - "type": "error" - }, - { - "inputs": [], - "name": "StoreCore_NotDynamicField", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "tableId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "tableIdString", - "type": "string" - } - ], - "name": "StoreCore_TableNotFound", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "tableId", - "type": "bytes32" - } - ], - "name": "getNumKeysInTable", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "tableId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "offset", - "type": "uint256" - } - ], - "name": "getRecords", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "tableId", - "type": "bytes32" - }, - { - "internalType": "bytes32[]", - "name": "keyTuple", - "type": "bytes32[]" - }, - { - "internalType": "bytes", - "name": "value", - "type": "bytes" - } - ], - "internalType": "struct SyncRecord[]", - "name": "records", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index a9bbbee3bc..a985bb4c6c 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -191,18 +191,6 @@ "name": "NotValueQuery", "gasUsed": 68780 }, - { - "file": "test/SnapSyncModule.t.sol", - "test": "testSnapSyncGas", - "name": "Call snap sync on a table with 1 record", - "gasUsed": 38930 - }, - { - "file": "test/SnapSyncModule.t.sol", - "test": "testSnapSyncGas", - "name": "Call snap sync on a table with 2 records", - "gasUsed": 56007 - }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index 0402df962f..f000ace062 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -162,8 +162,6 @@ export default mudConfig({ // (see https://github.com/latticexyz/mud/pull/584) "UniqueEntitySystem", - "SnapSyncSystem", - // Worldgen currently does not support systems inheriting logic // from other contracts, so all parts of CoreSystem are named // System too to be included in the IBaseWorld interface. diff --git a/packages/world/package.json b/packages/world/package.json index 92c0abd4ee..070b6d8f4f 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -15,7 +15,6 @@ "./node": "./dist/ts/node/index.js", "./abi/*": "./abi/*", "./types/*": "./types/*", - "./snapsync": "./dist/ts/plugins/snapsync/index.js", "./*": "./dist/*" }, "typesVersions": { @@ -28,9 +27,6 @@ ], "node": [ "./ts/node/index.ts" - ], - "snapsync": [ - "./ts/plugins/snapsync/index.ts" ] } }, diff --git a/packages/world/src/modules/snapsync/SnapSyncModule.sol b/packages/world/src/modules/snapsync/SnapSyncModule.sol deleted file mode 100644 index b35ef982c8..0000000000 --- a/packages/world/src/modules/snapsync/SnapSyncModule.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import { IBaseWorld } from "../../interfaces/IBaseWorld.sol"; -import { IModule } from "../../interfaces/IModule.sol"; - -import { WorldContext } from "../../WorldContext.sol"; - -import { SnapSyncSystem } from "./SnapSyncSystem.sol"; - -import { NAMESPACE, MODULE_NAME, SYSTEM_NAME, TABLE_NAME } from "./constants.sol"; - -/** - * NOTICE: Requires all tables in the world to use the KeysInTable module. - * This module registers a system that allows clients to load a snapshot of the World state - * by using view functions. - */ -contract SnapSyncModule is IModule, WorldContext { - // Since the SnapSyncSystem only exists once per World and writes to - // known tables, we can deploy it once and register it in multiple Worlds. - SnapSyncSystem private immutable snapSyncSystem = new SnapSyncSystem(); - - function getName() public pure returns (bytes16) { - return MODULE_NAME; - } - - function install(bytes memory) public { - IBaseWorld world = IBaseWorld(_world()); - - // Register system - world.registerSystem(NAMESPACE, SYSTEM_NAME, snapSyncSystem, true); - // Register system's functions - world.registerFunctionSelector(NAMESPACE, SYSTEM_NAME, "getRecords", "(bytes32,uint256,uint256)"); - world.registerFunctionSelector(NAMESPACE, SYSTEM_NAME, "getNumKeysInTable", "(bytes32)"); - } -} diff --git a/packages/world/src/modules/snapsync/SnapSyncSystem.sol b/packages/world/src/modules/snapsync/SnapSyncSystem.sol deleted file mode 100644 index 4657e4060e..0000000000 --- a/packages/world/src/modules/snapsync/SnapSyncSystem.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; -import { System } from "../../System.sol"; -import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { Schema } from "@latticexyz/store/src/Schema.sol"; - -import { getKeysInTable } from "../keysintable/getKeysInTable.sol"; -import { KeysInTable, KeysInTableTableId } from "../keysintable/tables/KeysInTable.sol"; -import { SyncRecord } from "./SyncRecord.sol"; - -function keyToTuple(bytes32 key) pure returns (bytes32[] memory keyTuple) {} - -contract SnapSyncSystem is System { - function getRecords( - bytes32 tableId, - uint256 limit, - uint256 offset - ) public view virtual returns (SyncRecord[] memory records) { - records = new SyncRecord[](limit); - - Schema schema = StoreSwitch.getKeySchema(tableId); - uint256 numFields = schema.numFields(); - - for (uint256 i = offset; i < limit + offset; i++) { - bytes32[] memory keyTuple = new bytes32[](numFields); - - if (numFields > 0) { - keyTuple[0] = KeysInTable.getItemKeys0(tableId, i); - if (numFields > 1) { - keyTuple[1] = KeysInTable.getItemKeys1(tableId, i); - if (numFields > 2) { - keyTuple[2] = KeysInTable.getItemKeys2(tableId, i); - if (numFields > 3) { - keyTuple[3] = KeysInTable.getItemKeys3(tableId, i); - if (numFields > 4) { - keyTuple[4] = KeysInTable.getItemKeys4(tableId, i); - } - } - } - } - } - - bytes memory value = StoreSwitch.getRecord(tableId, keyTuple); - records[i] = SyncRecord({ tableId: tableId, keyTuple: keyTuple, value: value }); - } - } - - function getNumKeysInTable(bytes32 tableId) public view virtual returns (uint256) { - return KeysInTable.lengthKeys0(tableId); - } -} diff --git a/packages/world/src/modules/snapsync/SyncRecord.sol b/packages/world/src/modules/snapsync/SyncRecord.sol deleted file mode 100644 index 7af8963611..0000000000 --- a/packages/world/src/modules/snapsync/SyncRecord.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -struct SyncRecord { - bytes32 tableId; - bytes32[] keyTuple; - bytes value; -} diff --git a/packages/world/src/modules/snapsync/constants.sol b/packages/world/src/modules/snapsync/constants.sol deleted file mode 100644 index ef743f0152..0000000000 --- a/packages/world/src/modules/snapsync/constants.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -bytes16 constant NAMESPACE = bytes16("snapSync"); -bytes16 constant MODULE_NAME = bytes16("snapSync.m"); -bytes16 constant SYSTEM_NAME = bytes16("system"); -bytes16 constant TABLE_NAME = bytes16("table"); diff --git a/packages/world/test/SnapSyncModule.t.sol b/packages/world/test/SnapSyncModule.t.sol deleted file mode 100644 index 4af7739520..0000000000 --- a/packages/world/test/SnapSyncModule.t.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import { Test } from "forge-std/Test.sol"; -import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; - -import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; -import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; -import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; - -import { World } from "../src/World.sol"; -import { IBaseWorld } from "../src/interfaces/IBaseWorld.sol"; -import { ResourceSelector } from "../src/ResourceSelector.sol"; -import { ROOT_NAMESPACE } from "../src/constants.sol"; - -import { CoreModule } from "../src/modules/core/CoreModule.sol"; -import { KeysInTableModule } from "../src/modules/keysintable/KeysInTableModule.sol"; -import { SnapSyncModule } from "../src/modules/snapsync/SnapSyncModule.sol"; -import { SyncRecord } from "../src/modules/snapsync/SyncRecord.sol"; -import { getKeysInTable } from "../src/modules/keysintable/getKeysInTable.sol"; -import { hasKey } from "../src/modules/keysintable/hasKey.sol"; - -interface ISnapSyncSystem { - function snapSync_system_getRecords( - bytes32 tableId, - uint256 limit, - uint256 offset - ) external view returns (SyncRecord[] memory records); - - function snapSync_system_getNumKeysInTable(bytes32 tableId) external view returns (uint256); -} - -contract SnapSyncModuleTest is Test, GasReporter { - using ResourceSelector for bytes32; - IBaseWorld world; - KeysInTableModule keysInTableModule = new KeysInTableModule(); // Modules can be deployed once and installed multiple times - SnapSyncModule snapSyncModule = new SnapSyncModule(); // Modules can be deployed once and installed multiple times - - bytes16 namespace = ROOT_NAMESPACE; - bytes16 name = bytes16("source"); - bytes16 singletonName = bytes16("singleton"); - bytes16 compositeName = bytes16("composite"); - bytes32 key1 = keccak256("test"); - bytes32[] keyTuple1; - bytes32 key2 = keccak256("test2"); - bytes32[] keyTuple2; - - Schema tableSchema; - Schema tableKeySchema; - Schema singletonKeySchema; - Schema compositeKeySchema; - bytes32 tableId; - bytes32 singletonTableId; - bytes32 compositeTableId; - - uint256 val1 = 123; - uint256 val2 = 42; - - function setUp() public { - tableSchema = SchemaEncodeHelper.encode(SchemaType.UINT256); - tableKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32); - compositeKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32, SchemaType.BYTES32, SchemaType.BYTES32); - - SchemaType[] memory _schema = new SchemaType[](0); - singletonKeySchema = SchemaLib.encode(_schema); - - world = IBaseWorld(address(new World())); - world.installRootModule(new CoreModule(), new bytes(0)); - keyTuple1 = new bytes32[](1); - keyTuple1[0] = key1; - keyTuple2 = new bytes32[](1); - keyTuple2[0] = key2; - } - - function _installModules() internal { - // Register source table - tableId = world.registerTable(namespace, name, tableSchema, tableKeySchema); - singletonTableId = world.registerTable(namespace, singletonName, tableSchema, singletonKeySchema); - compositeTableId = world.registerTable(namespace, compositeName, tableSchema, compositeKeySchema); - - world.installRootModule(keysInTableModule, abi.encode(tableId)); - world.installRootModule(keysInTableModule, abi.encode(singletonTableId)); - world.installRootModule(keysInTableModule, abi.encode(compositeTableId)); - world.installRootModule(snapSyncModule, ""); - } - - function testSnapSync(uint256 value1, uint256 value2) public { - _installModules(); - - // Set a value in the source table - world.setRecord(namespace, name, keyTuple1, abi.encodePacked(value1)); - - uint256 limit = ISnapSyncSystem(address(world)).snapSync_system_getNumKeysInTable(tableId); - - startGasReport("Call snap sync on a table with 1 record"); - SyncRecord[] memory records = ISnapSyncSystem(address(world)).snapSync_system_getRecords(tableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 1); - assertEq(records[0].keyTuple[0], key1); - assertEq(records[0].value, abi.encodePacked(value1)); - - // Set another key with a different value - world.setRecord(namespace, name, keyTuple2, abi.encodePacked(value2)); - - limit = ISnapSyncSystem(address(world)).snapSync_system_getNumKeysInTable(tableId); - - startGasReport("Call snap sync on a table with 2 records"); - records = ISnapSyncSystem(address(world)).snapSync_system_getRecords(tableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 2); - assertEq(records[0].keyTuple[0], key1); - assertEq(records[0].value, abi.encodePacked(value1)); - assertEq(records[1].keyTuple[0], key2); - assertEq(records[1].value, abi.encodePacked(value2)); - } - - function testSnapSyncGas() public { - testSnapSync(val1, val2); - } - - function testSnapSyncComposite(uint256 value1, uint256 value2) public { - _installModules(); - - bytes32[] memory keyTupleA = new bytes32[](3); - keyTupleA[0] = "A1"; - keyTupleA[1] = "A2"; - keyTupleA[2] = "A3"; - bytes32[] memory keyTupleB = new bytes32[](3); - keyTupleB[0] = "B1"; - keyTupleB[1] = "B2"; - keyTupleB[2] = "B3"; - - ISnapSyncSystem syncSystem = ISnapSyncSystem(address(world)); - - // Set a value in the source table - world.setRecord(namespace, compositeName, keyTupleA, abi.encodePacked(value1)); - - uint256 limit = syncSystem.snapSync_system_getNumKeysInTable(compositeTableId); - - startGasReport("Call snap sync on a table with 1 record"); - SyncRecord[] memory records = syncSystem.snapSync_system_getRecords(compositeTableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 1); - for (uint256 i; i < 2; i++) { - assertEq(records[0].keyTuple[i], keyTupleA[i]); - } - assertEq(records[0].value, abi.encodePacked(value1)); - - // Set another key with a different value - world.setRecord(namespace, compositeName, keyTupleB, abi.encodePacked(value2)); - - limit = syncSystem.snapSync_system_getNumKeysInTable(compositeTableId); - - startGasReport("Call snap sync on a table with 2 records"); - records = syncSystem.snapSync_system_getRecords(compositeTableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 2); - for (uint256 i; i < 3; i++) { - assertEq(records[0].keyTuple[i], keyTupleA[i]); - assertEq(records[1].keyTuple[i], keyTupleB[i]); - } - assertEq(records[0].value, abi.encodePacked(value1)); - assertEq(records[1].value, abi.encodePacked(value2)); - } - - function testSnapSyncCompositeSameKey(uint256 value1, uint256 value2) public { - _installModules(); - - bytes32[] memory keyTupleA = new bytes32[](3); - keyTupleA[0] = "KEY"; - keyTupleA[1] = "A2"; - keyTupleA[2] = "A3"; - bytes32[] memory keyTupleB = new bytes32[](3); - keyTupleB[0] = "KEY"; - keyTupleB[1] = "B2"; - keyTupleB[2] = "B3"; - - // Set a value in the source table - world.setRecord(namespace, compositeName, keyTupleA, abi.encodePacked(value1)); - - ISnapSyncSystem syncSystem = ISnapSyncSystem(address(world)); - - uint256 limit = syncSystem.snapSync_system_getNumKeysInTable(compositeTableId); - - startGasReport("Call snap sync on a table with 1 record"); - SyncRecord[] memory records = syncSystem.snapSync_system_getRecords(compositeTableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 1); - for (uint256 i; i < 2; i++) { - assertEq(records[0].keyTuple[i], keyTupleA[i]); - } - assertEq(records[0].value, abi.encodePacked(value1)); - - // Set another key with a different value - world.setRecord(namespace, compositeName, keyTupleB, abi.encodePacked(value2)); - - limit = syncSystem.snapSync_system_getNumKeysInTable(compositeTableId); - - startGasReport("Call snap sync on a table with 2 records"); - records = syncSystem.snapSync_system_getRecords(compositeTableId, limit, 0); - endGasReport(); - - // Assert that the list is correct - assertEq(records.length, 2); - for (uint256 i; i < 3; i++) { - assertEq(records[0].keyTuple[i], keyTupleA[i]); - assertEq(records[1].keyTuple[i], keyTupleB[i]); - } - assertEq(records[0].value, abi.encodePacked(value1)); - assertEq(records[1].value, abi.encodePacked(value2)); - } -} diff --git a/packages/world/ts/plugins/snapsync/configExtensions.ts b/packages/world/ts/plugins/snapsync/configExtensions.ts deleted file mode 100644 index 58234eac3b..0000000000 --- a/packages/world/ts/plugins/snapsync/configExtensions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { extendMUDCoreConfig, resolveTableId } from "@latticexyz/config"; -import { zPluginStoreConfig } from "@latticexyz/store"; -import { zSnapSyncPluginConfig } from "./plugin"; -import { zPluginWorldConfig } from "../../library"; - -extendMUDCoreConfig((config) => { - const modifiedConfig = { ...config } as Record; - const snapSyncConfig = zSnapSyncPluginConfig.parse(config); - - if (snapSyncConfig.snapSync) { - const worldConfig = zPluginWorldConfig.parse(config); - const storeConfig = zPluginStoreConfig.parse(config); - const tableNames = Object.entries(storeConfig.tables) - .filter(([_, t]) => !t.ephemeral) - .map(([name, _]) => name); - const newModules = tableNames.map((tableName) => { - return { - name: "KeysInTableModule", - root: true, - args: [resolveTableId(tableName)], - }; - }); - - modifiedConfig.modules = [ - ...worldConfig.modules, - { - name: "SnapSyncModule", - root: true, - args: [], - }, - ...newModules, - ]; - } - - return modifiedConfig; -}); diff --git a/packages/world/ts/plugins/snapsync/index.ts b/packages/world/ts/plugins/snapsync/index.ts deleted file mode 100644 index 74d508cd4f..0000000000 --- a/packages/world/ts/plugins/snapsync/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Importing this file automatically adds the required MUD modules to make - * snap sync work. - */ - -import "./configExtensions"; -import "./typeExtensions"; diff --git a/packages/world/ts/plugins/snapsync/plugin.ts b/packages/world/ts/plugins/snapsync/plugin.ts deleted file mode 100644 index d0131d1517..0000000000 --- a/packages/world/ts/plugins/snapsync/plugin.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from "zod"; - -export const zSnapSyncPluginConfig = z - .object({ - snapSync: z.boolean(), - }) - .catchall(z.any()); diff --git a/packages/world/ts/plugins/snapsync/typeExtensions.ts b/packages/world/ts/plugins/snapsync/typeExtensions.ts deleted file mode 100644 index 94dfc4d5ae..0000000000 --- a/packages/world/ts/plugins/snapsync/typeExtensions.ts +++ /dev/null @@ -1,12 +0,0 @@ -import "@latticexyz/store/register"; - -interface SnapSyncConfig { - snapSync: boolean; -} - -declare module "@latticexyz/config" { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface MUDCoreUserConfig extends SnapSyncConfig {} - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface MUDCoreConfig extends SnapSyncConfig {} -} diff --git a/packages/world/tsup.config.ts b/packages/world/tsup.config.ts index ca5e11ac7f..8217d6fa02 100644 --- a/packages/world/tsup.config.ts +++ b/packages/world/tsup.config.ts @@ -1,13 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: [ - "mud.config.ts", - "ts/library/index.ts", - "ts/register/index.ts", - "ts/node/index.ts", - "ts/plugins/snapsync/index.ts", - ], + entry: ["mud.config.ts", "ts/library/index.ts", "ts/register/index.ts", "ts/node/index.ts"], target: "esnext", format: ["esm"], dts: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b732f59097..da3f054842 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -86,7 +82,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/cli: dependencies: @@ -216,7 +212,7 @@ importers: version: 3.12.6 vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/common: dependencies: @@ -262,7 +258,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/config: dependencies: @@ -382,7 +378,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/ecs-browser: dependencies: @@ -462,98 +458,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 - - packages/network: - dependencies: - '@ethersproject/abi': - specifier: ^5.7.0 - version: 5.7.0 - '@ethersproject/providers': - specifier: ^5.7.2 - version: 5.7.2 - '@improbable-eng/grpc-web': - specifier: ^0.15.0 - version: 0.15.0(google-protobuf@3.21.2) - '@latticexyz/common': - specifier: workspace:* - version: link:../common - '@latticexyz/recs': - specifier: workspace:* - version: link:../recs - '@latticexyz/schema-type': - specifier: workspace:* - version: link:../schema-type - '@latticexyz/services': - specifier: workspace:* - version: link:../services - '@latticexyz/solecs': - specifier: workspace:* - version: link:../solecs - '@latticexyz/store': - specifier: workspace:* - version: link:../store - '@latticexyz/utils': - specifier: workspace:* - version: link:../utils - '@latticexyz/world': - specifier: workspace:* - version: link:../world - async-mutex: - specifier: ^0.3.2 - version: 0.3.2 - debug: - specifier: ^4.3.4 - version: 4.3.4(supports-color@8.1.1) - ethers: - specifier: ^5.7.2 - version: 5.7.2 - lodash: - specifier: ^4.17.21 - version: 4.17.21 - mobx: - specifier: ^6.7.0 - version: 6.9.0 - nice-grpc-web: - specifier: ^2.0.1 - version: 2.0.1(google-protobuf@3.21.2) - rxjs: - specifier: 7.5.5 - version: 7.5.5 - threads: - specifier: ^1.7.0 - version: 1.7.0 - viem: - specifier: 1.6.0 - version: 1.6.0(typescript@5.1.6)(zod@3.21.4) - devDependencies: - '@types/debug': - specifier: ^4.1.7 - version: 4.1.7 - '@types/jest': - specifier: ^27.4.1 - version: 27.4.1 - '@types/lodash': - specifier: ^4.14.182 - version: 4.14.182 - '@types/node': - specifier: ^18.15.11 - version: 18.15.11 - fake-indexeddb: - specifier: ^4.0.0 - version: 4.0.0 - jest: - specifier: ^29.3.1 - version: 29.5.0(@types/node@18.15.11) - jest-environment-jsdom: - specifier: ^29.3.1 - version: 29.3.1 - ts-jest: - specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(esbuild@0.17.17)(jest@29.5.0)(typescript@5.1.6) - tsup: - specifier: ^6.7.0 - version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) + version: 0.31.4(jsdom@22.1.0) packages/noise: dependencies: @@ -643,7 +548,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/react: dependencies: @@ -684,6 +589,9 @@ importers: eslint-plugin-react-hooks: specifier: 4.6.0 version: 4.6.0(eslint@8.29.0) + jsdom: + specifier: ^22.1.0 + version: 22.1.0 react-test-renderer: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -695,7 +603,7 @@ importers: version: 4.3.6(@types/node@18.15.11) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/recs: dependencies: @@ -754,7 +662,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/services: dependencies: @@ -764,6 +672,9 @@ importers: nice-grpc-common: specifier: ^2.0.2 version: 2.0.2 + nice-grpc-web: + specifier: ^2.0.1 + version: 2.0.1(google-protobuf@3.21.2) protobufjs: specifier: ^7.2.3 version: 7.2.3 @@ -848,9 +759,6 @@ importers: '@latticexyz/config': specifier: workspace:* version: link:../config - '@latticexyz/network': - specifier: workspace:* - version: link:../network '@latticexyz/recs': specifier: workspace:* version: link:../recs @@ -911,7 +819,7 @@ importers: version: 3.12.6 vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/std-contracts: devDependencies: @@ -1017,7 +925,7 @@ importers: version: 8.1.1(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/store-cache: dependencies: @@ -1045,7 +953,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/store-indexer: dependencies: @@ -1115,7 +1023,7 @@ importers: version: 3.12.6 vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/store-sync: dependencies: @@ -1188,7 +1096,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages/utils: dependencies: @@ -1295,7 +1203,7 @@ importers: version: 8.1.1(typescript@5.1.6) vitest: specifier: 0.31.4 - version: 0.31.4 + version: 0.31.4(jsdom@22.1.0) packages: @@ -3498,14 +3406,6 @@ packages: pretty-format: 27.5.1 dev: true - /@types/jsdom@20.0.1: - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} - dependencies: - '@types/node': 18.15.11 - '@types/tough-cookie': 4.0.2 - parse5: 7.1.2 - dev: true - /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -3651,10 +3551,6 @@ packages: resolution: {integrity: sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw==} dev: true - /@types/tough-cookie@4.0.2: - resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} - dev: true - /@types/uuid@8.3.4: resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} dev: true @@ -3967,13 +3863,6 @@ packages: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} dev: false - /acorn-globals@7.0.1: - resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} - dependencies: - acorn: 8.8.2 - acorn-walk: 8.2.0 - dev: true - /acorn-jsx@5.3.2(acorn@6.4.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -4296,12 +4185,6 @@ packages: async: 2.6.4 dev: true - /async-mutex@0.3.2: - resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==} - dependencies: - tslib: 2.5.0 - dev: false - /async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} dev: false @@ -4438,11 +4321,6 @@ packages: safe-buffer: 5.2.1 dev: true - /base64-arraybuffer-es6@0.7.0: - resolution: {integrity: sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==} - engines: {node: '>=6.0.0'} - dev: true - /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4675,6 +4553,7 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + dev: true /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} @@ -5121,19 +5000,11 @@ packages: hasBin: true dev: true - /cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - dev: true - - /cssom@0.5.0: - resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - dev: true - - /cssstyle@2.3.0: - resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} - engines: {node: '>=8'} + /cssstyle@3.0.0: + resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==} + engines: {node: '>=14'} dependencies: - cssom: 0.3.8 + rrweb-cssom: 0.6.0 dev: true /csstype@3.1.2: @@ -5168,13 +5039,13 @@ packages: type: 1.2.0 dev: false - /data-urls@3.0.2: - resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} - engines: {node: '>=12'} + /data-urls@4.0.0: + resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==} + engines: {node: '>=14'} dependencies: abab: 2.0.6 whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 + whatwg-url: 12.0.1 dev: true /dataloader@1.4.0: @@ -5352,12 +5223,6 @@ packages: esutils: 2.0.3 dev: true - /domexception@1.0.1: - resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==} - dependencies: - webidl-conversions: 4.0.2 - dev: true - /domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} @@ -5712,19 +5577,6 @@ packages: engines: {node: '>=10'} dev: true - /escodegen@2.0.0: - resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} - engines: {node: '>=6.0'} - hasBin: true - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionator: 0.8.3 - optionalDependencies: - source-map: 0.6.1 - dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.29.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -5907,12 +5759,6 @@ packages: - supports-color dev: true - /esm@3.2.25: - resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} - engines: {node: '>=6'} - dev: false - optional: true - /espree@5.0.1: resolution: {integrity: sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==} engines: {node: '>=6.0.0'} @@ -6209,12 +6055,6 @@ packages: iconv-lite: 0.4.24 tmp: 0.0.33 - /fake-indexeddb@4.0.0: - resolution: {integrity: sha512-oCfWSJ/qvQn1XPZ8SHX6kY3zr1t+bN7faZ/lltGY0SBGhFOPXnWf0+pbO/MOAgfMx6khC2gK3S/bvAgQpuQHDQ==} - dependencies: - realistic-structured-clone: 3.0.0 - dev: true - /fast-content-type-parse@1.0.0: resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} dev: false @@ -7281,11 +7121,6 @@ packages: symbol-observable: 1.2.0 dev: true - /is-observable@2.1.0: - resolution: {integrity: sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==} - engines: {node: '>=8'} - dev: false - /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -7594,29 +7429,6 @@ packages: pretty-format: 29.5.0 dev: true - /jest-environment-jsdom@29.3.1: - resolution: {integrity: sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - '@jest/environment': 29.5.0 - '@jest/fake-timers': 29.5.0 - '@jest/types': 29.5.0 - '@types/jsdom': 20.0.1 - '@types/node': 18.15.11 - jest-mock: 29.5.0 - jest-util: 29.5.0 - jsdom: 20.0.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - /jest-environment-node@29.5.0: resolution: {integrity: sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7954,9 +7766,9 @@ packages: argparse: 2.0.1 dev: true - /jsdom@20.0.3: - resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} - engines: {node: '>=14'} + /jsdom@22.1.0: + resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==} + engines: {node: '>=16'} peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: @@ -7964,29 +7776,26 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.8.2 - acorn-globals: 7.0.1 - cssom: 0.5.0 - cssstyle: 2.3.0 - data-urls: 3.0.2 + cssstyle: 3.0.0 + data-urls: 4.0.0 decimal.js: 10.4.3 domexception: 4.0.0 - escodegen: 2.0.0 form-data: 4.0.0 html-encoding-sniffer: 3.0.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.2 + nwsapi: 2.2.7 parse5: 7.1.2 + rrweb-cssom: 0.6.0 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.1.2 + tough-cookie: 4.1.3 w3c-xmlserializer: 4.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 + whatwg-url: 12.0.1 ws: 8.13.0 xml-name-validator: 4.0.0 transitivePeerDependencies: @@ -8919,8 +8728,8 @@ packages: strip-hex-prefix: 1.0.0 dev: true - /nwsapi@2.2.2: - resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + /nwsapi@2.2.7: + resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} dev: true /object-assign@4.1.1: @@ -9003,10 +8812,6 @@ packages: /obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} - /observable-fns@0.6.1: - resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==} - dev: false - /on-exit-leak-free@2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} dev: false @@ -9637,6 +9442,11 @@ packages: /punycode@2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} + dev: true + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} /pure-rand@6.0.1: resolution: {integrity: sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==} @@ -9843,14 +9653,6 @@ packages: engines: {node: '>= 12.13.0'} dev: false - /realistic-structured-clone@3.0.0: - resolution: {integrity: sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==} - dependencies: - domexception: 1.0.1 - typeson: 6.1.0 - typeson-registry: 1.0.0-alpha.39 - dev: true - /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -10032,6 +9834,10 @@ packages: fsevents: 2.3.2 dev: true + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: true + /run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -10788,19 +10594,6 @@ packages: real-require: 0.2.0 dev: false - /threads@1.7.0: - resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} - dependencies: - callsites: 3.1.0 - debug: 4.3.4(supports-color@8.1.1) - is-observable: 2.1.0 - observable-fns: 0.6.1 - optionalDependencies: - tiny-worker: 2.3.0 - transitivePeerDependencies: - - supports-color - dev: false - /throttle-debounce@5.0.0: resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==} engines: {node: '>=12.22'} @@ -10826,14 +10619,6 @@ packages: engines: {node: '>=12'} dev: false - /tiny-worker@2.3.0: - resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} - requiresBuild: true - dependencies: - esm: 3.2.25 - dev: false - optional: true - /tinybench@2.5.0: resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} dev: true @@ -10874,8 +10659,8 @@ packages: engines: {node: '>=0.6'} dev: true - /tough-cookie@4.1.2: - resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} engines: {node: '>=6'} dependencies: psl: 1.9.0 @@ -10890,21 +10675,14 @@ packages: /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: - punycode: 2.1.1 + punycode: 2.3.0 dev: true - /tr46@2.1.0: - resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} - engines: {node: '>=8'} - dependencies: - punycode: 2.1.1 - dev: true - - /tr46@3.0.0: - resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} - engines: {node: '>=12'} + /tr46@4.1.1: + resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} + engines: {node: '>=14'} dependencies: - punycode: 2.1.1 + punycode: 2.3.0 dev: true /tree-kill@1.2.2: @@ -11273,20 +11051,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typeson-registry@1.0.0-alpha.39: - resolution: {integrity: sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==} - engines: {node: '>=10.0.0'} - dependencies: - base64-arraybuffer-es6: 0.7.0 - typeson: 6.1.0 - whatwg-url: 8.7.0 - dev: true - - /typeson@6.1.0: - resolution: {integrity: sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==} - engines: {node: '>=0.1.14'} - dev: true - /typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -11356,7 +11120,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.1.1 + punycode: 2.3.0 /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} @@ -11505,7 +11269,7 @@ packages: fsevents: 2.3.2 dev: true - /vitest@0.31.4: + /vitest@0.31.4(jsdom@22.1.0): resolution: {integrity: sha512-GoV0VQPmWrUFOZSg3RpQAPN+LPmHg2/gxlMNJlyxJihkz6qReHDV6b0pPDcqFLNEPya4tWJ1pgwUNP9MLmUfvQ==} engines: {node: '>=v14.18.0'} hasBin: true @@ -11550,6 +11314,7 @@ packages: chai: 4.3.7 concordance: 5.0.4 debug: 4.3.4(supports-color@8.1.1) + jsdom: 22.1.0 local-pkg: 0.4.3 magic-string: 0.30.0 pathe: 1.1.0 @@ -11617,11 +11382,6 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true - /webidl-conversions@6.1.0: - resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} - engines: {node: '>=10.4'} - dev: true - /webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -11644,11 +11404,11 @@ packages: engines: {node: '>=12'} dev: true - /whatwg-url@11.0.0: - resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} - engines: {node: '>=12'} + /whatwg-url@12.0.1: + resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==} + engines: {node: '>=14'} dependencies: - tr46: 3.0.0 + tr46: 4.1.1 webidl-conversions: 7.0.0 dev: true @@ -11666,15 +11426,6 @@ packages: webidl-conversions: 4.0.2 dev: true - /whatwg-url@8.7.0: - resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} - engines: {node: '>=10'} - dependencies: - lodash: 4.17.21 - tr46: 2.1.0 - webidl-conversions: 6.1.0 - dev: true - /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -12069,3 +11820,7 @@ packages: resolution: {tarball: https://codeload.github.com/transmissions11/solmate/tar.gz/9cf1428245074e39090dceacb0c28b1f684f584c} name: solmate version: 6.5.0 + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/templates/phaser/packages/client/package.json b/templates/phaser/packages/client/package.json index a7cc7a229c..159bbb6d1e 100644 --- a/templates/phaser/packages/client/package.json +++ b/templates/phaser/packages/client/package.json @@ -14,12 +14,11 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/phaserx": "link:../../../../packages/phaserx", "@latticexyz/react": "link:../../../../packages/react", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-client": "link:../../../../packages/std-client", + "@latticexyz/services": "link:../../../../packages/services", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/templates/phaser/packages/client/src/mud/getNetworkConfig.ts b/templates/phaser/packages/client/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/templates/phaser/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/phaser/packages/client/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/templates/phaser/packages/client/src/mud/setupNetwork.ts b/templates/phaser/packages/client/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/templates/phaser/packages/client/src/mud/setupNetwork.ts +++ b/templates/phaser/packages/client/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/templates/react/packages/client/package.json b/templates/react/packages/client/package.json index 51136034e4..28b862d94a 100644 --- a/templates/react/packages/client/package.json +++ b/templates/react/packages/client/package.json @@ -14,11 +14,10 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/react": "link:../../../../packages/react", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-client": "link:../../../../packages/std-client", + "@latticexyz/services": "link:../../../../packages/services", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/templates/react/packages/client/src/mud/getNetworkConfig.ts b/templates/react/packages/client/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/templates/react/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/react/packages/client/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/templates/react/packages/client/src/mud/setupNetwork.ts b/templates/react/packages/client/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/templates/react/packages/client/src/mud/setupNetwork.ts +++ b/templates/react/packages/client/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/templates/threejs/packages/client/package.json b/templates/threejs/packages/client/package.json index 3c93834ce1..2cc3243792 100644 --- a/templates/threejs/packages/client/package.json +++ b/templates/threejs/packages/client/package.json @@ -14,11 +14,10 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/react": "link:../../../../packages/react", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-client": "link:../../../../packages/std-client", + "@latticexyz/services": "link:../../../../packages/services", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/templates/threejs/packages/client/src/mud/getNetworkConfig.ts b/templates/threejs/packages/client/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/templates/threejs/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/threejs/packages/client/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/templates/threejs/packages/client/src/mud/setupNetwork.ts b/templates/threejs/packages/client/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/templates/threejs/packages/client/src/mud/setupNetwork.ts +++ b/templates/threejs/packages/client/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world"; diff --git a/templates/vanilla/packages/client/package.json b/templates/vanilla/packages/client/package.json index ca849d3f48..bb0912bd52 100644 --- a/templates/vanilla/packages/client/package.json +++ b/templates/vanilla/packages/client/package.json @@ -14,10 +14,9 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/common": "link:../../../../packages/common", "@latticexyz/dev-tools": "link:../../../../packages/dev-tools", - "@latticexyz/network": "link:../../../../packages/network", "@latticexyz/recs": "link:../../../../packages/recs", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-client": "link:../../../../packages/std-client", + "@latticexyz/services": "link:../../../../packages/services", "@latticexyz/store-sync": "link:../../../../packages/store-sync", "@latticexyz/utils": "link:../../../../packages/utils", "@latticexyz/world": "link:../../../../packages/world", diff --git a/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts b/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts index 1366dc88b4..e6ed032bfa 100644 --- a/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts +++ b/templates/vanilla/packages/client/src/mud/getNetworkConfig.ts @@ -1,4 +1,4 @@ -import { getBurnerWallet } from "@latticexyz/std-client"; +import { getBurnerPrivateKey } from "@latticexyz/common"; import worldsJson from "contracts/worlds.json"; import { supportedChains } from "./supportedChains"; @@ -24,7 +24,7 @@ export async function getNetworkConfig() { : world?.blockNumber ?? 0n; return { - privateKey: getBurnerWallet().value, + privateKey: getBurnerPrivateKey(), chainId, chain, faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl, diff --git a/templates/vanilla/packages/client/src/mud/setupNetwork.ts b/templates/vanilla/packages/client/src/mud/setupNetwork.ts index d42c811ec9..7272b874db 100644 --- a/templates/vanilla/packages/client/src/mud/setupNetwork.ts +++ b/templates/vanilla/packages/client/src/mud/setupNetwork.ts @@ -1,5 +1,5 @@ import { createPublicClient, fallback, webSocket, http, createWalletClient, Hex, parseEther, ClientConfig } from "viem"; -import { createFaucetService } from "@latticexyz/network"; +import { createFaucetService } from "@latticexyz/services/faucet"; import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs"; import { getNetworkConfig } from "./getNetworkConfig"; import { world } from "./world";