From 4c1a2038e5eb864e4523274ecb97a608d55bc9a9 Mon Sep 17 00:00:00 2001 From: alvrs Date: Tue, 15 Aug 2023 16:33:46 +0200 Subject: [PATCH] remove network package --- .changeset/late-geese-guess.md | 7 + package.json | 2 +- packages/network/.gitignore | 4 - packages/network/.hg | 2 - packages/network/.npmignore | 7 - packages/network/CHANGELOG.md | 653 ------------------ 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/createFaucetService.ts | 14 - 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 | 20 - 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 -- 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 - .../SnapSyncModule.abi.json | 39 -- .../SnapSyncSystem.abi.json | 104 --- pnpm-lock.yaml | 493 +------------ 76 files changed, 9 insertions(+), 6752 deletions(-) create mode 100644 .changeset/late-geese-guess.md 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/createFaucetService.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/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 delete mode 100644 packages/world/abi/SnapSyncModule.sol/SnapSyncModule.abi.json delete mode 100644 packages/world/abi/SnapSyncSystem.sol/SnapSyncSystem.abi.json 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/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/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 76967eec03..0000000000 --- a/packages/network/CHANGELOG.md +++ /dev/null @@ -1,653 +0,0 @@ -# Change Log - -## 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 0c773301a2..0000000000 --- a/packages/network/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@latticexyz/network", - "version": "2.0.0-next.1", - "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.3.1" - }, - "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/createFaucetService.ts b/packages/network/src/createFaucetService.ts deleted file mode 100644 index 19ec92c215..0000000000 --- a/packages/network/src/createFaucetService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FaucetServiceDefinition } from "@latticexyz/services/faucet"; -import { createChannel, createClient, RawClient } from "nice-grpc-web"; -import { FromTsProtoServiceDefinition } from "nice-grpc-web/lib/service-definitions/ts-proto"; - -/** - * Create a FaucetServiceClient - * @param url FaucetService URL - * @returns FaucetServiceClient - */ -export function createFaucetService( - url: string -): RawClient> { - return createClient(FaucetServiceDefinition, createChannel(url)); -} 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 cb1959abd8..0000000000 --- a/packages/network/src/index.ts +++ /dev/null @@ -1,20 +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 { 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/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/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/pnpm-lock.yaml b/pnpm-lock.yaml index 15dd8af991..37bb6df129 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -464,97 +464,6 @@ importers: 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.3.1 - version: 1.3.1(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) - packages/noise: dependencies: abdk-libraries-solidity: @@ -3292,11 +3201,6 @@ packages: react-test-renderer: 18.2.0(react@18.2.0) dev: true - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: true - /@trpc/client@10.34.0(@trpc/server@10.34.0): resolution: {integrity: sha512-nqtDTIqSY/9syo2EjSy4WWWXPU9GsamEh9Tsg698gLAh1nhgFc5+/YYeb+Ne1pbvWGZ5/3t9Dcz3h4wMyyJ9gQ==} peerDependencies: @@ -3455,14 +3359,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 @@ -3608,10 +3504,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 @@ -3861,10 +3753,6 @@ packages: typescript: 5.1.6 dev: false - /abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - dev: true - /abdk-libraries-solidity@3.0.0: resolution: {integrity: sha512-oKBj/1wK6hIXxYjX7b2vCzJPHs9GDu49EBpNfUOw2FpA/nu1oV7V598MgAbB1+R1u7vuUhL6DYqolV+GMWaLvA==} dev: false @@ -3922,13 +3810,6 @@ packages: xtend: 4.0.2 dev: true - /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: @@ -4236,12 +4117,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 @@ -4363,11 +4238,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==} @@ -4593,6 +4463,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==} @@ -5042,21 +4913,6 @@ 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'} - dependencies: - cssom: 0.3.8 - dev: true - /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -5089,15 +4945,6 @@ packages: type: 1.2.0 dev: false - /data-urls@3.0.2: - resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} - engines: {node: '>=12'} - dependencies: - abab: 2.0.6 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - dev: true - /dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} dev: true @@ -5142,10 +4989,6 @@ packages: engines: {node: '>=10'} dev: true - /decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true - /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -5273,19 +5116,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'} - dependencies: - webidl-conversions: 7.0.0 - dev: true - /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -5435,11 +5265,6 @@ packages: ansi-colors: 4.1.3 dev: true - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - dev: true - /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -5633,19 +5458,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'} @@ -5828,12 +5640,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'} @@ -6126,12 +5932,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-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -6326,15 +6126,6 @@ packages: mime-types: 2.1.35 dev: true - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /fp-ts@1.19.3: resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} dev: true @@ -6773,13 +6564,6 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /html-encoding-sniffer@3.0.0: - resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} - engines: {node: '>=12'} - dependencies: - whatwg-encoding: 2.0.0 - dev: true - /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true @@ -6795,17 +6579,6 @@ packages: toidentifier: 1.0.1 dev: true - /http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - dev: true - /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -6844,13 +6617,6 @@ packages: dependencies: safer-buffer: 2.1.2 - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -7118,11 +6884,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'} @@ -7138,10 +6899,6 @@ packages: engines: {node: '>=8'} dev: true - /is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - /is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} @@ -7431,29 +7188,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} @@ -7791,47 +7525,6 @@ packages: argparse: 2.0.1 dev: true - /jsdom@20.0.3: - resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} - engines: {node: '>=14'} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - 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 - 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 - parse5: 7.1.2 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.2 - 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 - ws: 8.13.0 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -8742,10 +8435,6 @@ packages: strip-hex-prefix: 1.0.0 dev: true - /nwsapi@2.2.2: - resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} - dev: true - /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -8826,10 +8515,6 @@ packages: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} dev: true - /observable-fns@0.6.1: - resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==} - dev: false - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -9021,12 +8706,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - dev: true - /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -9403,10 +9082,6 @@ packages: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true - /psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - dev: true - /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -9428,10 +9103,6 @@ packages: side-channel: 1.0.4 dev: true - /querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true - /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -9602,14 +9273,6 @@ packages: dependencies: picomatch: 2.3.1 - /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'} @@ -9659,10 +9322,6 @@ packages: /require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true - /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -9821,13 +9480,6 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -10381,10 +10033,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - /table-layout@1.0.2: resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==} engines: {node: '>=8.0.0'} @@ -10502,19 +10150,6 @@ packages: any-promise: 1.3.0 dev: true - /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'} @@ -10535,14 +10170,6 @@ packages: next-tick: 1.1.0 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 @@ -10583,16 +10210,6 @@ packages: engines: {node: '>=0.6'} dev: true - /tough-cookie@4.1.2: - resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} - engines: {node: '>=6'} - dependencies: - psl: 1.9.0 - punycode: 2.1.1 - universalify: 0.2.0 - url-parse: 1.5.10 - dev: true - /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -10602,20 +10219,6 @@ packages: punycode: 2.1.1 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'} - dependencies: - punycode: 2.1.1 - dev: true - /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -10982,20 +10585,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'} @@ -11036,11 +10625,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - /universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - dev: true - /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} @@ -11067,13 +10651,6 @@ packages: dependencies: punycode: 2.1.1 - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: true - /use-local-storage-state@18.3.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-JiTuQsJmmKvc0mH0hiSjaTkKFlwtwXTeOlJ+cdg7rRJzZWwv+s/Rr2S2r2NR68O0W5ogwwt1MX1y+P2wQ1lY4w==} engines: {node: '>=12'} @@ -11291,13 +10868,6 @@ packages: resolution: {integrity: sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==} dev: false - /w3c-xmlserializer@4.0.0: - resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} - engines: {node: '>=14'} - dependencies: - xml-name-validator: 4.0.0 - dev: true - /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -11330,41 +10900,11 @@ 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'} - dev: true - /well-known-symbols@2.0.0: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} dev: true - /whatwg-encoding@2.0.0: - resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} - engines: {node: '>=12'} - dependencies: - iconv-lite: 0.6.3 - dev: true - - /whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - dev: true - - /whatwg-url@11.0.0: - resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} - engines: {node: '>=12'} - dependencies: - tr46: 3.0.0 - webidl-conversions: 7.0.0 - dev: true - /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -11379,15 +10919,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: @@ -11542,28 +11073,6 @@ packages: optional: true dev: false - /ws@8.13.0: - resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - - /xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - dev: true - - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true - /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}