From e4f256a27414134aa9ac541343bae97e219bcdd9 Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:12:11 -0400 Subject: [PATCH 01/44] chore: try to fix changesets issue --- .changeset/config.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.changeset/config.json b/.changeset/config.json index 91dc9ed06..a2eb8a038 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,6 +3,12 @@ "changelog": ["@changesets/changelog-github", { "repo": "0xOlias/ponder" }], "commit": false, "fixed": [["@ponder/core", "create-ponder", "eslint-config-ponder"]], + "ignore": [ + "ponder-examples-ethfs", + "ponder-examples-art-gobblers", + "ponder-examples-factory-llama", + "ponder-examples-friendtech" + ], "linked": [], "access": "public", "baseBranch": "main", From 80bdb974215396d87175956201eb9a036818b1a5 Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:12:37 -0400 Subject: [PATCH 02/44] Rename "event handlers" to "indexing functions" + docs updates (#399) * update nextra * rename event handlers to indexing function in docs * update readmes to use indexing functions * convert core to indexing functions * rest of repo --- README.md | 21 +- benchmarks/src/ponder.ts | 2 +- docs/package.json | 4 +- docs/pages/advanced/factory-contracts.mdx | 6 +- docs/pages/advanced/proxy-contracts.mdx | 4 +- docs/pages/api-reference.mdx | 2 +- docs/pages/api-reference/_meta.json | 2 +- ...nt-handlers.mdx => indexing-functions.mdx} | 32 +- docs/pages/api-reference/ponder-config.mdx | 6 +- docs/pages/api-reference/schema-graphql.mdx | 2 - docs/pages/compared-to-subgraphs.mdx | 2 +- .../getting-started/migrate-subgraph.mdx | 14 +- docs/pages/getting-started/new-project.mdx | 28 +- docs/pages/guides/design-your-schema.mdx | 4 +- docs/pages/guides/installation.mdx | 6 + docs/pages/guides/read-contract-data.mdx | 4 +- docs/pages/index.mdx | 37 +- docs/public/architecture.svg | 6 +- docs/theme.config.tsx | 3 +- packages/core/README.md | 21 +- packages/core/src/Ponder.ts | 39 +- .../core/src/_test/art-gobblers/app.test.ts | 2 +- packages/core/src/_test/ens/app.test.ts | 2 +- .../src/build/{handlers.ts => functions.ts} | 73 ++-- packages/core/src/build/service.ts | 32 +- packages/core/src/codegen/event.ts | 6 +- packages/core/src/codegen/service.ts | 2 +- packages/core/src/config/abi.ts | 2 +- packages/core/src/config/types.ts | 4 +- packages/core/src/event-aggregator/service.ts | 10 +- packages/core/src/index.ts | 2 +- .../contract.test.ts | 0 .../{user-handlers => indexing}/contract.ts | 2 +- .../src/{user-handlers => indexing}/model.ts | 0 .../service.test.ts | 74 ++-- .../{user-handlers => indexing}/service.ts | 166 ++++---- .../src/{user-handlers => indexing}/trace.ts | 0 packages/core/src/metrics/service.ts | 34 +- .../ui/{HandlersBar.tsx => IndexingBar.tsx} | 26 +- packages/core/src/ui/app.tsx | 33 +- packages/core/src/ui/service.ts | 26 +- .../core/src/user-store/postgres/store.ts | 4 +- packages/core/src/user-store/sqlite/store.ts | 4 +- packages/create-ponder/README.md | 21 +- packages/create-ponder/src/index.ts | 6 +- packages/create-ponder/src/templates/basic.ts | 2 +- packages/eslint-config-ponder/README.md | 21 +- pnpm-lock.yaml | 395 ++++++++++++------ 48 files changed, 671 insertions(+), 523 deletions(-) rename docs/pages/api-reference/{event-handlers.mdx => indexing-functions.mdx} (78%) rename packages/core/src/build/{handlers.ts => functions.ts} (73%) rename packages/core/src/{user-handlers => indexing}/contract.test.ts (100%) rename packages/core/src/{user-handlers => indexing}/contract.ts (98%) rename packages/core/src/{user-handlers => indexing}/model.ts (100%) rename packages/core/src/{user-handlers => indexing}/service.test.ts (82%) rename packages/core/src/{user-handlers => indexing}/service.ts (75%) rename packages/core/src/{user-handlers => indexing}/trace.ts (100%) rename packages/core/src/ui/{HandlersBar.tsx => IndexingBar.tsx} (65%) diff --git a/README.md b/README.md index 2fd265e25..b50089dff 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ Join [Ponder's telegram chat](https://t.me/ponder_sh) for support, feedback, and ✅  Supports all Ethereum-based blockchains, including test nodes like [Anvil](https://book.getfoundry.sh/anvil)
✅  Index events from multiple chains in the same app
✅  Reconciles chain reorganization
-🏗️  Transaction call event handlers
-🏗️  Support for factory contracts like Uniswap V2/V3
+✅  Factory contracts
+🏗️  Process transactions calls (in addition to logs)
+🏗️  Run effects (e.g. send an API request) in indexing code
## Quickstart ### 1. Run `create-ponder` -You will be asked for a project name, and if you are using an Etherscan or Graph Protocol [template](https://ponder.sh/api-reference/create-ponder) (recommended). +You will be asked for a project name, and if you are using a [template](https://ponder.sh/api-reference/create-ponder#templates) (recommended). Then, the CLI will create a project directory, install dependencies, and initialize a git repository. ```bash npm init ponder@latest @@ -45,7 +46,7 @@ yarn create ponder ### 2. Start the development server -The development server automatically reloads your app when you save changes in any project file, and prints `console.log` statements and errors in your code. +Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. First, `cd` into your project directory, then start the server. ```bash npm run dev @@ -57,7 +58,7 @@ yarn dev ### 3. Add contracts & networks -Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the handler functions you write. +Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the indexing functions you write. ```ts // ponder.config.ts @@ -85,7 +86,7 @@ export const config = { ### 4. Define your schema -The `schema.graphql` file specifies the shape of your application's data. +The `schema.graphql` file contains a model of your application data. The entity types defined here correspond to database tables. ```ts // schema.graphql @@ -98,9 +99,9 @@ type EnsName @entity { } ``` -### 5. Write event handlers +### 5. Write indexing functions -Use event handler functions to convert raw blockchain events into application data. +Files in the `src/` directory contain **indexing functions**, which are TypeScript functions that process a contract event. The purpose of these functions is to insert data into the entity store. ```ts // src/BaseRegistrar.ts @@ -122,9 +123,11 @@ ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => { }); ``` +See the [create & update entities](https://ponder.sh/guides/create-update-entities) docs for a detailed guide on writing indexing functions. + ### 6. Query the GraphQL API -Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API will serve the data that you inserted in your event handler functions. +Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API serves the data that you inserted in your indexing functions. ```ts { diff --git a/benchmarks/src/ponder.ts b/benchmarks/src/ponder.ts index 4ac96f9cb..85c30f333 100644 --- a/benchmarks/src/ponder.ts +++ b/benchmarks/src/ponder.ts @@ -26,7 +26,7 @@ const waitForSyncComplete = async () => { const metrics = await fetchPonderMetrics(); const latestProcessedTimestamp = metrics.find( - (m) => m.name === "ponder_handlers_latest_processed_timestamp" + (m) => m.name === "ponder_indexing_latest_processed_timestamp" )?.metrics[0].value ?? 0; console.log( `Latest processed timestamp: ${latestProcessedTimestamp}/${END_BLOCK_TIMESTAMP}` diff --git a/docs/package.json b/docs/package.json index 87ed903e9..1d3318d5e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -10,8 +10,8 @@ "dependencies": { "@segment/analytics-node": "^1.1.0", "next": "^13.4.12", - "nextra": "^2.10.0", - "nextra-theme-docs": "^2.10.0", + "nextra": "^2.13.2", + "nextra-theme-docs": "^2.13.2", "react": "^18.1.0", "react-dom": "^18.1.0" }, diff --git a/docs/pages/advanced/factory-contracts.mdx b/docs/pages/advanced/factory-contracts.mdx index be6ac67ce..7d3a97bdb 100644 --- a/docs/pages/advanced/factory-contracts.mdx +++ b/docs/pages/advanced/factory-contracts.mdx @@ -44,11 +44,11 @@ export const config: Config = { }; ``` -### Register event handlers +### Register indexing functions -When you register a handler function for a child contract event, that function will process events for **every** instance of that child contract. The `event.log.address` field contains the address of the specific child contract that emitted the event. +When you register an indexing function for a child contract event, that function will process events for **every** instance of that child contract. The `event.log.address` field contains the address of the specific child contract that emitted the event. -This event handler function will run for every `SudoswapPool` contract created by the factory. +This indexing function will run for every `SudoswapPool` contract created by the factory. ```ts filename="src/index.ts" import { ponder } from "@/generated"; diff --git a/docs/pages/advanced/proxy-contracts.mdx b/docs/pages/advanced/proxy-contracts.mdx index 5d3e1ac57..1fcd266ef 100644 --- a/docs/pages/advanced/proxy-contracts.mdx +++ b/docs/pages/advanced/proxy-contracts.mdx @@ -6,7 +6,7 @@ import { Callout } from "nextra-theme-docs"; # Proxy contracts -Ponder supports [EIP-1967 proxy contracts](https://docs.openzeppelin.com/contracts/4.x/api/proxy) (Transparent and UUPS). Ponder handles proxy contracts by merging the ABI of the proxy contract with the ABI of the implementation contract. +Ponder supports [EIP-1967 proxy contracts](https://docs.openzeppelin.com/contracts/4.x/api/proxy) (Transparent and UUPS) by merging the ABI of the proxy contract with the ABI of the implementation contract. The `create-ponder` Etherscan [contract link template](/api-reference/create-ponder#etherscan-contract-link) automatically detects proxy contracts and fetches all implementation contract ABIs. @@ -45,7 +45,7 @@ export const config: Config = { }; ``` -3. Add event handlers for events defined in the implementation ABI. That's it! +3. Add indexing functions for events defined in the implementation ABI. That's it! ```ts filename="src/index.ts" import { ponder } from "@/generated"; diff --git a/docs/pages/api-reference.mdx b/docs/pages/api-reference.mdx index 322733faa..fbf8c1df6 100644 --- a/docs/pages/api-reference.mdx +++ b/docs/pages/api-reference.mdx @@ -6,7 +6,7 @@ description: "Table of contents for Ponder API reference" [Create Ponder →](/api-reference/create-ponder) -[Event handlers →](/api-reference/event-handlers) +[Indexing functions →](/api-reference/indexing-functions) [`ponder.config.ts` →](/api-reference/ponder-config) diff --git a/docs/pages/api-reference/_meta.json b/docs/pages/api-reference/_meta.json index 168352fbb..908a91ab5 100644 --- a/docs/pages/api-reference/_meta.json +++ b/docs/pages/api-reference/_meta.json @@ -1,6 +1,6 @@ { "ponder-config": "ponder.config.ts", "schema-graphql": "schema.graphql", - "event-handlers": "Event handlers", + "indexing-functions": "Indexing functions", "create-ponder": "Create Ponder" } diff --git a/docs/pages/api-reference/event-handlers.mdx b/docs/pages/api-reference/indexing-functions.mdx similarity index 78% rename from docs/pages/api-reference/event-handlers.mdx rename to docs/pages/api-reference/indexing-functions.mdx index 5709b627b..e0d31e4df 100644 --- a/docs/pages/api-reference/event-handlers.mdx +++ b/docs/pages/api-reference/indexing-functions.mdx @@ -1,16 +1,16 @@ --- -description: "API reference for Ponder event handlers" +description: "API reference for Ponder indexing functions" --- import { Callout } from "nextra-theme-docs"; -# Event handler API +# Indexing function API -Event handlers are user-defined functions that process blockchain data. They can be registered within any `.ts` file inside the `src/` directory. There are two kinds of events: **log events** and the **`"setup"` event**. +Indexing functions are user-defined functions that process blockchain data. They can be registered within any `.ts` file inside the `src/` directory. There are two kinds of events: **log events** and the **`"setup"` event**. ## Log events -Log events correspond to a smart contract event log that has occurred. The Ponder engine fetches event logs for all contracts defined in `ponder.config.ts`, then passes that data to the corresponding handler function. +Log events correspond to a smart contract event log that has occurred. The Ponder engine fetches event logs for all contracts defined in `ponder.config.ts`, then passes that data to the corresponding indexing function. ```ts filename="src/index.ts" import { ponder } from "@/generated"; @@ -25,14 +25,12 @@ ponder.on("ContractName:EventName", async ({ event, context }) => { ### Options -| name | description | -| :------------------------------------------------- | :--------------------------------------------------------------- | -| [`event`](/api-reference/event-handlers#event) | Event-specific data (log arguments, log, block, and transaction) | -| [`context`](/api-reference/event-handlers#context) | Global resources (entity objects, read-only contract objects) | +| name | description | +| :----------------------------------------------------- | :--------------------------------------------------------------- | +| [`event`](/api-reference/indexing-functions#event) | Event-specific data (log arguments, log, block, and transaction) | +| [`context`](/api-reference/indexing-functions#context) | Global resources (entity objects, read-only contract objects) | - - The value returned by event handler functions is ignored. - +Value returned by indexing functions are ignored. ### `Event` @@ -183,7 +181,7 @@ See [Create & update entities](/guides/create-update-entities) for a complete AP `ReadOnlyContract` objects are used to read data directly from a contract. These objects have a method for each read-only function present in the contract's ABI (functions with state mutability of `"pure"` or `"view"`). The `context.contracts` object has a `ReadOnlyContract` for each contract defined in [ponder.config.ts](/api-reference/ponder-config#contracts). -A `ReadOnlyContract` is a [viem Contract Instance](https://viem.sh/docs/contract/getContract.html#contract-instances) that has been modified to cache contract read results. By default, contract reads use the `eth_call` RPC method with `blockNumber` set to the block number of the event being handled (`event.block.number`). You can read the contract at a different block number (e.g. the contract deployment block number or `"latest"`) by passing the `blockNumber` or `blockTag` option, but this will disable caching. +A `ReadOnlyContract` is a [viem Contract Instance](https://viem.sh/docs/contract/getContract.html#contract-instances) that has been modified to cache contract read results. By default, contract reads use the `eth_call` RPC method with `blockNumber` set to the block number of the event being processed (`event.block.number`). You can read the contract at a different block number (e.g. the contract deployment block number or `"latest"`) by passing the `blockNumber` or `blockTag` option, but this will disable caching. ```ts filename="src/index.ts" import { ponder } from "@/generated"; @@ -192,7 +190,7 @@ ponder.on("MyERC20:Transfer", async ({ event, context }) => { const { MyERC20 } = context.contracts; // This read will occur at the block number of the event being - // handled (event.block.number) and will be served from the cache + // processed (event.block.number) and will be served from the cache // on hot reloads / reployments. const totalSupply = await MyERC20.read.totalSupply(); @@ -206,7 +204,7 @@ ponder.on("MyERC20:Transfer", async ({ event, context }) => { ## The `"setup"` event -You can also define a handler for a special event called `"setup"` that runs before all other event handlers. +You can also define an indexing function for a special event called `"setup"` that runs before all other events. ```ts filename="src/index.ts" import { ponder } from "@/generated"; @@ -220,6 +218,6 @@ ponder.on("setup", async ({ context }) => { ### Options -| name | description | -| :------------------------------------------------- | :------------------------------------------------------------ | -| [`context`](/api-reference/event-handlers#context) | Global resources (entity objects, read-only contract objects) | +| name | description | +| :----------------------------------------------------- | :------------------------------------------------------------ | +| [`context`](/api-reference/indexing-functions#context) | Global resources (entity objects, read-only contract objects) | diff --git a/docs/pages/api-reference/ponder-config.mdx b/docs/pages/api-reference/ponder-config.mdx index 1d0b12d6a..1c86879eb 100644 --- a/docs/pages/api-reference/ponder-config.mdx +++ b/docs/pages/api-reference/ponder-config.mdx @@ -33,7 +33,7 @@ Networks are Ethereum-based blockchains like Ethereum mainnet, Goerli, or Foundr ### Contracts -Contracts are smart contracts deployed to a network. All contracts defined here will become available as `ReadOnlyContract` objects in the event context. Ponder will fetch and handle event logs emitted by all contracts with `isLogEventSource: true` (the default). +Contracts are smart contracts deployed to a network. All contracts defined here will become available as `ReadOnlyContract` objects in the event context. Ponder will fetch and process event logs emitted by all contracts with `isLogEventSource: true` (the default). | field | type | | | :------------------- | :---------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -45,7 +45,7 @@ Contracts are smart contracts deployed to a network. All contracts defined here | **startBlock** | `number \| undefined` | **Default: `0`**. Block number to start handling events. Usually set to the contract deployment block number. **Default: `0`** | | **endBlock** | `number \| undefined` | **Default: `undefined`**. Block number to stop handling events. If this field is specified, Ponder will not listen to "live" events. This field can be used alongside `startBlock` to only process events for a range of blocks. | | **maxBlockRange** | `number \| undefined` | The maximum block range that Ponder will use when calling `eth_getLogs`. If not provided, Ponder uses a sane default based on the network. | -| **isLogEventSource** | `boolean \| undefined` | **Default: `true`**. Specifies if events should be handled for this contract. If `false`, this contrat will still be available on the event handler `context.contracts`. | +| **isLogEventSource** | `boolean \| undefined` | **Default: `true`**. Specifies if events should be processed for this contract. If `false`, this contract will still be available on the indexing function `context.contracts`. | #### Factory @@ -57,7 +57,7 @@ Contracts are smart contracts deployed to a network. All contracts defined here ### Filters -Filters are custom event log filters that are more flexible than `contracts`. Ponder will fetch and handle event logs that match the `filter` field. +Filters are custom event log filters that are more flexible than `contracts`. Ponder will fetch and process event logs that match the `filter` field. | field | type | | | :------------- | :--------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **name** | `string` | A unique name for the custom filter. Must be unique across all contracts & filters. | diff --git a/docs/pages/api-reference/schema-graphql.mdx b/docs/pages/api-reference/schema-graphql.mdx index bfb2ee2d4..2c1487743 100644 --- a/docs/pages/api-reference/schema-graphql.mdx +++ b/docs/pages/api-reference/schema-graphql.mdx @@ -17,7 +17,5 @@ Ponder currently supports a subset of the Graph Protocol schema definition langu - `BigDecimal` and `ID` GraphQL field types - Full text search -- Call handlers -- Block handlers - Anonymous events - The `immutable` argument to the `@entity` type directive is allowed for compatibility, but immutability is not enforced diff --git a/docs/pages/compared-to-subgraphs.mdx b/docs/pages/compared-to-subgraphs.mdx index 8d719cc63..64ed47ae2 100644 --- a/docs/pages/compared-to-subgraphs.mdx +++ b/docs/pages/compared-to-subgraphs.mdx @@ -14,6 +14,6 @@ description: "A comparison between Ponder and Graph Protocol subgraphs" | Cross-chain support | ✅  Any number of chains per app | ❌  Each subgraph indexes one chain | | Chain reorganizations | ✅ | ✅ | | Time-travel queries | ✅ | ✅ | -| Factory contracts | 🚧 (in progress) | ✅ | +| Factory contracts | ✅ | ✅ | | Transaction call handlers | 🚧 (planned) | ✅ | | Block handlers | 🚧 (planned) | ✅ | diff --git a/docs/pages/getting-started/migrate-subgraph.mdx b/docs/pages/getting-started/migrate-subgraph.mdx index 12d1be8b2..5311bfea5 100644 --- a/docs/pages/getting-started/migrate-subgraph.mdx +++ b/docs/pages/getting-started/migrate-subgraph.mdx @@ -42,12 +42,6 @@ First, enter a project name. Then select the **Subgraph ID** template option and The newly generated Ponder app will copy the subgraph's `schema.graphql` as well as the addresses, ABIs, and start blocks for each event source defined in `subgraph.yaml`. Then, it will create a project directory, install dependencies, and initialize a git repository. - - Ponder requires `node-gyp` to be installed globally. If you encounter an - installation error, try `pnpm install -g node-gyp` and then run - `create-ponder` again. - - ### Start the development server Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. @@ -84,21 +78,21 @@ Open up `.env.local` and paste in RPC URLs for any networks that your project us PONDER_RPC_URL_1 = "https://eth-mainnet.g.alchemy.com/v2/..." ``` -### Write event handler functions +### Write indexing functions -Open an event handler file in the `src/` directory. Ponder event handler functions are very similar to subgraph mapping functions, with some key differences. You will need to refactor your code, but because the local dev server has hot reloading, you'll get instant feedback on your changes. +Open any file in the `src/` directory. Ponder indexing functions are very similar to subgraph mapping functions, with some key differences. You will need to refactor your code, but because the local dev server has hot reloading, you'll get instant feedback on your changes. See [tips for migrating mapping functions](/getting-started/migrate-subgraph#tips-for-migrating-mapping-functions) for more details. ### Query the GraphQL API -As you migrate your event handlers and start inserting entity data, open the GraphiQL interface at `http://localhost:42069/graphql` to explore your GraphQL API locally. Any changes you make to your `schema.graphql` file will be reflected here. +As you migrate your indexing functions and start inserting entity data, open the GraphiQL interface at `http://localhost:42069/graphql` to explore your GraphQL API locally. Any changes you make to your `schema.graphql` file will be reflected here. ## Tips for migrating mapping functions -### Handlers run in Node.js, not WebAssembly +### Functions run in Node.js, not WebAssembly You can import NPM packages, debug with `console.log`, and use normal JavaScript types like `string` and `number`. diff --git a/docs/pages/getting-started/new-project.mdx b/docs/pages/getting-started/new-project.mdx index 73d8bcae5..e45d2447f 100644 --- a/docs/pages/getting-started/new-project.mdx +++ b/docs/pages/getting-started/new-project.mdx @@ -11,6 +11,11 @@ import { Callout } from "nextra-theme-docs"; ### Create a new Ponder project + + If the contract you're indexing has already been deployed, use the [Etherscan + contract link template](/api-reference/create-ponder#etherscan-contract-link). + + {/* prettier-ignore */} @@ -32,17 +37,6 @@ npm create ponder@latest You will be asked for a project name, and if you are using a [template](/api-reference/create-ponder#templates) (recommended). Then, it will create a project directory, install dependencies, and initialize a git repository. - - If the contract you're indexing has already been deployed, use the [Etherscan - contract link template](/api-reference/create-ponder#etherscan-contract-link). - - - - Ponder requires `node-gyp` to be installed globally. If you encounter an - installation error, try `pnpm install -g node-gyp` and then run - `create-ponder` again. - - ### Start the development server Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. @@ -81,7 +75,7 @@ PONDER_RPC_URL_1 = "https://eth-mainnet.g.alchemy.com/v2/..." ### Design your schema -The `schema.graphql` file contains a model of your application data. The **entity types** defined here can be thought of as database tables. +The `schema.graphql` file contains a model of your application data. The **entity types** defined here correspond to database tables. ```graphql filename="schema.graphql" type BlitmapToken @entity { @@ -92,11 +86,11 @@ type BlitmapToken @entity { See [Design your schema](/guides/design-your-schema) for a detailed guide on schema design. -### Write event handler functions +### Write indexing functions -Files in the `src/` directory contain **event handler functions**, which are simply TypeScript functions that process a contract event. The purpose of event handler functions is to insert data into the entity store. +Files in the `src/` directory contain **indexing functions**, which are TypeScript functions that process a contract event. The purpose of these functions is to insert data into the entity store. -Here's a sample event handler for an ERC721 `Transfer` event. +Here's a sample indexing function for an ERC721 `Transfer` event. ```ts filename="src/index.ts" import { ponder } from "@/generated"; @@ -113,11 +107,11 @@ ponder.on("Blitmap:Transfer", async ({ event, context }) => { }); ``` -See [Create & update entities](/guides/create-update-entities) for a detailed guide on writing event handlers. +See [Create & update entities](/guides/create-update-entities) for a detailed guide on writing indexing functions. ### Query the GraphQL API -As you write your event handlers and start inserting entity data, open the GraphiQL interface at `http://localhost:42069/graphql` to explore your GraphQL API locally. Any changes you make to your `schema.graphql` file will be reflected here. +As you write your indexing functions and start inserting entity data, open the GraphiQL interface at `http://localhost:42069/graphql` to explore your GraphQL API locally. Any changes you make to your `schema.graphql` file will be reflected here.
diff --git a/docs/pages/guides/design-your-schema.mdx b/docs/pages/guides/design-your-schema.mdx index ef225f7eb..c2d63d169 100644 --- a/docs/pages/guides/design-your-schema.mdx +++ b/docs/pages/guides/design-your-schema.mdx @@ -30,7 +30,7 @@ Every entity type _must_ have an `id` field that is a `String!`, `Int!`, `Bytes! ### Scalars -In GraphQL, **scalars** are the most primitive types – they represent concrete data like strings and numbers. Each GraphQL scalar maps to a TypeScript type (used in event handler code) and a JSON data type (returned by the GraphQL API). +In GraphQL, **scalars** are the most primitive types – they represent concrete data like strings and numbers. Each GraphQL scalar maps to a TypeScript type (used in indexing function code) and a JSON data type (returned by the GraphQL API). | name | description | TypeScript type | JSON data type | | :-------- | :------------------------------------------ | :-------------- | :------------- | @@ -290,7 +290,7 @@ const bob = await Person.get("Bob"); ### Entity types should generally be nouns -Blockchain events describe an action that has taken place (a verb). Event handler funtions are most effective when they convert events into the nouns that represent the current state of your application. For example, prefer modeling an ERC721 collection using `Account` and `Token` entities rather than `TransferEvent` entities. (Unless you need the full transfer history of every account). +Blockchain events describe an action that has taken place (a verb). Indexing funtions are most effective when they convert events into the nouns that represent the current state of your application. For example, prefer modeling an ERC721 collection using `Account` and `Token` entities rather than `TransferEvent` entities. (Unless you need the full transfer history of every account). ### Use relationship types generously diff --git a/docs/pages/guides/installation.mdx b/docs/pages/guides/installation.mdx index 298d842ef..4b90b267e 100644 --- a/docs/pages/guides/installation.mdx +++ b/docs/pages/guides/installation.mdx @@ -6,6 +6,12 @@ import { Callout } from "nextra-theme-docs"; # Installation + + Ponder usually requires `node-gyp` to be installed globally. If you encounter + an installation error, try `npm install -g node-gyp` and then run + `create-ponder` again. + + ## System requirements - MacOS, Linux, or Windows (via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) diff --git a/docs/pages/guides/read-contract-data.mdx b/docs/pages/guides/read-contract-data.mdx index 3fb2fb560..a22279980 100644 --- a/docs/pages/guides/read-contract-data.mdx +++ b/docs/pages/guides/read-contract-data.mdx @@ -92,7 +92,7 @@ ponder.on("AaveToken:Mint", async ({ event, context }) => { ## Caching -To avoid unnecessary RPC requests and speed up indexing, Ponder caches all contract read results. When an event handler that reads a contract runs for the first time, it will make an RPC request. But on subsequent hot reloads or redeployments, this data will be served from the cache. +To avoid unnecessary RPC requests and speed up indexing, Ponder caches all contract read results. When an indexing function that reads a contract runs for the first time, it will make an RPC request. But on subsequent hot reloads or redeployments, this data will be served from the cache. To take advantage of caching, you _must_ use `context.contracts`. _Do not manually set up a viem Client._ @@ -134,7 +134,7 @@ ponder.on("Blitmap:Mint", async ({ event, context }) => { ## Specify a block number -By default, contract reads use the `eth_call` RPC method with `blockNumber` set to the block number of the event being handled (`event.block.number`). You can read the contract at a different block number (e.g. the contract deployment block number or `"latest"`) by passing the `blockNumber` or `blockTag` option, but this will disable caching. +By default, contract reads use the `eth_call` RPC method with `blockNumber` set to the block number of the event being processed (`event.block.number`). You can read the contract at a different block number (e.g. the contract deployment block number or `"latest"`) by passing the `blockNumber` or `blockTag` option, but this will disable caching. ```ts filename="src/index.ts" ponder.on("Blitmap:Mint", async ({ event, context }) => { diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index fbbbc3ad9..c8a5d77b7 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -3,7 +3,7 @@ description: "An introduction to Ponder" title: "Introduction" --- -import { Steps } from "nextra/components"; +import { FileTree, Steps } from "nextra/components"; import { Callout } from "nextra-theme-docs"; import Architecture from "../public/architecture.svg"; @@ -20,31 +20,28 @@ Ponder is an open-source framework for blockchain application backends. With Pon ✅  Quickly deploy to [Railway](/guides/production), or anywhere using Node.js/Docker
✅  Native support for cross-chain apps
✅  Gracefully handles chain reorganizations
-🏗️  Handle transactions calls (in addition to logs)
-🏗️  Run effects (e.g. send an API request) in event handler code
+🏗️  Process transactions calls (in addition to logs)
+🏗️  Run effects (e.g. send an API request) in indexing code
## Architecture
- Data flows from smart contracts → event handler functions → application data - store → GraphQL API → client applications. + Data flows from smart contracts → indexing functions → application data store + → GraphQL API → client applications.
-## Example: ENS +## Example: Ethereum Name Service Ponder is similar to Graph Protocol subgraphs. If you've built a subgraph before, skip to [getting started](/getting-started/new-project). -Let's use Ponder to build a GraphQL API that tracks ownership of Ethereum Name Service registrations. - -The ENS `BaseRegistrar` contract emits a `NameRegistered` event every time a user registers a new ENS name. Here's the relevant snippet from that contract: +Let's use Ponder to build a GraphQL API that tracks ownership of ENS registrations. The ENS `BaseRegistrar` contract emits a `NameRegistered` event every time a user registers a new ENS name. ```solidity contract BaseRegistrar { - event NameRegistered(string name, address owner); function registerName(string calldata name, address owner) external { @@ -56,9 +53,9 @@ contract BaseRegistrar { -### Ponder config +### Add a contract to `ponder.config.ts` -First we'll add `BaseRegistrar` as a contract in our config file. Ponder's sync engine will now fetch all the events logs that have been emitted by the `BaseRegistrar` contract. +First, we add `BaseRegistrar` as a contract in our config file. Ponder's sync engine fetches all the events logs that have been emitted by the `BaseRegistrar` contract. ```ts filename="ponder.config.ts" import { http } from "viem"; @@ -83,9 +80,9 @@ export const config = { }; ``` -### GraphQL schema +### Create your schema -Next we'll add an entity to our schema. The `schema.graphql` file defines the shape of the data that our GraphQL API will serve. Let's add an `EnsName` entity that stores the name, the owner's address, and a timestamp. +Next, we add an entity to our schema. The `schema.graphql` file defines the shape of the data that our GraphQL API will serve. Let's add an `EnsName` entity that stores the name, the owner's address, and a timestamp. ```graphql filename="schema.graphql" type EnsName @entity { @@ -96,11 +93,9 @@ type EnsName @entity { } ``` -### Event handler functions - -Event handlers are TypeScript functions that process a blockchain event and insert data into the Entity store matching the GraphQL schema. +### Write indexing code -Here's an event handler to process the `NameRegistered` event. It inserts an `EnsName` entity into the store using the event parameters and the block timestamp. +Indexing functions are TypeScript functions that process a blockchain event and insert data into the entity store. Here's an indexing function to process the `NameRegistered` event. It inserts an `EnsName` entity into the store using the event parameters and the block timestamp. {/* prettier-ignore */} ```ts filename="src/EthereumNameService.ts" @@ -121,9 +116,9 @@ ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => { }); ``` -### GraphQL API +### Query the API -Now that we've inserted some data into the Entity store, we can query that data from the autogenerated GraphQL API: +Now that we've inserted some data into the Entity store, we can query that data from the autogenerated GraphQL API. {/* prettier-ignore */} ```graphql filename="http://localhost:42069/graphql" @@ -154,7 +149,7 @@ query GetEnsNames { } ``` -The GraphQL API includes useful features like pagination, sorting, and complex filters for each entity defined in `schema.graphql`. +The GraphQL API automatically includes features like pagination, sorting, and complex filters for each entity defined in `schema.graphql`. diff --git a/docs/public/architecture.svg b/docs/public/architecture.svg index 681692b9f..2b3147011 100644 --- a/docs/public/architecture.svg +++ b/docs/public/architecture.svg @@ -1,4 +1,4 @@ - + @@ -12,5 +12,7 @@ src: url("https://excalidraw.com/Cascadia.woff2"); } + - PonderEvent handlersBlockchainRawblockchaindataDatabaseApplicationApplicationdataWebsiteGraphQL APIMobile appSmart contractSmart contractApplication data \ No newline at end of file + + PonderIndexing functionsBlockchainRawblockchaindataDatabaseApplicationApplicationdataWebsiteGraphQL APIMobile appSmart contractSmart contractApplication data \ No newline at end of file diff --git a/docs/theme.config.tsx b/docs/theme.config.tsx index 2cd61396b..1924839ac 100644 --- a/docs/theme.config.tsx +++ b/docs/theme.config.tsx @@ -18,7 +18,8 @@ const config: DocsThemeConfig = { sidebar: { defaultMenuCollapseLevel: 2, }, - primaryHue: 188, + primaryHue: 186, + primarySaturation: 86, editLink: { text: "Edit this page on GitHub →", }, diff --git a/packages/core/README.md b/packages/core/README.md index 2fd265e25..b50089dff 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -26,14 +26,15 @@ Join [Ponder's telegram chat](https://t.me/ponder_sh) for support, feedback, and ✅  Supports all Ethereum-based blockchains, including test nodes like [Anvil](https://book.getfoundry.sh/anvil)
✅  Index events from multiple chains in the same app
✅  Reconciles chain reorganization
-🏗️  Transaction call event handlers
-🏗️  Support for factory contracts like Uniswap V2/V3
+✅  Factory contracts
+🏗️  Process transactions calls (in addition to logs)
+🏗️  Run effects (e.g. send an API request) in indexing code
## Quickstart ### 1. Run `create-ponder` -You will be asked for a project name, and if you are using an Etherscan or Graph Protocol [template](https://ponder.sh/api-reference/create-ponder) (recommended). +You will be asked for a project name, and if you are using a [template](https://ponder.sh/api-reference/create-ponder#templates) (recommended). Then, the CLI will create a project directory, install dependencies, and initialize a git repository. ```bash npm init ponder@latest @@ -45,7 +46,7 @@ yarn create ponder ### 2. Start the development server -The development server automatically reloads your app when you save changes in any project file, and prints `console.log` statements and errors in your code. +Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. First, `cd` into your project directory, then start the server. ```bash npm run dev @@ -57,7 +58,7 @@ yarn dev ### 3. Add contracts & networks -Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the handler functions you write. +Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the indexing functions you write. ```ts // ponder.config.ts @@ -85,7 +86,7 @@ export const config = { ### 4. Define your schema -The `schema.graphql` file specifies the shape of your application's data. +The `schema.graphql` file contains a model of your application data. The entity types defined here correspond to database tables. ```ts // schema.graphql @@ -98,9 +99,9 @@ type EnsName @entity { } ``` -### 5. Write event handlers +### 5. Write indexing functions -Use event handler functions to convert raw blockchain events into application data. +Files in the `src/` directory contain **indexing functions**, which are TypeScript functions that process a contract event. The purpose of these functions is to insert data into the entity store. ```ts // src/BaseRegistrar.ts @@ -122,9 +123,11 @@ ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => { }); ``` +See the [create & update entities](https://ponder.sh/guides/create-update-entities) docs for a detailed guide on writing indexing functions. + ### 6. Query the GraphQL API -Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API will serve the data that you inserted in your event handler functions. +Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API serves the data that you inserted in your indexing functions. ```ts { diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index e1f1835be..7e5b452c5 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -16,13 +16,13 @@ import { PostgresEventStore } from "@/event-store/postgres/store"; import { SqliteEventStore } from "@/event-store/sqlite/store"; import { type EventStore } from "@/event-store/store"; import { HistoricalSyncService } from "@/historical-sync/service"; +import { IndexingService } from "@/indexing/service"; import { LoggerService } from "@/logs/service"; import { MetricsService } from "@/metrics/service"; import { RealtimeSyncService } from "@/realtime-sync/service"; import { ServerService } from "@/server/service"; import { TelemetryService } from "@/telemetry/service"; import { UiService } from "@/ui/service"; -import { EventHandlerService } from "@/user-handlers/service"; import { PostgresUserStore } from "@/user-store/postgres/store"; import { SqliteUserStore } from "@/user-store/sqlite/store"; import { type UserStore } from "@/user-store/store"; @@ -52,7 +52,7 @@ export class Ponder { }[] = []; eventAggregatorService: EventAggregatorService; - eventHandlerService: EventHandlerService; + indexingService: IndexingService; serverService: ServerService; buildService: BuildService; @@ -162,7 +162,7 @@ export class Ponder { factories, }); - this.eventHandlerService = new EventHandlerService({ + this.indexingService = new IndexingService({ common, eventStore: this.eventStore, userStore: this.userStore, @@ -197,16 +197,16 @@ export class Ponder { await this.serverService.start(); // These files depend only on ponder.config.ts, so can generate once on setup. - // Note that loadHandlers depends on the index.ts file being present. + // Note that buildIndexingFunctions depends on the index.ts file being present. this.codegenService.generateAppFile(); - // Note that this must occur before loadSchema and loadHandlers. + // Note that this must occur before buildSchema and buildIndexingFunctions. await this.eventStore.migrateUp(); - // Manually trigger loading schema and handlers. Subsequent loads + // Manually trigger loading schema and indexing functions. Subsequent loads // are triggered by changes to project files (handled in BuildService). this.buildService.buildSchema(); - await this.buildService.buildHandlers(); + await this.buildService.buildIndexingFunctions(); } async dev() { @@ -295,7 +295,7 @@ export class Ponder { await this.buildService.kill?.(); this.uiService.kill(); - this.eventHandlerService.kill(); + this.indexingService.kill(); await this.serverService.kill(); await this.userStore.teardown(); await this.common.telemetry.kill(); @@ -323,14 +323,17 @@ export class Ponder { this.serverService.reload({ graphqlSchema }); - await this.eventHandlerService.reset({ schema }); - await this.eventHandlerService.processEvents(); + await this.indexingService.reset({ schema }); + await this.indexingService.processEvents(); }); - this.buildService.on("newHandlers", async ({ handlers }) => { - await this.eventHandlerService.reset({ handlers }); - await this.eventHandlerService.processEvents(); - }); + this.buildService.on( + "newIndexingFunctions", + async ({ indexingFunctions }) => { + await this.indexingService.reset({ indexingFunctions }); + await this.indexingService.processEvents(); + } + ); this.networkSyncServices.forEach((networkSyncService) => { const { chainId } = networkSyncService.network; @@ -374,18 +377,18 @@ export class Ponder { }); this.eventAggregatorService.on("newCheckpoint", async () => { - await this.eventHandlerService.processEvents(); + await this.indexingService.processEvents(); }); this.eventAggregatorService.on( "reorg", async ({ commonAncestorTimestamp }) => { - await this.eventHandlerService.handleReorg({ commonAncestorTimestamp }); - await this.eventHandlerService.processEvents(); + await this.indexingService.handleReorg({ commonAncestorTimestamp }); + await this.indexingService.processEvents(); } ); - this.eventHandlerService.on("eventsProcessed", ({ toTimestamp }) => { + this.indexingService.on("eventsProcessed", ({ toTimestamp }) => { if (this.serverService.isHistoricalIndexingComplete) return; // If a batch of events are processed AND the historical sync is complete AND diff --git a/packages/core/src/_test/art-gobblers/app.test.ts b/packages/core/src/_test/art-gobblers/app.test.ts index 9f8d572ee..434a65975 100644 --- a/packages/core/src/_test/art-gobblers/app.test.ts +++ b/packages/core/src/_test/art-gobblers/app.test.ts @@ -43,7 +43,7 @@ const setup = async ({ context }: { context: TestContext }) => { // Wait for historical sync event processing to complete. await new Promise((resolve) => { - ponder.eventHandlerService.on("eventsProcessed", ({ toTimestamp }) => { + ponder.indexingService.on("eventsProcessed", ({ toTimestamp }) => { // Block 15870405 if (toTimestamp >= 1667247815) { resolve(); diff --git a/packages/core/src/_test/ens/app.test.ts b/packages/core/src/_test/ens/app.test.ts index 0dc6855c9..86de428de 100644 --- a/packages/core/src/_test/ens/app.test.ts +++ b/packages/core/src/_test/ens/app.test.ts @@ -43,7 +43,7 @@ const setup = async ({ context }: { context: TestContext }) => { // Wait for historical sync event processing to complete. await new Promise((resolve) => { - ponder.eventHandlerService.on("eventsProcessed", ({ toTimestamp }) => { + ponder.indexingService.on("eventsProcessed", ({ toTimestamp }) => { // Block 16370020 if (toTimestamp >= 1673276663) { resolve(); diff --git a/packages/core/src/build/handlers.ts b/packages/core/src/build/functions.ts similarity index 73% rename from packages/core/src/build/handlers.ts rename to packages/core/src/build/functions.ts index ef658e8e3..0e4ae5d70 100644 --- a/packages/core/src/build/handlers.ts +++ b/packages/core/src/build/functions.ts @@ -24,7 +24,7 @@ export interface LogEvent { type EventSourceName = string; type EventName = string; -type LogEventHandlerFunction = ({ +type LogEventIndexingFunction = ({ event, context, }: { @@ -32,37 +32,38 @@ type LogEventHandlerFunction = ({ context: unknown; }) => Promise | void; -type SetupEventHandlerFunction = ({ +type SetupEventIndexingFunction = ({ context, }: { context: unknown; }) => Promise | void; -type RawHandlerFunctions = { +type RawIndexingFunctions = { _meta_?: { - setup?: SetupEventHandlerFunction; + setup?: SetupEventIndexingFunction; }; eventSources: { [key: EventSourceName]: { - [key: EventName]: LogEventHandlerFunction; + [key: EventName]: LogEventIndexingFunction; }; }; }; // @ponder/core creates an instance of this class called `ponder` export class PonderApp< - EventHandlers = Record + IndexingFunctions = Record > { - private handlerFunctions: RawHandlerFunctions = { eventSources: {} }; + private indexingFunctions: RawIndexingFunctions = { eventSources: {} }; private errors: Error[] = []; - on>( + on>( name: EventName, - handler: EventHandlers[EventName] + indexingFunction: IndexingFunctions[EventName] ) { if (name === "setup") { - this.handlerFunctions._meta_ ||= {}; - this.handlerFunctions._meta_.setup = handler as SetupEventHandlerFunction; + this.indexingFunctions._meta_ ||= {}; + this.indexingFunctions._meta_.setup = + indexingFunction as SetupEventIndexingFunction; return; } @@ -72,19 +73,19 @@ export class PonderApp< return; } - this.handlerFunctions.eventSources[eventSourceName] ||= {}; - if (this.handlerFunctions.eventSources[eventSourceName][eventName]) { + this.indexingFunctions.eventSources[eventSourceName] ||= {}; + if (this.indexingFunctions.eventSources[eventSourceName][eventName]) { this.errors.push( - new Error(`Cannot add multiple handler functions for event: ${name}`) + new Error(`Cannot add multiple indexing functions for event: ${name}`) ); return; } - this.handlerFunctions.eventSources[eventSourceName][eventName] = - handler as LogEventHandlerFunction; + this.indexingFunctions.eventSources[eventSourceName][eventName] = + indexingFunction as LogEventIndexingFunction; } } -export const buildRawHandlerFunctions = async ({ +export const buildRawIndexingFunctions = async ({ options, }: { options: Options; @@ -182,49 +183,47 @@ export const buildRawHandlerFunctions = async ({ throw error; } - const handlers = app["handlerFunctions"] as RawHandlerFunctions; - - return handlers; + return app["indexingFunctions"] as RawIndexingFunctions; }; -export type HandlerFunctions = { +export type IndexingFunctions = { _meta_: { setup?: { - fn: SetupEventHandlerFunction; + fn: SetupEventIndexingFunction; }; }; eventSources: { [key: EventSourceName]: { - // This mapping is passed from the EventHandlerService to the EventAggregatorService, which uses - // it to fetch from the store _only_ the events that the user has handled. + // This mapping is passed from the IndexingService to the EventAggregatorService, which uses + // it to fetch from the store _only_ the events that the user has indexed. bySelector: { [key: Hex]: LogEventMetadata }; - // This mapping is used by the EventHandlerService to fetch the user-provided `fn` before running it. + // This mapping is used by the IndexingService to fetch the user-provided `fn` before running it. bySafeName: { - [key: EventName]: LogEventMetadata & { fn: LogEventHandlerFunction }; + [key: EventName]: LogEventMetadata & { fn: LogEventIndexingFunction }; }; }; }; }; -export const hydrateHandlerFunctions = ({ - rawHandlerFunctions, +export const hydrateIndexingFunctions = ({ + rawIndexingFunctions, logFilters, factories, }: { - rawHandlerFunctions: RawHandlerFunctions; + rawIndexingFunctions: RawIndexingFunctions; logFilters: LogFilter[]; factories: Factory[]; }) => { - const handlerFunctions: HandlerFunctions = { + const indexingFunctions: IndexingFunctions = { _meta_: {}, eventSources: {}, }; - if (rawHandlerFunctions._meta_?.setup) { - handlerFunctions._meta_.setup = { fn: rawHandlerFunctions._meta_.setup }; + if (rawIndexingFunctions._meta_?.setup) { + indexingFunctions._meta_.setup = { fn: rawIndexingFunctions._meta_.setup }; } - Object.entries(rawHandlerFunctions.eventSources).forEach( + Object.entries(rawIndexingFunctions.eventSources).forEach( ([eventSourceName, eventSourceFunctions]) => { const logFilter = logFilters.find((l) => l.name === eventSourceName); const factory = factories.find((f) => f.name === eventSourceName); @@ -242,19 +241,19 @@ export const hydrateHandlerFunctions = ({ throw new Error(`Log event not found in ABI: ${eventName}`); } - handlerFunctions.eventSources[eventSourceName] ||= { + indexingFunctions.eventSources[eventSourceName] ||= { bySafeName: {}, bySelector: {}, }; - handlerFunctions.eventSources[eventSourceName].bySelector[ + indexingFunctions.eventSources[eventSourceName].bySelector[ eventData.selector ] = eventData; - handlerFunctions.eventSources[eventSourceName].bySafeName[ + indexingFunctions.eventSources[eventSourceName].bySafeName[ eventData.safeName ] = { ...eventData, fn: fn }; }); } ); - return handlerFunctions; + return indexingFunctions; }; diff --git a/packages/core/src/build/service.ts b/packages/core/src/build/service.ts index 81f4a3777..2376b589b 100644 --- a/packages/core/src/build/service.ts +++ b/packages/core/src/build/service.ts @@ -14,15 +14,15 @@ import type { Schema } from "@/schema/types"; import { buildGqlSchema } from "@/server/graphql/schema"; import { - type HandlerFunctions, - buildRawHandlerFunctions, - hydrateHandlerFunctions, -} from "./handlers"; + type IndexingFunctions, + buildRawIndexingFunctions, + hydrateIndexingFunctions, +} from "./functions"; import { readGraphqlSchema } from "./schema"; type BuildServiceEvents = { newConfig: undefined; - newHandlers: { handlers: HandlerFunctions }; + newIndexingFunctions: { indexingFunctions: IndexingFunctions }; newSchema: { schema: Schema; graphqlSchema: GraphQLSchema }; }; @@ -88,38 +88,38 @@ export class BuildService extends Emittery { if (filePath === this.common.options.schemaFile) { this.buildSchema(); } else { - await this.buildHandlers(); + await this.buildIndexingFunctions(); } } }); } - async buildHandlers() { + async buildIndexingFunctions() { try { - const rawHandlerFunctions = await buildRawHandlerFunctions({ + const rawIndexingFunctions = await buildRawIndexingFunctions({ options: this.common.options, }); - const handlers = hydrateHandlerFunctions({ - rawHandlerFunctions, + const indexingFunctions = hydrateIndexingFunctions({ + rawIndexingFunctions, logFilters: this.logFilters, factories: this.factories, }); - if (Object.values(handlers.eventSources).length === 0) { + if (Object.values(indexingFunctions.eventSources).length === 0) { this.common.logger.warn({ service: "build", - msg: "No event handler functions found", + msg: "No indexing functions found", }); } - this.emit("newHandlers", { handlers }); + this.emit("newIndexingFunctions", { indexingFunctions }); } catch (error_) { const error = error_ as Error; - // TODO: Build the UserError object within readHandlers, check instanceof, + // TODO: Build the UserError object within readIndexingFunctions, check instanceof, // then log/submit as-is if it's already a UserError. - const message = `Error while building handlers: ${error.message}`; + const message = `Error during build: ${error.message}`; const userError = new UserError(message, { stack: error.stack, }); @@ -147,7 +147,7 @@ export class BuildService extends Emittery { // TODO: Parse GraphQLError instances better here. // We can use the `.locations` property to build a pretty codeframe. - // TODO: Build the UserError object within readHandlers, check instanceof, + // TODO: Build the UserError object within readIndexingFunctions, check instanceof, // then log/submit as-is if it's already a UserError. const message = `Error while building schema.graphql: ${error.message}`; const userError = new UserError(message, { diff --git a/packages/core/src/codegen/event.ts b/packages/core/src/codegen/event.ts index 73f8b676e..715992e77 100644 --- a/packages/core/src/codegen/event.ts +++ b/packages/core/src/codegen/event.ts @@ -9,7 +9,7 @@ export const buildEventTypes = ({ logFilters: LogFilter[]; factories: Factory[]; }) => { - const allHandlers = [ + const allIndexingFunctions = [ ...logFilters.map((logFilter) => { return Object.values(logFilter.events) .filter((val): val is LogEventMetadata => !!val) @@ -66,13 +66,13 @@ export const buildEventTypes = ({ }), ]; - allHandlers.unshift( + allIndexingFunctions.unshift( `["setup"]: ({ context }: { context: Context; }) => Promise | any;` ); const final = ` export type AppType = { - ${allHandlers.join("")} + ${allIndexingFunctions.join("")} } `; diff --git a/packages/core/src/codegen/service.ts b/packages/core/src/codegen/service.ts index 137289b04..7d231b4aa 100644 --- a/packages/core/src/codegen/service.ts +++ b/packages/core/src/codegen/service.ts @@ -74,7 +74,7 @@ export class CodegenService extends Emittery { } - /* HANDLER TYPES */ + /* INDEXING FUNCTION TYPES */ ${buildEventTypes({ logFilters: this.logFilters, diff --git a/packages/core/src/config/abi.ts b/packages/core/src/config/abi.ts index ffbb0b275..80766415b 100644 --- a/packages/core/src/config/abi.ts +++ b/packages/core/src/config/abi.ts @@ -85,7 +85,7 @@ type SafeEventName = string; export type LogEventMetadata = { // Event name (if no overloads) or full event signature (if name is overloaded). - // This is the event name used when registering event handlers using `ponder.on("ContractName:EventName", ...)` + // This is the event name used when registering indexing functions using `ponder.on("ContractName:EventName", ...)` safeName: string; // Full event signature, e.g. `event Deposit(address indexed from,bytes32 indexed id,uint value);` signature: string; diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 5ce5f76c2..23f02e201 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -40,7 +40,7 @@ export type ResolvedConfig = { /** Maximum concurrency of RPC requests during the historical sync. Default: `10`. */ maxRpcRequestConcurrency?: number; }[]; - /** List of contracts to fetch & handle events from. Contracts defined here will be present in `context.contracts`. */ + /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ contracts?: ({ /** Contract name. Must be unique across `contracts` and `filters`. */ name: string; @@ -76,7 +76,7 @@ export type ResolvedConfig = { /** Whether to fetch & process event logs for this contract. If `false`, this contract will still be present in `context.contracts`. Default: `true`. */ isLogEventSource?: boolean; })[]; - /** List of log filters from which to fetch & handle event logs. */ + /** List of log filters from which to sync & index event logs. */ filters?: { /** Filter name. Must be unique across `contracts` and `filters`. */ name: string; diff --git a/packages/core/src/event-aggregator/service.ts b/packages/core/src/event-aggregator/service.ts index a3a2cc9be..97f0a9117 100644 --- a/packages/core/src/event-aggregator/service.ts +++ b/packages/core/src/event-aggregator/service.ts @@ -114,11 +114,11 @@ export class EventAggregatorService extends Emittery { async *getEvents({ fromTimestamp, toTimestamp, - handledEventMetadata, + indexingMetadata, }: { fromTimestamp: number; toTimestamp: number; - handledEventMetadata: { + indexingMetadata: { [eventSourceName: string]: | { bySelector: { [selector: Hex]: LogEventMetadata }; @@ -136,7 +136,7 @@ export class EventAggregatorService extends Emittery { fromBlock: logFilter.startBlock, toBlock: logFilter.endBlock, includeEventSelectors: Object.keys( - handledEventMetadata[logFilter.name]?.bySelector ?? {} + indexingMetadata[logFilter.name]?.bySelector ?? {} ) as Hex[], })), factories: this.factories.map((factory) => ({ @@ -146,7 +146,7 @@ export class EventAggregatorService extends Emittery { fromBlock: factory.startBlock, toBlock: factory.endBlock, includeEventSelectors: Object.keys( - handledEventMetadata[factory.name]?.bySelector ?? {} + indexingMetadata[factory.name]?.bySelector ?? {} ) as Hex[], })), }); @@ -163,7 +163,7 @@ export class EventAggregatorService extends Emittery { } const logEventMetadata = - handledEventMetadata[event.eventSourceName]?.bySelector[selector]; + indexingMetadata[event.eventSourceName]?.bySelector[selector]; if (!logEventMetadata) { throw new Error( `Metadata for event ${event.eventSourceName}:${selector} not found in includeEvents` diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dad8b386b..d26b8e021 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -export { PonderApp } from "@/build/handlers"; +export { PonderApp } from "@/build/functions"; export type { Config, ResolvedConfig } from "@/config/types"; export type { Block } from "@/types/block"; export type { ReadOnlyContract } from "@/types/contract"; diff --git a/packages/core/src/user-handlers/contract.test.ts b/packages/core/src/indexing/contract.test.ts similarity index 100% rename from packages/core/src/user-handlers/contract.test.ts rename to packages/core/src/indexing/contract.test.ts diff --git a/packages/core/src/user-handlers/contract.ts b/packages/core/src/indexing/contract.ts similarity index 98% rename from packages/core/src/user-handlers/contract.ts rename to packages/core/src/indexing/contract.ts index 095ec06a9..f1c7120dd 100644 --- a/packages/core/src/user-handlers/contract.ts +++ b/packages/core/src/indexing/contract.ts @@ -58,7 +58,7 @@ export function buildReadOnlyContracts({ } // If the user specified a block number, use it, otherwise use the - // block number of the current event being handled. + // block number of the current event being processed. const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(); const calldata = encodeFunctionData({ abi, args, functionName }); diff --git a/packages/core/src/user-handlers/model.ts b/packages/core/src/indexing/model.ts similarity index 100% rename from packages/core/src/user-handlers/model.ts rename to packages/core/src/indexing/model.ts diff --git a/packages/core/src/user-handlers/service.test.ts b/packages/core/src/indexing/service.test.ts similarity index 82% rename from packages/core/src/user-handlers/service.test.ts rename to packages/core/src/indexing/service.test.ts index ecc7b5069..201c8aad3 100644 --- a/packages/core/src/user-handlers/service.test.ts +++ b/packages/core/src/indexing/service.test.ts @@ -4,13 +4,13 @@ import { beforeEach, expect, test, vi } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; import { setupEventStore, setupUserStore } from "@/_test/setup"; import { publicClient } from "@/_test/utils"; -import type { HandlerFunctions } from "@/build/handlers"; +import type { IndexingFunctions } from "@/build/functions"; import { schemaHeader } from "@/build/schema"; import { LogEventMetadata } from "@/config/abi"; import { EventAggregatorService } from "@/event-aggregator/service"; import { buildSchema } from "@/schema/schema"; -import { EventHandlerService } from "./service"; +import { IndexingService } from "./service"; beforeEach((context) => setupEventStore(context)); beforeEach((context) => setupUserStore(context)); @@ -46,7 +46,7 @@ const schema = buildSchema( `) ); -const transferHandler = vi.fn(async ({ event, context }) => { +const transferIndexingFunction = vi.fn(async ({ event, context }) => { await context.entities.TransferEvent.create({ id: event.log.id, data: { @@ -59,7 +59,7 @@ const transferEventMetadata = usdcContractConfig.events[ "Transfer" ] as LogEventMetadata; -const handlers: HandlerFunctions = { +const indexingFunctions: IndexingFunctions = { _meta_: {}, eventSources: { USDC: { @@ -69,7 +69,7 @@ const handlers: HandlerFunctions = { bySafeName: { ["Transfer"]: { ...transferEventMetadata, - fn: transferHandler, + fn: transferIndexingFunction, }, }, }, @@ -118,7 +118,7 @@ beforeEach(() => { test("processEvents() calls getEvents with sequential timestamp ranges", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -127,7 +127,7 @@ test("processEvents() calls getEvents with sequential timestamp ranges", async ( logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); expect(getEvents).not.toHaveBeenCalled(); @@ -154,10 +154,10 @@ test("processEvents() calls getEvents with sequential timestamp ranges", async ( service.kill(); }); -test("processEvents() calls event handler functions with correct arguments", async (context) => { +test("processEvents() calls indexing functions with correct arguments", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -166,12 +166,12 @@ test("processEvents() calls event handler functions with correct arguments", asy logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); - expect(transferHandler).toHaveBeenCalledWith( + expect(transferIndexingFunction).toHaveBeenCalledWith( expect.objectContaining({ event: { eventSourceName: "USDC", @@ -195,7 +195,7 @@ test("processEvents() calls event handler functions with correct arguments", asy test("processEvents() model methods insert data into the user store", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -204,7 +204,7 @@ test("processEvents() model methods insert data into the user store", async (con logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); @@ -220,7 +220,7 @@ test("processEvents() model methods insert data into the user store", async (con test("processEvents() updates event count metrics", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -229,13 +229,13 @@ test("processEvents() updates event count metrics", async (context) => { logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); const matchedEventsMetric = ( - await common.metrics.ponder_handlers_matched_events.get() + await common.metrics.ponder_indexing_matched_events.get() ).values; expect(matchedEventsMetric).toMatchObject([ { labels: { eventName: "setup" }, value: 1 }, @@ -243,14 +243,14 @@ test("processEvents() updates event count metrics", async (context) => { ]); const handledEventsMetric = ( - await common.metrics.ponder_handlers_handled_events.get() + await common.metrics.ponder_indexing_handled_events.get() ).values; expect(handledEventsMetric).toMatchObject([ { labels: { eventName: "USDC:Transfer" }, value: 5 }, ]); const processedEventsMetric = ( - await common.metrics.ponder_handlers_processed_events.get() + await common.metrics.ponder_indexing_processed_events.get() ).values; expect(processedEventsMetric).toMatchObject([ { labels: { eventName: "USDC:Transfer" }, value: 1 }, @@ -262,7 +262,7 @@ test("processEvents() updates event count metrics", async (context) => { test("reset() reloads the user store", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -271,7 +271,7 @@ test("reset() reloads the user store", async (context) => { logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); @@ -283,7 +283,7 @@ test("reset() reloads the user store", async (context) => { const versionIdBeforeReset = userStore.versionId; - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); expect(userStore.versionId).not.toBe(versionIdBeforeReset); @@ -298,7 +298,7 @@ test("reset() reloads the user store", async (context) => { test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -307,20 +307,20 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); const latestProcessedTimestampMetric = ( - await common.metrics.ponder_handlers_latest_processed_timestamp.get() + await common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value; expect(latestProcessedTimestampMetric).toBe(10); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); const latestProcessedTimestampMetricAfterReset = ( - await common.metrics.ponder_handlers_latest_processed_timestamp.get() + await common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value; expect(latestProcessedTimestampMetricAfterReset).toBe(0); @@ -330,7 +330,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", test("handleReorg() reverts the user store", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -341,7 +341,7 @@ test("handleReorg() reverts the user store", async (context) => { const userStoreRevertSpy = vi.spyOn(userStore, "revert"); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); @@ -356,7 +356,7 @@ test("handleReorg() reverts the user store", async (context) => { test("handleReorg() does nothing if there is a user error", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -367,9 +367,9 @@ test("handleReorg() does nothing if there is a user error", async (context) => { const userStoreRevertSpy = vi.spyOn(userStore, "revert"); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); - transferHandler.mockImplementationOnce(() => { + transferIndexingFunction.mockImplementationOnce(() => { throw new Error("User error!"); }); @@ -386,7 +386,7 @@ test("handleReorg() does nothing if there is a user error", async (context) => { test("handleReorg() processes the correct range of events after a reorg", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -395,7 +395,7 @@ test("handleReorg() processes the correct range of events after a reorg", async logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); @@ -426,7 +426,7 @@ test("handleReorg() processes the correct range of events after a reorg", async test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", async (context) => { const { common, eventStore, userStore } = context; - const service = new EventHandlerService({ + const service = new IndexingService({ common, eventStore, userStore, @@ -435,13 +435,13 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", logFilters, }); - await service.reset({ schema, handlers }); + await service.reset({ schema, indexingFunctions }); eventAggregatorService.checkpoint = 10; await service.processEvents(); const latestProcessedTimestampMetric = ( - await common.metrics.ponder_handlers_latest_processed_timestamp.get() + await common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value; expect(latestProcessedTimestampMetric).toBe(10); @@ -451,7 +451,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", await service.handleReorg({ commonAncestorTimestamp: 6 }); const latestProcessedTimestampMetricAfterReorg = ( - await common.metrics.ponder_handlers_latest_processed_timestamp.get() + await common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value; expect(latestProcessedTimestampMetricAfterReorg).toBe(6); diff --git a/packages/core/src/user-handlers/service.ts b/packages/core/src/indexing/service.ts similarity index 75% rename from packages/core/src/user-handlers/service.ts rename to packages/core/src/indexing/service.ts index fb215b6f2..f76c3d0c5 100644 --- a/packages/core/src/user-handlers/service.ts +++ b/packages/core/src/indexing/service.ts @@ -1,7 +1,7 @@ import { E_CANCELED, Mutex } from "async-mutex"; import Emittery from "emittery"; -import type { HandlerFunctions } from "@/build/handlers"; +import type { IndexingFunctions } from "@/build/functions"; import { LogEventMetadata } from "@/config/abi"; import type { Contract } from "@/config/contracts"; import { Factory } from "@/config/factories"; @@ -26,16 +26,16 @@ import { buildReadOnlyContracts } from "./contract"; import { buildModels } from "./model"; import { getStackTrace } from "./trace"; -type EventHandlerEvents = { +type IndexingEvents = { eventsProcessed: { toTimestamp: number }; }; type SetupTask = { kind: "SETUP" }; type LogEventTask = { kind: "LOG"; event: LogEvent }; -type EventHandlerTask = SetupTask | LogEventTask; -type EventHandlerQueue = Queue; +type IndexingFunctionTask = SetupTask | LogEventTask; +type IndexingFunctionQueue = Queue; -export class EventHandlerService extends Emittery { +export class IndexingService extends Emittery { private common: Common; private userStore: UserStore; private eventAggregatorService: EventAggregatorService; @@ -47,11 +47,11 @@ export class EventHandlerService extends Emittery { private schema?: Schema; private models: Record> = {}; - private handlers?: HandlerFunctions; - private handledEventMetadata: HandlerFunctions["eventSources"] = {}; + private indexingFunctions?: IndexingFunctions; + private indexingMetadata: IndexingFunctions["eventSources"] = {}; private eventProcessingMutex: Mutex; - private queue?: EventHandlerQueue; + private queue?: IndexingFunctionQueue; private eventsProcessedToTimestamp = 0; private hasError = false; @@ -99,23 +99,23 @@ export class EventHandlerService extends Emittery { this.eventProcessingMutex.cancel(); this.common.logger.debug({ - service: "handlers", - msg: `Killed user handler service`, + service: "indexing", + msg: `Killed indexing service`, }); }; /** - * Registers a new set of handler functions and/or a new schema, cancels + * Registers a new set of indexing functions and/or a new schema, cancels * the current event processing mutex & event queue, drops and re-creates * all tables from the user store, and resets eventsProcessedToTimestamp to zero. * * Note: Caller should (probably) immediately call processEvents after this method. */ reset = async ({ - handlers: newHandlers, + indexingFunctions: newIndexingFunctions, schema: newSchema, }: { - handlers?: HandlerFunctions; + indexingFunctions?: IndexingFunctions; schema?: Schema; } = {}) => { if (newSchema) { @@ -128,47 +128,49 @@ export class EventHandlerService extends Emittery { }); } - if (newHandlers) { - this.handlers = newHandlers; - this.handledEventMetadata = this.handlers.eventSources; + if (newIndexingFunctions) { + this.indexingFunctions = newIndexingFunctions; + this.indexingMetadata = this.indexingFunctions.eventSources; } - // If either the schema or handlers have not been provided yet, + // If either the schema or indexing functions have not been provided yet, // we're not ready to process events. Just return early. - if (!this.schema || !this.handlers) return; + if (!this.schema || !this.indexingFunctions) return; // Cancel all pending calls to processEvents and reset the mutex. this.eventProcessingMutex.cancel(); this.eventProcessingMutex = new Mutex(); - // Pause the old queue, (maybe) wait for the current event handler to finish, - // then create a new queue using the new handlers. + // Pause the old queue, (maybe) wait for the current indexing function to finish, + // then create a new queue using the new indexing functions. this.queue?.clear(); this.queue?.pause(); await this.queue?.onIdle(); - this.queue = this.createEventQueue({ handlers: this.handlers }); + this.queue = this.createEventQueue({ + indexingFunctions: this.indexingFunctions, + }); this.common.logger.debug({ - service: "handlers", + service: "indexing", msg: `Paused event queue (versionId=${this.userStore.versionId})`, }); this.hasError = false; - this.common.metrics.ponder_handlers_has_error.set(0); + this.common.metrics.ponder_indexing_has_error.set(0); - this.common.metrics.ponder_handlers_matched_events.reset(); - this.common.metrics.ponder_handlers_handled_events.reset(); - this.common.metrics.ponder_handlers_processed_events.reset(); + this.common.metrics.ponder_indexing_matched_events.reset(); + this.common.metrics.ponder_indexing_handled_events.reset(); + this.common.metrics.ponder_indexing_processed_events.reset(); await this.userStore.reload({ schema: this.schema }); this.common.logger.debug({ - service: "handlers", + service: "indexing", msg: `Reset user store (versionId=${this.userStore.versionId})`, }); // When we call userStore.reload() above, the user store is dropped. // Set the latest processed timestamp to zero accordingly. this.eventsProcessedToTimestamp = 0; - this.common.metrics.ponder_handlers_latest_processed_timestamp.set(0); + this.common.metrics.ponder_indexing_latest_processed_timestamp.set(0); }; /** @@ -201,7 +203,7 @@ export class EventHandlerService extends Emittery { if (this.eventsProcessedToTimestamp <= commonAncestorTimestamp) { // No unsafe events have been processed, so no need to revert (case 1 & case 2). this.common.logger.debug({ - service: "handlers", + service: "indexing", msg: `No unsafe events were detected while reconciling a reorg, no-op`, }); } else { @@ -212,16 +214,16 @@ export class EventHandlerService extends Emittery { }); this.eventsProcessedToTimestamp = commonAncestorTimestamp; - this.common.metrics.ponder_handlers_latest_processed_timestamp.set( + this.common.metrics.ponder_indexing_latest_processed_timestamp.set( commonAncestorTimestamp ); // Note: There's currently no way to know how many events are "thrown out" // during the reorg reconciliation, so the event count metrics - // (e.g. ponder_handlers_processed_events) will be slightly inflated. + // (e.g. ponder_indexing_processed_events) will be slightly inflated. this.common.logger.debug({ - service: "handlers", + service: "indexing", msg: `Reverted user store to safe timestamp ${commonAncestorTimestamp}`, }); } @@ -266,12 +268,12 @@ export class EventHandlerService extends Emittery { // If no events have been added yet, add the setup event & associated metrics. if (this.eventsProcessedToTimestamp === 0) { - this.common.metrics.ponder_handlers_matched_events.inc({ + this.common.metrics.ponder_indexing_matched_events.inc({ eventName: "setup", }); - if (this.handlers?._meta_.setup) { + if (this.indexingFunctions?._meta_.setup) { this.queue.addTask({ kind: "SETUP" }); - this.common.metrics.ponder_handlers_handled_events.inc({ + this.common.metrics.ponder_indexing_handled_events.inc({ eventName: "setup", }); } @@ -280,7 +282,7 @@ export class EventHandlerService extends Emittery { const iterator = this.eventAggregatorService.getEvents({ fromTimestamp, toTimestamp, - handledEventMetadata: this.handledEventMetadata, + indexingMetadata: this.indexingMetadata, }); let pageIndex = 0; @@ -288,7 +290,7 @@ export class EventHandlerService extends Emittery { for await (const page of iterator) { const { events, metadata } = page; - // Increment the metrics for the total number of matching & handled events in this timestamp range. + // Increment the metrics for the total number of matching & indexed events in this timestamp range. if (pageIndex === 0) { metadata.counts.forEach(({ eventSourceName, selector, count }) => { const safeName = Object.values({ @@ -303,16 +305,15 @@ export class EventHandlerService extends Emittery { if (!safeName) return; const isHandled = - !!this.handlers?.eventSources[eventSourceName]?.bySelector?.[ - selector - ]; + !!this.indexingFunctions?.eventSources[eventSourceName] + ?.bySelector?.[selector]; - this.common.metrics.ponder_handlers_matched_events.inc( + this.common.metrics.ponder_indexing_matched_events.inc( { eventName: `${eventSourceName}:${safeName}` }, count ); if (isHandled) { - this.common.metrics.ponder_handlers_handled_events.inc( + this.common.metrics.ponder_indexing_handled_events.inc( { eventName: `${eventSourceName}:${safeName}` }, count ); @@ -340,7 +341,7 @@ export class EventHandlerService extends Emittery { if (events.length > 0) { this.common.logger.info({ - service: "handlers", + service: "indexing", msg: `Processed ${ events.length === 1 ? "1 event" : `${events.length} events` } (up to ${formatShortDate(metadata.pageEndsAtTimestamp)})`, @@ -353,9 +354,9 @@ export class EventHandlerService extends Emittery { this.emit("eventsProcessed", { toTimestamp }); this.eventsProcessedToTimestamp = toTimestamp; - // Note that this happens both here and in the log event handler function. + // Note that this happens both here and in the log event indexing function. // They must also happen here to handle the case where no events were processed. - this.common.metrics.ponder_handlers_latest_processed_timestamp.set( + this.common.metrics.ponder_indexing_latest_processed_timestamp.set( toTimestamp ); }); @@ -366,41 +367,45 @@ export class EventHandlerService extends Emittery { } }; - private createEventQueue = ({ handlers }: { handlers: HandlerFunctions }) => { + private createEventQueue = ({ + indexingFunctions, + }: { + indexingFunctions: IndexingFunctions; + }) => { const context = { contracts: this.readOnlyContracts, entities: this.models, }; - const eventHandlerWorker: Worker = async ({ + const indexingFunctionWorker: Worker = async ({ task, queue, }) => { - // This is a hack to ensure that the eventsProcessed handler is called and updates + // This is a hack to ensure that the eventsProcessed method is called and updates // the UI when using SQLite. It also allows the process to GC and handle SIGINT events. // It does, however, slow down event processing a bit. await wait(0); switch (task.kind) { case "SETUP": { - const setupHandler = handlers._meta_.setup?.fn; - if (!setupHandler) return; + const setupFunction = indexingFunctions._meta_.setup?.fn; + if (!setupFunction) return; try { this.common.logger.trace({ - service: "handlers", - msg: `Started handler (event="setup")`, + service: "indexing", + msg: `Started indexing function (event="setup")`, }); // Running user code here! - await setupHandler({ context }); + await setupFunction({ context }); this.common.logger.trace({ - service: "handlers", - msg: `Completed handler (event="setup")`, + service: "indexing", + msg: `Completed indexing function (event="setup")`, }); - this.common.metrics.ponder_handlers_processed_events.inc({ + this.common.metrics.ponder_indexing_processed_events.inc({ eventName: "setup", }); } catch (error_) { @@ -408,17 +413,17 @@ export class EventHandlerService extends Emittery { queue.clear(); this.hasError = true; - this.common.metrics.ponder_handlers_has_error.set(1); + this.common.metrics.ponder_indexing_has_error.set(1); this.common.logger.trace({ - service: "handlers", - msg: `Failed while running handler (event="setup")`, + service: "indexing", + msg: `Failed while running indexing function (event="setup")`, }); const error = error_ as Error; const trace = getStackTrace(error, this.common.options); - const message = `Error while handling "setup" event: ${error.message}`; + const message = `Error while processing "setup" event: ${error.message}`; const userError = new UserError(message, { stack: trace, @@ -426,7 +431,7 @@ export class EventHandlerService extends Emittery { }); this.common.logger.error({ - service: "handlers", + service: "indexing", error: userError, }); this.common.errors.submitUserError({ error: userError }); @@ -437,28 +442,27 @@ export class EventHandlerService extends Emittery { case "LOG": { const event = task.event; - const handlerData = - this.handlers?.eventSources[event.eventSourceName].bySafeName[ - event.eventName - ]; - if (!handlerData) + const indexingMetadata = + this.indexingFunctions?.eventSources[event.eventSourceName] + .bySafeName[event.eventName]; + if (!indexingMetadata) throw new Error( - `Internal: Handler not found for event source ${event.eventSourceName}` + `Internal: Indexing function not found for event source ${event.eventSourceName}` ); // This enables contract calls occurring within the - // handler code to use the event block number by default. + // user code to use the event block number by default. this.currentEventBlockNumber = event.block.number; this.currentEventTimestamp = Number(event.block.timestamp); try { this.common.logger.trace({ - service: "handlers", - msg: `Started handler (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, + service: "indexing", + msg: `Started indexing function (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, }); // Running user code here! - await handlerData.fn({ + await indexingMetadata.fn({ event: { ...event, name: event.eventName, @@ -467,25 +471,25 @@ export class EventHandlerService extends Emittery { }); this.common.logger.trace({ - service: "handlers", - msg: `Completed handler (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, + service: "indexing", + msg: `Completed indexing function (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, }); } catch (error_) { // Remove all remaining tasks from the queue. queue.clear(); this.hasError = true; - this.common.metrics.ponder_handlers_has_error.set(1); + this.common.metrics.ponder_indexing_has_error.set(1); this.common.logger.trace({ - service: "handlers", - msg: `Failed while running handler (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, + service: "indexing", + msg: `Failed while running indexing function (event="${event.eventSourceName}:${event.eventName}", block=${event.block.number})`, }); const error = error_ as Error; const trace = getStackTrace(error, this.common.options); - const message = `Error while handling "${event.eventSourceName}:${ + const message = `Error while processing "${event.eventSourceName}:${ event.eventName }" event at block ${Number(event.block.number)}: ${error.message}`; @@ -498,16 +502,16 @@ export class EventHandlerService extends Emittery { }); this.common.logger.error({ - service: "handlers", + service: "indexing", error: userError, }); this.common.errors.submitUserError({ error: userError }); } - this.common.metrics.ponder_handlers_processed_events.inc({ + this.common.metrics.ponder_indexing_processed_events.inc({ eventName: `${event.eventSourceName}:${event.eventName}`, }); - this.common.metrics.ponder_handlers_latest_processed_timestamp.set( + this.common.metrics.ponder_indexing_latest_processed_timestamp.set( this.currentEventTimestamp ); @@ -517,7 +521,7 @@ export class EventHandlerService extends Emittery { }; const queue = createQueue({ - worker: eventHandlerWorker, + worker: indexingFunctionWorker, context: undefined, options: { concurrency: 1, diff --git a/packages/core/src/user-handlers/trace.ts b/packages/core/src/indexing/trace.ts similarity index 100% rename from packages/core/src/user-handlers/trace.ts rename to packages/core/src/indexing/trace.ts diff --git a/packages/core/src/metrics/service.ts b/packages/core/src/metrics/service.ts index 1b92a3a26..3edc173e8 100644 --- a/packages/core/src/metrics/service.ts +++ b/packages/core/src/metrics/service.ts @@ -33,11 +33,11 @@ export class MetricsService { "network" | "method" >; - ponder_handlers_matched_events: prometheus.Gauge<"eventName">; - ponder_handlers_handled_events: prometheus.Gauge<"eventName">; - ponder_handlers_processed_events: prometheus.Gauge<"eventName">; - ponder_handlers_has_error: prometheus.Gauge; - ponder_handlers_latest_processed_timestamp: prometheus.Gauge; + ponder_indexing_matched_events: prometheus.Gauge<"eventName">; + ponder_indexing_handled_events: prometheus.Gauge<"eventName">; + ponder_indexing_processed_events: prometheus.Gauge<"eventName">; + ponder_indexing_has_error: prometheus.Gauge; + ponder_indexing_latest_processed_timestamp: prometheus.Gauge; ponder_server_port: prometheus.Gauge; ponder_server_request_size: prometheus.Histogram< @@ -122,31 +122,31 @@ export class MetricsService { registers: [this.registry], }); - this.ponder_handlers_matched_events = new prometheus.Gauge({ - name: "ponder_handlers_matched_events", + this.ponder_indexing_matched_events = new prometheus.Gauge({ + name: "ponder_indexing_matched_events", help: "Number of available events for all log filters", labelNames: ["eventName"] as const, registers: [this.registry], }); - this.ponder_handlers_handled_events = new prometheus.Gauge({ - name: "ponder_handlers_handled_events", - help: "Number of available events for which there is a handler function registered", + this.ponder_indexing_handled_events = new prometheus.Gauge({ + name: "ponder_indexing_handled_events", + help: "Number of available events for which there is an indexing function registered", labelNames: ["eventName"] as const, registers: [this.registry], }); - this.ponder_handlers_processed_events = new prometheus.Gauge({ - name: "ponder_handlers_processed_events", + this.ponder_indexing_processed_events = new prometheus.Gauge({ + name: "ponder_indexing_processed_events", help: "Number of available events that have been processed", labelNames: ["eventName"] as const, registers: [this.registry], }); - this.ponder_handlers_has_error = new prometheus.Gauge({ - name: "ponder_handlers_has_error", - help: "Boolean (0 or 1) indicating if an error was encountered while running handlers", + this.ponder_indexing_has_error = new prometheus.Gauge({ + name: "ponder_indexing_has_error", + help: "Boolean (0 or 1) indicating if an error was encountered while running user code", registers: [this.registry], }); - this.ponder_handlers_latest_processed_timestamp = new prometheus.Gauge({ - name: "ponder_handlers_latest_processed_timestamp", + this.ponder_indexing_latest_processed_timestamp = new prometheus.Gauge({ + name: "ponder_indexing_latest_processed_timestamp", help: "Block timestamp of the latest processed event", registers: [this.registry], }); diff --git a/packages/core/src/ui/HandlersBar.tsx b/packages/core/src/ui/IndexingBar.tsx similarity index 65% rename from packages/core/src/ui/HandlersBar.tsx rename to packages/core/src/ui/IndexingBar.tsx index 2a56bea8c..608e4f869 100644 --- a/packages/core/src/ui/HandlersBar.tsx +++ b/packages/core/src/ui/IndexingBar.tsx @@ -6,25 +6,25 @@ import { formatShortDate } from "@/utils/date"; import type { UiState } from "./app"; import { ProgressBar } from "./ProgressBar"; -export const HandlersBar = ({ ui }: { ui: UiState }) => { +export const IndexingBar = ({ ui }: { ui: UiState }) => { const completionRate = - ui.handlersCurrent / Math.max(ui.handlersHandledTotal, 1); + ui.processedEventCount / Math.max(ui.totalMatchedEventCount, 1); const completionDecimal = Math.round(completionRate * 1000) / 10; const completionText = Number.isInteger(completionDecimal) && completionDecimal < 100 ? `${completionDecimal}.0%` : `${completionDecimal}%`; - const isStarted = ui.handlersTotal > 0; + const isStarted = ui.totalEventCount > 0; const isHistoricalSyncComplete = ui.isHistoricalSyncComplete; - const isUpToDate = ui.handlersCurrent === ui.handlersHandledTotal; + const isUpToDate = ui.processedEventCount === ui.totalMatchedEventCount; const titleText = () => { if (!isStarted) return (not started); if (!isHistoricalSyncComplete || !isUpToDate) { return ( - (up to {formatShortDate(ui.handlersToTimestamp)}) + (up to {formatShortDate(ui.eventsProcessedToTimestamp)}) ); } @@ -37,17 +37,17 @@ export const HandlersBar = ({ ui }: { ui: UiState }) => { return ( {" "} - | {ui.handlersCurrent}/ - {"?".repeat(ui.handlersCurrent.toString().length)} events ( - {ui.handlersTotal} total) + | {ui.processedEventCount}/ + {"?".repeat(ui.processedEventCount.toString().length)} events ( + {ui.totalEventCount} total) ); } return ( {" "} - | {ui.handlersCurrent}/{ui.handlersHandledTotal} events ( - {ui.handlersTotal} total) + | {ui.processedEventCount}/{ui.totalMatchedEventCount} events ( + {ui.totalEventCount} total) ); }; @@ -55,13 +55,13 @@ export const HandlersBar = ({ ui }: { ui: UiState }) => { return ( - Event handlers + Indexing {titleText()} {" "} diff --git a/packages/core/src/ui/app.tsx b/packages/core/src/ui/app.tsx index c53d6cfce..f3b43ec34 100644 --- a/packages/core/src/ui/app.tsx +++ b/packages/core/src/ui/app.tsx @@ -4,8 +4,8 @@ import React from "react"; import { Factory } from "@/config/factories"; import type { LogFilter } from "@/config/logFilters"; -import { HandlersBar } from "./HandlersBar"; import { HistoricalBar } from "./HistoricalBar"; +import { IndexingBar } from "./IndexingBar"; export type UiState = { port: number; @@ -14,14 +14,13 @@ export type UiState = { string, { rate: number; eta?: number } >; - isHistoricalSyncComplete: boolean; - handlerError: boolean; - handlersCurrent: number; - handlersTotal: number; - handlersHandledTotal: number; - handlersToTimestamp: number; + indexingError: boolean; + processedEventCount: number; + totalEventCount: number; + totalMatchedEventCount: number; + eventsProcessedToTimestamp: number; networks: string[]; }; @@ -40,11 +39,11 @@ export const buildUiState = ({ isHistoricalSyncComplete: false, - handlerError: false, - handlersCurrent: 0, - handlersTotal: 0, - handlersHandledTotal: 0, - handlersToTimestamp: 0, + indexingError: false, + processedEventCount: 0, + totalEventCount: 0, + totalMatchedEventCount: 0, + eventsProcessedToTimestamp: 0, networks: [], }; @@ -68,12 +67,12 @@ const App = (ui: UiState) => { port, historicalSyncEventSourceStats, isHistoricalSyncComplete, - handlersCurrent, - handlerError, + processedEventCount, + indexingError, networks, } = ui; - if (handlerError) { + if (indexingError) { return ( @@ -114,7 +113,7 @@ const App = (ui: UiState) => { )} - + {networks.length > 0 && ( @@ -130,7 +129,7 @@ const App = (ui: UiState) => { )} - {handlersCurrent > 0 && ( + {processedEventCount > 0 && ( GraphQL diff --git a/packages/core/src/ui/service.ts b/packages/core/src/ui/service.ts index fdf906284..301c64de0 100644 --- a/packages/core/src/ui/service.ts +++ b/packages/core/src/ui/service.ts @@ -86,27 +86,27 @@ export class UiService { this.ui.networks = connectedNetworks; - // Handlers - const matchedEvents = ( - await this.common.metrics.ponder_handlers_matched_events.get() + // Indexing + const matchedEventCount = ( + await this.common.metrics.ponder_indexing_matched_events.get() ).values.reduce((a, v) => a + v.value, 0); - const handledEvents = ( - await this.common.metrics.ponder_handlers_handled_events.get() + const totalEventCount = ( + await this.common.metrics.ponder_indexing_handled_events.get() ).values.reduce((a, v) => a + v.value, 0); - const processedEvents = ( - await this.common.metrics.ponder_handlers_processed_events.get() + const processedEventCount = ( + await this.common.metrics.ponder_indexing_processed_events.get() ).values.reduce((a, v) => a + v.value, 0); const latestProcessedTimestamp = ( - await this.common.metrics.ponder_handlers_latest_processed_timestamp.get() + await this.common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value ?? 0; - this.ui.handlersTotal = matchedEvents; - this.ui.handlersHandledTotal = handledEvents; - this.ui.handlersCurrent = processedEvents; - this.ui.handlersToTimestamp = latestProcessedTimestamp; + this.ui.totalMatchedEventCount = matchedEventCount; + this.ui.totalEventCount = totalEventCount; + this.ui.processedEventCount = processedEventCount; + this.ui.eventsProcessedToTimestamp = latestProcessedTimestamp; // Errors - this.ui.handlerError = this.common.errors.hasUserError; + this.ui.indexingError = this.common.errors.hasUserError; // Server const port = (await this.common.metrics.ponder_server_port.get()) diff --git a/packages/core/src/user-store/postgres/store.ts b/packages/core/src/user-store/postgres/store.ts index a3661a47e..859dc8c6c 100644 --- a/packages/core/src/user-store/postgres/store.ts +++ b/packages/core/src/user-store/postgres/store.ts @@ -447,8 +447,8 @@ export class PostgresUserStore implements UserStore { const instance = await this.db.transaction().execute(async (tx) => { // If the latest version is effective from the delete timestamp, // then delete the instance in place. It "never existed". - // This needs to be done first, because an update() earlier in the handler - // call would have created a new version with the delete timestamp. + // This needs to be done first, because an update() earlier in the + // indexing function would have created a new version with the delete timestamp. // Attempting to update first would result in a constraint violation. let deletedInstance = await tx .deleteFrom(tableName) diff --git a/packages/core/src/user-store/sqlite/store.ts b/packages/core/src/user-store/sqlite/store.ts index 00bff13d3..4092d5644 100644 --- a/packages/core/src/user-store/sqlite/store.ts +++ b/packages/core/src/user-store/sqlite/store.ts @@ -428,8 +428,8 @@ export class SqliteUserStore implements UserStore { const instance = await this.db.transaction().execute(async (tx) => { // If the latest version is effective from the delete timestamp, // then delete the instance in place. It "never existed". - // This needs to be done first, because an update() earlier in the handler - // call would have created a new version with the delete timestamp. + // This needs to be done first, because an update() earlier in the + // indexing function would have created a new version with the delete timestamp. // Attempting to update first would result in a constraint violation. let deletedInstance = await tx .deleteFrom(tableName) diff --git a/packages/create-ponder/README.md b/packages/create-ponder/README.md index 96ba24377..5044809fa 100644 --- a/packages/create-ponder/README.md +++ b/packages/create-ponder/README.md @@ -26,14 +26,15 @@ Join [Ponder's telegram chat](https://t.me/ponder_sh) for support, feedback, and ✅  Supports all Ethereum-based blockchains, including test nodes like [Anvil](https://book.getfoundry.sh/anvil)
✅  Index events from multiple chains in the same app
✅  Reconciles chain reorganization
-🏗️  Transaction call event handlers
-🏗️  Support for factory contracts like Uniswap V2/V3
+✅  Factory contracts
+🏗️  Process transactions calls (in addition to logs)
+🏗️  Run effects (e.g. send an API request) in indexing code
## Quickstart ### 1. Run `create-ponder` -You will be asked for a project name, and if you are using an Etherscan or Graph Protocol [template](https://ponder.sh/api-reference/create-ponder) (recommended). +You will be asked for a project name, and if you are using a [template](https://ponder.sh/api-reference/create-ponder#templates) (recommended). Then, the CLI will create a project directory, install dependencies, and initialize a git repository. ```bash npm init ponder@latest @@ -45,7 +46,7 @@ yarn create ponder ### 2. Start the development server -The development server automatically reloads your app when you save changes in any project file, and prints `console.log` statements and errors in your code. +Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. First, `cd` into your project directory, then start the server. ```bash npm run dev @@ -57,7 +58,7 @@ yarn dev ### 3. Add contracts & networks -Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the handler functions you write. +Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the indexing functions you write. ```ts // ponder.config.ts @@ -85,7 +86,7 @@ export const config = { ### 4. Define your schema -The `schema.graphql` file specifies the shape of your application's data. +The `schema.graphql` file contains a model of your application data. The entity types defined here correspond to database tables. ```ts // schema.graphql @@ -98,9 +99,9 @@ type EnsName @entity { } ``` -### 5. Write event handlers +### 5. Write indexing functions -Use event handler functions to convert raw blockchain events into application data. +Files in the `src/` directory contain **indexing functions**, which are TypeScript functions that process a contract event. The purpose of these functions is to insert data into the entity store. ```ts // src/BaseRegistrar.ts @@ -122,9 +123,11 @@ ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => { }); ``` +See the [create & update entities](https://ponder.sh/guides/create-update-entities) docs for a detailed guide on writing indexing functions. + ### 6. Query the GraphQL API -Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API will serve the data that you inserted in your event handler functions. +Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API serves the data that you inserted in your indexing functions. ```ts { diff --git a/packages/create-ponder/src/index.ts b/packages/create-ponder/src/index.ts index 90bd2804b..38f6d8f45 100644 --- a/packages/create-ponder/src/index.ts +++ b/packages/create-ponder/src/index.ts @@ -88,7 +88,7 @@ export const run = async ( } } - // Write the handler ts files. + // Write the indexing function files. config.contracts.forEach((contract) => { let abi: Abi; if (Array.isArray(contract.abi)) { @@ -110,7 +110,7 @@ export const run = async ( const eventNamesToWrite = abiEvents.map((event) => event.name).slice(0, 2); - const handlerFileContents = ` + const indexingFunctionFileContents = ` import { ponder } from '@/generated' ${eventNamesToWrite @@ -125,7 +125,7 @@ export const run = async ( writeFileSync( path.join(rootDir, `./src/${contract.name}.ts`), - prettier.format(handlerFileContents, { parser: "typescript" }) + prettier.format(indexingFunctionFileContents, { parser: "typescript" }) ); }); diff --git a/packages/create-ponder/src/templates/basic.ts b/packages/create-ponder/src/templates/basic.ts index 3d0c4ea47..59ed635ea 100644 --- a/packages/create-ponder/src/templates/basic.ts +++ b/packages/create-ponder/src/templates/basic.ts @@ -13,7 +13,7 @@ export const fromBasic = ({ rootDir }: { rootDir: string }) => { const schemaGraphqlFileContents = ` # The entity types defined below map to database tables. - # The functions you write as event handlers inside the \`src/\` directory are responsible for creating and updating records in those tables. + # The functions you write in the \`src/\` directory are responsible for creating and updating records in these tables. # Your schema will be more flexible and powerful if it accurately models the logical relationships in your application's domain. # Visit the [documentation](https://ponder.sh/guides/design-your-schema) or the [\`examples/\`](https://github.com/0xOlias/ponder/tree/main/examples) directory for further guidance on designing your schema. diff --git a/packages/eslint-config-ponder/README.md b/packages/eslint-config-ponder/README.md index 37ec8817a..18b46f253 100644 --- a/packages/eslint-config-ponder/README.md +++ b/packages/eslint-config-ponder/README.md @@ -26,14 +26,15 @@ Join [Ponder's telegram chat](https://t.me/ponder_sh) for support, feedback, and ✅  Supports all Ethereum-based blockchains, including test nodes like [Anvil](https://book.getfoundry.sh/anvil)
✅  Index events from multiple chains in the same app
✅  Reconciles chain reorganization
-🏗️  Transaction call event handlers
-🏗️  Support for factory contracts like Uniswap V2/V3
+✅  Factory contracts
+🏗️  Process transactions calls (in addition to logs)
+🏗️  Run effects (e.g. send an API request) in indexing code
## Quickstart ### 1. Run `create-ponder` -You will be asked for a project name, and if you are using an Etherscan or Graph Protocol [template](https://ponder.sh/api-reference/create-ponder) (recommended). +You will be asked for a project name, and if you are using a [template](https://ponder.sh/api-reference/create-ponder#templates) (recommended). Then, the CLI will create a project directory, install dependencies, and initialize a git repository. ```bash npm init ponder@latest @@ -45,7 +46,7 @@ yarn create ponder ### 2. Start the development server -The development server automatically reloads your app when you save changes in any project file, and prints `console.log` statements and errors in your code. +Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints `console.log` statements and errors encountered while running your code. First, `cd` into your project directory, then start the server. ```bash npm run dev @@ -57,7 +58,7 @@ yarn dev ### 3. Add contracts & networks -Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the handler functions you write. +Ponder fetches event logs for the contracts added to `ponder.config.ts`, and passes those events to the indexing functions you write. ```ts // ponder.config.ts @@ -85,7 +86,7 @@ export const config = { ### 4. Define your schema -The `schema.graphql` file specifies the shape of your application's data. +The `schema.graphql` file contains a model of your application data. The entity types defined here correspond to database tables. ```ts // schema.graphql @@ -98,9 +99,9 @@ type EnsName @entity { } ``` -### 5. Write event handlers +### 5. Write indexing functions -Use event handler functions to convert raw blockchain events into application data. +Files in the `src/` directory contain **indexing functions**, which are TypeScript functions that process a contract event. The purpose of these functions is to insert data into the entity store. ```ts // src/BaseRegistrar.ts @@ -122,9 +123,11 @@ ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => { }); ``` +See the [create & update entities](https://ponder.sh/guides/create-update-entities) docs for a detailed guide on writing indexing functions. + ### 6. Query the GraphQL API -Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API will serve the data that you inserted in your event handler functions. +Ponder automatically generates a frontend-ready GraphQL API based on your project's `schema.graphql`. The API serves the data that you inserted in your indexing functions. ```ts { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd362b865..09e1d6cb9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,11 +92,11 @@ importers: specifier: ^13.4.12 version: 13.4.12(@babel/core@7.21.0)(react-dom@18.2.0)(react@18.2.0) nextra: - specifier: ^2.10.0 - version: 2.10.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) + specifier: ^2.13.2 + version: 2.13.2(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) nextra-theme-docs: - specifier: ^2.10.0 - version: 2.10.0(next@13.4.12)(nextra@2.10.0)(react-dom@18.2.0)(react@18.2.0) + specifier: ^2.13.2 + version: 2.13.2(next@13.4.12)(nextra@2.13.2)(react-dom@18.2.0)(react@18.2.0) react: specifier: ^18.1.0 version: 18.2.0 @@ -2857,8 +2857,8 @@ packages: '@types/react': 18.0.25 react: 18.2.0 - /@napi-rs/simple-git-android-arm-eabi@0.1.8: - resolution: {integrity: sha512-JJCejHBB1G6O8nxjQLT4quWCcvLpC3oRdJJ9G3MFYSCoYS8i1bWCWeU+K7Br+xT+D6s1t9q8kNJAwJv9Ygpi0g==} + /@napi-rs/simple-git-android-arm-eabi@0.1.9: + resolution: {integrity: sha512-9D4JnfePMpgL4pg9aMUX7/TIWEUQ+Tgx8n3Pf8TNCMGjUbImJyYsDSLJzbcv9wH7srgn4GRjSizXFJHAPjzEug==} engines: {node: '>= 10'} cpu: [arm] os: [android] @@ -2866,8 +2866,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-android-arm64@0.1.8: - resolution: {integrity: sha512-mraHzwWBw3tdRetNOS5KnFSjvdAbNBnjFLA8I4PwTCPJj3Q4txrigcPp2d59cJ0TC51xpnPXnZjYdNwwSI9g6g==} + /@napi-rs/simple-git-android-arm64@0.1.9: + resolution: {integrity: sha512-Krilsw0gPrrASZzudNEl9pdLuNbhoTK0j7pUbfB8FRifpPdFB/zouwuEm0aSnsDXN4ftGrmGG82kuiR/2MeoPg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] @@ -2875,8 +2875,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-darwin-arm64@0.1.8: - resolution: {integrity: sha512-ufy/36eI/j4UskEuvqSH7uXtp3oXeLDmjQCfKJz3u5Vx98KmOMKrqAm2H81AB2WOtCo5mqS6PbBeUXR8BJX8lQ==} + /@napi-rs/simple-git-darwin-arm64@0.1.9: + resolution: {integrity: sha512-H/F09nDgYjv4gcFrZBgdTKkZEepqt0KLYcCJuUADuxkKupmjLdecMhypXLk13AzvLW4UQI7NlLTLDXUFLyr2BA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -2884,8 +2884,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-darwin-x64@0.1.8: - resolution: {integrity: sha512-Vb21U+v3tPJNl+8JtIHHT8HGe6WZ8o1Tq3f6p+Jx9Cz71zEbcIiB9FCEMY1knS/jwQEOuhhlI9Qk7d4HY+rprA==} + /@napi-rs/simple-git-darwin-x64@0.1.9: + resolution: {integrity: sha512-jBR2xS9nVPqmHv0TWz874W0m/d453MGrMeLjB+boK5IPPLhg3AWIZj0aN9jy2Je1BGVAa0w3INIQJtBBeB6kFA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -2893,8 +2893,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-linux-arm-gnueabihf@0.1.8: - resolution: {integrity: sha512-6BPTJ7CzpSm2t54mRLVaUr3S7ORJfVJoCk2rQ8v8oDg0XAMKvmQQxOsAgqKBo9gYNHJnqrOx3AEuEgvB586BuQ==} + /@napi-rs/simple-git-linux-arm-gnueabihf@0.1.9: + resolution: {integrity: sha512-3n0+VpO4YfZxndZ0sCvsHIvsazd+JmbSjrlTRBCnJeAU1/sfos3skNZtKGZksZhjvd+3o+/GFM8L7Xnv01yggA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -2902,8 +2902,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-linux-arm64-gnu@0.1.8: - resolution: {integrity: sha512-qfESqUCAA/XoQpRXHptSQ8gIFnETCQt1zY9VOkplx6tgYk9PCeaX4B1Xuzrh3eZamSCMJFn+1YB9Ut8NwyGgAA==} + /@napi-rs/simple-git-linux-arm64-gnu@0.1.9: + resolution: {integrity: sha512-lIzf0KHU2SKC12vMrWwCtysG2Sdt31VHRPMUiz9lD9t3xwVn8qhFSTn5yDkTeG3rgX6o0p5EKalfQN5BXsJq2w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2911,8 +2911,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-linux-arm64-musl@0.1.8: - resolution: {integrity: sha512-G80BQPpaRmQpn8dJGHp4I2/YVhWDUNJwcCrJAtAdbKFDCMyCHJBln2ERL/+IEUlIAT05zK/c1Z5WEprvXEdXow==} + /@napi-rs/simple-git-linux-arm64-musl@0.1.9: + resolution: {integrity: sha512-KQozUoNXrxrB8k741ncWXSiMbjl1AGBGfZV21PANzUM8wH4Yem2bg3kfglYS/QIx3udspsT35I9abu49n7D1/w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2920,8 +2920,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-linux-x64-gnu@0.1.8: - resolution: {integrity: sha512-NI6o1sZYEf6vPtNWJAm9w8BxJt+LlSFW0liSjYe3lc3e4dhMfV240f0ALeqlwdIldRPaDFwZSJX5/QbS7nMzhw==} + /@napi-rs/simple-git-linux-x64-gnu@0.1.9: + resolution: {integrity: sha512-O/Niui5mnHPcK3iYC3ui8wgERtJWsQ3Y74W/09t0bL/3dgzGMl4oQt0qTj9dWCsnoGsIEYHPzwCBp/2vqYp/pw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2929,8 +2929,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-linux-x64-musl@0.1.8: - resolution: {integrity: sha512-wljGAEOW41er45VTiU8kXJmO480pQKzsgRCvPlJJSCaEVBbmo6XXbFIXnZy1a2J3Zyy2IOsRB4PVkUZaNuPkZQ==} + /@napi-rs/simple-git-linux-x64-musl@0.1.9: + resolution: {integrity: sha512-L9n+e8Wn3hKr3RsIdY8GaB+ry4xZ4BaGwyKExgoB8nDGQuRUY9oP6p0WA4hWfJvJnU1H6hvo36a5UFPReyBO7A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -2938,8 +2938,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-win32-arm64-msvc@0.1.8: - resolution: {integrity: sha512-QuV4QILyKPfbWHoQKrhXqjiCClx0SxbCTVogkR89BwivekqJMd9UlMxZdoCmwLWutRx4z9KmzQqokvYI5QeepA==} + /@napi-rs/simple-git-win32-arm64-msvc@0.1.9: + resolution: {integrity: sha512-Z6Ja/SZK+lMvRWaxj7wjnvSbAsGrH006sqZo8P8nxKUdZfkVvoCaAWr1r0cfkk2Z3aijLLtD+vKeXGlUPH6gGQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -2947,8 +2947,8 @@ packages: dev: false optional: true - /@napi-rs/simple-git-win32-x64-msvc@0.1.8: - resolution: {integrity: sha512-UzNS4JtjhZhZ5hRLq7BIUq+4JOwt1ThIKv11CsF1ag2l99f0123XvfEpjczKTaa94nHtjXYc2Mv9TjccBqYOew==} + /@napi-rs/simple-git-win32-x64-msvc@0.1.9: + resolution: {integrity: sha512-VAZj1UvC+R2MjKOD3I/Y7dmQlHWAYy4omhReQJRpbCf+oGCBi9CWiIduGqeYEq723nLIKdxP7XjaO0wl1NnUww==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2956,21 +2956,21 @@ packages: dev: false optional: true - /@napi-rs/simple-git@0.1.8: - resolution: {integrity: sha512-BvOMdkkofTz6lEE35itJ/laUokPhr/5ToMGlOH25YnhLD2yN1KpRAT4blW9tT8281/1aZjW3xyi73bs//IrDKA==} + /@napi-rs/simple-git@0.1.9: + resolution: {integrity: sha512-qKzDS0+VjMvVyU28px+C6zlD1HKy83NIdYzfMQWa/g/V1iG/Ic8uwrS2ihHfm7mp7X0PPrmINLiTTi6ieUIKfw==} engines: {node: '>= 10'} optionalDependencies: - '@napi-rs/simple-git-android-arm-eabi': 0.1.8 - '@napi-rs/simple-git-android-arm64': 0.1.8 - '@napi-rs/simple-git-darwin-arm64': 0.1.8 - '@napi-rs/simple-git-darwin-x64': 0.1.8 - '@napi-rs/simple-git-linux-arm-gnueabihf': 0.1.8 - '@napi-rs/simple-git-linux-arm64-gnu': 0.1.8 - '@napi-rs/simple-git-linux-arm64-musl': 0.1.8 - '@napi-rs/simple-git-linux-x64-gnu': 0.1.8 - '@napi-rs/simple-git-linux-x64-musl': 0.1.8 - '@napi-rs/simple-git-win32-arm64-msvc': 0.1.8 - '@napi-rs/simple-git-win32-x64-msvc': 0.1.8 + '@napi-rs/simple-git-android-arm-eabi': 0.1.9 + '@napi-rs/simple-git-android-arm64': 0.1.9 + '@napi-rs/simple-git-darwin-arm64': 0.1.9 + '@napi-rs/simple-git-darwin-x64': 0.1.9 + '@napi-rs/simple-git-linux-arm-gnueabihf': 0.1.9 + '@napi-rs/simple-git-linux-arm64-gnu': 0.1.9 + '@napi-rs/simple-git-linux-arm64-musl': 0.1.9 + '@napi-rs/simple-git-linux-x64-gnu': 0.1.9 + '@napi-rs/simple-git-linux-x64-musl': 0.1.9 + '@napi-rs/simple-git-win32-arm64-msvc': 0.1.9 + '@napi-rs/simple-git-win32-x64-msvc': 0.1.9 dev: false /@next/env@13.4.12: @@ -3425,8 +3425,8 @@ packages: tslib: 2.5.3 dev: false - /@theguild/remark-mermaid@0.0.4(react@18.2.0): - resolution: {integrity: sha512-C1gssw07eURtCwzXqZZdvyV/eawQ/cXfARaXIgBU9orffox+/YQ+exxmNu9v16NSGzAVsGF4qEVHvCOcCR/FpQ==} + /@theguild/remark-mermaid@0.0.5(react@18.2.0): + resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} peerDependencies: react: ^18.2.0 dependencies: @@ -3437,10 +3437,10 @@ packages: - supports-color dev: false - /@theguild/remark-npm2yarn@0.1.1: - resolution: {integrity: sha512-ZKwd/bjQ9V+pESLnu8+q8jqn15alXzJOuVckraebsXwqVBTw53Gmupiw9zCdLNHU829KTYNycJYea6m9HRLuOg==} + /@theguild/remark-npm2yarn@0.2.1: + resolution: {integrity: sha512-jUTFWwDxtLEFtGZh/TW/w30ySaDJ8atKWH8dq2/IiQF61dPrGfETpl0WxD0VdBfuLOeU14/kop466oBSRO/5CA==} dependencies: - npm-to-yarn: 2.0.0 + npm-to-yarn: 2.1.0 unist-util-visit: 5.0.0 dev: false @@ -3586,6 +3586,12 @@ packages: '@types/unist': 2.0.7 dev: false + /@types/hast@3.0.2: + resolution: {integrity: sha512-B5hZHgHsXvfCoO3xgNJvBnX7N8p86TqQeGKXcokW4XXi+qY4vxxPSFYofytvVmpFxzPv7oxDQzjg5Un5m2/xiw==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /@types/is-ci@3.0.0: resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} dependencies: @@ -3603,10 +3609,6 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/katex@0.14.0: - resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==} - dev: false - /@types/katex@0.16.1: resolution: {integrity: sha512-cwglq2A63Yk082CQk0t8LIoDhZAVgJqkumLyk3grpg3K8sevaDW//Qsspmxj9Sf+97biqt79CfAlPrvizHlP0w==} dev: false @@ -3621,6 +3623,12 @@ packages: '@types/unist': 2.0.7 dev: false + /@types/mdast@4.0.2: + resolution: {integrity: sha512-tYR83EignvhYO9iU3kDg8V28M0jqyh9zzp5GV+EO+AYnyUl3P5ltkTeJuTiFZQFz670FSb3EwT/6LQdX+UdKfw==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /@types/mdx@2.0.5: resolution: {integrity: sha512-76CqzuD6Q7LC+AtbPqrvD9AqsN0k8bsYo2bM2J8pmNldP1aIPAbzUQ7QbobyXL4eLr1wK5x8FZFe8eF/ubRuBg==} @@ -3965,6 +3973,10 @@ packages: '@typescript-eslint/types': 6.3.0 eslint-visitor-keys: 3.4.1 + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: false + /@viem/anvil@0.0.6: resolution: {integrity: sha512-OjKR/+FVwzuygXYFqP8MBal1SXG8bT2gbZwqqB0XuLw81LNBBvmE/Repm6+5kkBh4IUj0PhYdrqOsnayS14Gtg==} dependencies: @@ -5040,8 +5052,8 @@ packages: engines: {node: '>=0.8'} dev: true - /clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} dev: false @@ -5834,6 +5846,12 @@ packages: execa: 5.1.1 dev: false + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} dependencies: @@ -7685,55 +7703,75 @@ packages: minimalistic-assert: 1.0.1 dev: true - /hast-util-from-dom@4.2.0: - resolution: {integrity: sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==} + /hast-util-from-dom@5.0.0: + resolution: {integrity: sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==} dependencies: - hastscript: 7.2.0 + '@types/hast': 3.0.2 + hastscript: 8.0.0 web-namespaces: 2.0.1 dev: false - /hast-util-from-html-isomorphic@1.0.0: - resolution: {integrity: sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==} + /hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} dependencies: - '@types/hast': 2.3.5 - hast-util-from-dom: 4.2.0 - hast-util-from-html: 1.0.2 - unist-util-remove-position: 4.0.2 + '@types/hast': 3.0.2 + hast-util-from-dom: 5.0.0 + hast-util-from-html: 2.0.1 + unist-util-remove-position: 5.0.0 dev: false - /hast-util-from-html@1.0.2: - resolution: {integrity: sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==} + /hast-util-from-html@2.0.1: + resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} dependencies: - '@types/hast': 2.3.5 - hast-util-from-parse5: 7.1.2 + '@types/hast': 3.0.2 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.1 parse5: 7.1.2 - vfile: 5.3.7 - vfile-message: 3.1.4 + vfile: 6.0.1 + vfile-message: 4.0.2 dev: false - /hast-util-from-parse5@7.1.2: - resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} + /hast-util-from-parse5@8.0.1: + resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} dependencies: - '@types/hast': 2.3.5 - '@types/unist': 2.0.7 - hastscript: 7.2.0 + '@types/hast': 3.0.2 + '@types/unist': 3.0.0 + devlop: 1.1.0 + hastscript: 8.0.0 property-information: 6.2.0 - vfile: 5.3.7 - vfile-location: 4.1.0 + vfile: 6.0.1 + vfile-location: 5.0.2 web-namespaces: 2.0.1 dev: false - /hast-util-is-element@2.1.3: - resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==} + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} dependencies: - '@types/hast': 2.3.5 - '@types/unist': 2.0.7 + '@types/hast': 3.0.2 dev: false - /hast-util-parse-selector@3.1.1: - resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} dependencies: - '@types/hast': 2.3.5 + '@types/hast': 3.0.2 + dev: false + + /hast-util-raw@9.0.1: + resolution: {integrity: sha512-5m1gmba658Q+lO5uqL5YNGQWeh1MYWZbZmWrM5lncdcuiXuo5E2HT/CIOp0rLF8ksfSwiCVJ3twlgVRyTGThGA==} + dependencies: + '@types/hast': 3.0.2 + '@types/unist': 3.0.0 + '@ungap/structured-clone': 1.2.0 + hast-util-from-parse5: 8.0.1 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.0.2 + parse5: 7.1.2 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 + web-namespaces: 2.0.1 + zwitch: 2.0.4 dev: false /hast-util-to-estree@2.3.3: @@ -7758,25 +7796,37 @@ packages: - supports-color dev: false - /hast-util-to-text@3.1.2: - resolution: {integrity: sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==} + /hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} dependencies: - '@types/hast': 2.3.5 - '@types/unist': 2.0.7 - hast-util-is-element: 2.1.3 - unist-util-find-after: 4.0.1 + '@types/hast': 3.0.2 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-to-text@4.0.0: + resolution: {integrity: sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==} + dependencies: + '@types/hast': 3.0.2 + '@types/unist': 3.0.0 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 dev: false /hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} dev: false - /hastscript@7.2.0: - resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} + /hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} dependencies: - '@types/hast': 2.3.5 + '@types/hast': 3.0.2 comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 3.1.1 + hast-util-parse-selector: 4.0.0 property-information: 6.2.0 space-separated-tokens: 2.0.2 dev: false @@ -7802,6 +7852,10 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true + /html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + dev: false + /http-basic@8.1.3: resolution: {integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==} engines: {node: '>=6.0.0'} @@ -8628,8 +8682,8 @@ packages: object.assign: 4.1.4 dev: true - /katex@0.16.8: - resolution: {integrity: sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==} + /katex@0.16.9: + resolution: {integrity: sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==} hasBin: true dependencies: commander: 8.3.0 @@ -9139,6 +9193,19 @@ packages: unist-util-visit: 4.1.2 dev: false + /mdast-util-to-hast@13.0.2: + resolution: {integrity: sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og==} + dependencies: + '@types/hast': 3.0.2 + '@types/mdast': 4.0.2 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + dev: false + /mdast-util-to-markdown@1.5.0: resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} dependencies: @@ -9328,7 +9395,7 @@ packages: resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} dependencies: '@types/katex': 0.16.1 - katex: 0.16.8 + katex: 0.16.9 micromark-factory-space: 1.1.0 micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 @@ -9459,6 +9526,13 @@ packages: micromark-util-types: 1.1.0 dev: false + /micromark-util-character@2.0.1: + resolution: {integrity: sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + /micromark-util-chunked@1.1.0: resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} dependencies: @@ -9499,6 +9573,10 @@ packages: resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} dev: false + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: false + /micromark-util-events-to-acorn@1.2.3: resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} dependencies: @@ -9536,6 +9614,14 @@ packages: micromark-util-symbol: 1.1.0 dev: false + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + /micromark-util-subtokenize@1.1.0: resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} dependencies: @@ -9549,10 +9635,18 @@ packages: resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} dev: false + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: false + /micromark-util-types@1.1.0: resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} dev: false + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: false + /micromark@3.2.0: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: @@ -9918,17 +10012,18 @@ packages: - babel-plugin-macros dev: false - /nextra-theme-docs@2.10.0(next@13.4.12)(nextra@2.10.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-uXoqRoewbu0JoqQ1m67aIztWe9/nEhcSeHMimhLxZghKZxkYN0kTR5y5jmrwOHRPuJUTLL2YFwy1rvWJIZS2lw==} + /nextra-theme-docs@2.13.2(next@13.4.12)(nextra@2.13.2)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yE4umXaImp1/kf/sFciPj2+EFrNSwd9Db26hi98sIIiujzGf3+9eUgAz45vF9CwBw50FSXxm1QGRcY+slQ4xQQ==} peerDependencies: next: '>=9.5.3' - nextra: 2.10.0 + nextra: 2.13.2 react: '>=16.13.1' react-dom: '>=16.13.1' dependencies: '@headlessui/react': 1.7.15(react-dom@18.2.0)(react@18.2.0) '@popperjs/core': 2.11.6 - clsx: 1.2.1 + clsx: 2.0.0 + escape-string-regexp: 5.0.0 flexsearch: 0.7.31 focus-visible: 5.2.0 git-url-parse: 13.1.0 @@ -9937,15 +10032,15 @@ packages: next: 13.4.12(@babel/core@7.21.0)(react-dom@18.2.0)(react@18.2.0) next-seo: 6.1.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) next-themes: 0.2.1(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) - nextra: 2.10.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) + nextra: 2.13.2(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) scroll-into-view-if-needed: 3.0.10 - zod: 3.21.4 + zod: 3.22.4 dev: false - /nextra@2.10.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-euv93UnWpdth8slMRJLqMrWvCCzR/VTVH6DPrn1JW7hZS03c2lzG2q+fsiYULGiy/kFyysmlxd4Nx5KGB1Txwg==} + /nextra@2.13.2(next@13.4.12)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pIgOSXNUqTz1laxV4ChFZOU7lzJAoDHHaBPj8L09PuxrLKqU1BU/iZtXAG6bQeKCx8EPdBsoXxEuENnL9QGnGA==} engines: {node: '>=16'} peerDependencies: next: '>=9.5.3' @@ -9955,22 +10050,23 @@ packages: '@headlessui/react': 1.7.15(react-dom@18.2.0)(react@18.2.0) '@mdx-js/mdx': 2.3.0 '@mdx-js/react': 2.3.0(react@18.2.0) - '@napi-rs/simple-git': 0.1.8 - '@theguild/remark-mermaid': 0.0.4(react@18.2.0) - '@theguild/remark-npm2yarn': 0.1.1 - clsx: 1.2.1 + '@napi-rs/simple-git': 0.1.9 + '@theguild/remark-mermaid': 0.0.5(react@18.2.0) + '@theguild/remark-npm2yarn': 0.2.1 + clsx: 2.0.0 github-slugger: 2.0.0 graceful-fs: 4.2.11 gray-matter: 4.0.3 - katex: 0.16.8 + katex: 0.16.9 lodash.get: 4.4.2 next: 13.4.12(@babel/core@7.21.0)(react-dom@18.2.0)(react@18.2.0) next-mdx-remote: 4.4.1(react-dom@18.2.0)(react@18.2.0) p-limit: 3.1.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rehype-katex: 6.0.3 + rehype-katex: 7.0.0 rehype-pretty-code: 0.9.11(shiki@0.14.3) + rehype-raw: 7.0.0 remark-gfm: 3.0.1 remark-math: 5.1.1 remark-reading-time: 2.0.1 @@ -9979,7 +10075,7 @@ packages: title: 3.5.3 unist-util-remove: 4.0.0 unist-util-visit: 5.0.0 - zod: 3.21.4 + zod: 3.22.4 transitivePeerDependencies: - supports-color dev: false @@ -10055,9 +10151,9 @@ packages: path-key: 4.0.0 dev: true - /npm-to-yarn@2.0.0: - resolution: {integrity: sha512-/IbjiJ7vqbxfxJxAZ+QI9CCRjnIbvGxn5KQcSY9xHh0lMKc/Sgqmm7yp7KPmd6TiTZX5/KiSBKlkGHo59ucZbg==} - engines: {node: '>=6.0.0'} + /npm-to-yarn@2.1.0: + resolution: {integrity: sha512-2C1IgJLdJngq1bSER7K7CGFszRr9s2rijEwvENPEgI0eK9xlD3tNwDc0UJnRj7FIT2aydWm72jB88uVswAhXHA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false /nth-check@2.1.1: @@ -11114,15 +11210,16 @@ packages: jsesc: 0.5.0 dev: true - /rehype-katex@6.0.3: - resolution: {integrity: sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==} + /rehype-katex@7.0.0: + resolution: {integrity: sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==} dependencies: - '@types/hast': 2.3.5 - '@types/katex': 0.14.0 - hast-util-from-html-isomorphic: 1.0.0 - hast-util-to-text: 3.1.2 - katex: 0.16.8 - unist-util-visit: 4.1.2 + '@types/hast': 3.0.2 + '@types/katex': 0.16.1 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.0 + katex: 0.16.9 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.1 dev: false /rehype-pretty-code@0.9.11(shiki@0.14.3): @@ -11137,6 +11234,14 @@ packages: shiki: 0.14.3 dev: false + /rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + dependencies: + '@types/hast': 3.0.2 + hast-util-raw: 9.0.1 + vfile: 6.0.1 + dev: false + /remark-gfm@3.0.1: resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} dependencies: @@ -12623,11 +12728,11 @@ packages: vfile: 5.3.7 dev: false - /unist-util-find-after@4.0.1: - resolution: {integrity: sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==} + /unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} dependencies: - '@types/unist': 2.0.7 - unist-util-is: 5.2.1 + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 dev: false /unist-util-generated@2.0.1: @@ -12658,6 +12763,12 @@ packages: '@types/unist': 2.0.7 dev: false + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /unist-util-remove-position@4.0.2: resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} dependencies: @@ -12665,6 +12776,13 @@ packages: unist-util-visit: 4.1.2 dev: false + /unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + dependencies: + '@types/unist': 3.0.0 + unist-util-visit: 5.0.0 + dev: false + /unist-util-remove@4.0.0: resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} dependencies: @@ -12679,6 +12797,12 @@ packages: '@types/unist': 2.0.7 dev: false + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /unist-util-visit-parents@4.1.1: resolution: {integrity: sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==} dependencies: @@ -12826,11 +12950,11 @@ packages: extsprintf: 1.3.0 dev: true - /vfile-location@4.1.0: - resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + /vfile-location@5.0.2: + resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} dependencies: - '@types/unist': 2.0.7 - vfile: 5.3.7 + '@types/unist': 3.0.0 + vfile: 6.0.1 dev: false /vfile-matter@3.0.1: @@ -12848,6 +12972,13 @@ packages: unist-util-stringify-position: 3.0.3 dev: false + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + dev: false + /vfile@5.3.7: resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} dependencies: @@ -12857,6 +12988,14 @@ packages: vfile-message: 3.1.4 dev: false + /vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + dev: false + /viem@0.3.50(typescript@5.1.3): resolution: {integrity: sha512-s+LxCYZTR9F/qPk1/n1YDVAX9vSeVz7GraqBZWGrDuenCJxo9ArCoIceJ6ksI0WwSeNzcZ0VVbD/kWRzTxkipw==} dependencies: @@ -13326,6 +13465,10 @@ packages: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false From 980c40e3dbb4a86c11c050f69dfd0f75e38a6c96 Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:16:54 -0400 Subject: [PATCH 03/44] add all examples to ignored --- .changeset/config.json | 6 +++++- examples/with-docker/package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index a2eb8a038..ae001d666 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,7 +7,11 @@ "ponder-examples-ethfs", "ponder-examples-art-gobblers", "ponder-examples-factory-llama", - "ponder-examples-friendtech" + "ponder-examples-friendtech", + "ponder-examples-token-erc20", + "ponder-examples-token-erc721", + "ponder-examples-token-reth", + "ponder-examples-with-docker" ], "linked": [], "access": "public", diff --git a/examples/with-docker/package.json b/examples/with-docker/package.json index fb9725068..5bb368119 100644 --- a/examples/with-docker/package.json +++ b/examples/with-docker/package.json @@ -1,5 +1,5 @@ { - "name": "ponder-examples-token-erc20-with-docker", + "name": "ponder-examples-with-docker", "private": true, "scripts": { "dev:up": "docker-compose -f docker-compose-local.yml up", From 0381cb806612fe3bd5e77185b041d59ad6cef80d Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:51:02 -0400 Subject: [PATCH 04/44] test: add sort test for encoding --- packages/core/src/utils/encoding.test.ts | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/core/src/utils/encoding.test.ts b/packages/core/src/utils/encoding.test.ts index e9bbcfeda..bfad7a8fd 100644 --- a/packages/core/src/utils/encoding.test.ts +++ b/packages/core/src/utils/encoding.test.ts @@ -72,3 +72,29 @@ test("encodeAsText handles zero", () => { ); expect(decodeToBigInt(encoded)).toBe(value); }); + +test("lexicographic sort works as expected", () => { + const values = [ + EVM_MAX_UINT, + 0n, + EVM_MIN_INT, + EVM_MAX_UINT - 1n, + 1_000n, + -500n, + EVM_MIN_INT + 1n, + ]; + + const encoded = values.map(encodeAsText); + const sorted = encoded.slice().sort(); + const decoded = sorted.map(decodeToBigInt); + + expect(decoded).toMatchObject([ + EVM_MIN_INT, + EVM_MIN_INT + 1n, + -500n, + 0n, + 1_000n, + EVM_MAX_UINT - 1n, + EVM_MAX_UINT, + ]); +}); From 2f69117138ec622aa0bfc9f57758eb087846d60a Mon Sep 17 00:00:00 2001 From: kyscott18 <43524469+kyscott18@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:55:51 -0400 Subject: [PATCH 05/44] Handle custom topics for factory contracts internally (#404) * handle events for factory contracts internally * update postgres store to support factory topics * :) * fix join because factory log filters are non-null * test inclusion rules for factory log filter * log filter fragments use id instead of index * update realtime sync for factory log filters and fix bug with filter utility * fix: remove unnecessary flat --------- Co-authored-by: typedarray <90073088+0xOlias@users.noreply.github.com> --- packages/core/src/config/factories.test.ts | 25 --- packages/core/src/config/factories.ts | 7 +- .../core/src/event-store/postgres/format.ts | 4 + .../src/event-store/postgres/migrations.ts | 4 + .../core/src/event-store/postgres/store.ts | 190 ++++++++++++------ .../core/src/event-store/sqlite/format.ts | 4 + .../core/src/event-store/sqlite/migrations.ts | 4 + packages/core/src/event-store/sqlite/store.ts | 190 ++++++++++++------ packages/core/src/event-store/store.test.ts | 39 ++++ packages/core/src/historical-sync/service.ts | 1 + .../core/src/realtime-sync/filter.test.ts | 9 + packages/core/src/realtime-sync/filter.ts | 2 +- packages/core/src/realtime-sync/service.ts | 37 ++-- .../{logFilter.test.ts => fragments.test.ts} | 33 ++- packages/core/src/utils/fragments.ts | 125 ++++++++++++ packages/core/src/utils/logFilter.ts | 65 ------ 16 files changed, 502 insertions(+), 237 deletions(-) rename packages/core/src/utils/{logFilter.test.ts => fragments.test.ts} (63%) create mode 100644 packages/core/src/utils/fragments.ts delete mode 100644 packages/core/src/utils/logFilter.ts diff --git a/packages/core/src/config/factories.test.ts b/packages/core/src/config/factories.test.ts index 5b04a7a5c..e61f8bc26 100644 --- a/packages/core/src/config/factories.test.ts +++ b/packages/core/src/config/factories.test.ts @@ -3,7 +3,6 @@ import { expect, test } from "vitest"; import { buildFactoryCriteria, - buildFactoryId, getAddressFromFactoryEventLog, } from "./factories"; @@ -171,27 +170,3 @@ test("getAddressFromFactoryEventLog gets address from nonindexed parameter 3", ( expect(address).toBe("0x5a6480483462533564634b8c81aea01cf87c6ddb"); }); - -test("buildFactoryId builds id containing topic", () => { - const criteria = buildFactoryCriteria({ - address: "0xa", - event: llamaFactoryEventAbiItem, - parameter: "deployer", - }); - - expect(buildFactoryId({ chainId: 1, ...criteria })).toBe( - "1_0xa_0x00fef2d461a2fabbb523f9f42752c61336f03b17a602af52cc6c83cb8b110599_topic1" - ); -}); - -test("buildFactoryId builds id containing offset", () => { - const criteria = buildFactoryCriteria({ - address: "0xa", - event: llamaFactoryEventAbiItem, - parameter: "llamaPolicy", - }); - - expect(buildFactoryId({ chainId: 115511, ...criteria })).toBe( - "115511_0xa_0x00fef2d461a2fabbb523f9f42752c61336f03b17a602af52cc6c83cb8b110599_offset64" - ); -}); diff --git a/packages/core/src/config/factories.ts b/packages/core/src/config/factories.ts index fd0b9a481..3a373ee7e 100644 --- a/packages/core/src/config/factories.ts +++ b/packages/core/src/config/factories.ts @@ -12,6 +12,7 @@ export type FactoryCriteria = { address: Address; eventSelector: Hex; childAddressLocation: "topic1" | "topic2" | "topic3" | `offset${number}`; + topics?: (Hex | Hex[] | null)[]; }; export type Factory = { @@ -171,9 +172,3 @@ export function getAddressFromFactoryEventLog({ `Invalid child address location identifier: ${childAddressLocation}` ); } - -export function buildFactoryId( - criteria: FactoryCriteria & { chainId: number } -) { - return `${criteria.chainId}_${criteria.address}_${criteria.eventSelector}_${criteria.childAddressLocation}` as const; -} diff --git a/packages/core/src/event-store/postgres/format.ts b/packages/core/src/event-store/postgres/format.ts index 719af7280..69d29caff 100644 --- a/packages/core/src/event-store/postgres/format.ts +++ b/packages/core/src/event-store/postgres/format.ts @@ -186,6 +186,10 @@ type FactoriesTable = { address: Hex; eventSelector: Hex; childAddressLocation: `topic${1 | 2 | 3}` | `offset${number}`; + topic0: Hex | null; + topic1: Hex | null; + topic2: Hex | null; + topic3: Hex | null; }; type FactoryLogFilterIntervalsTable = { diff --git a/packages/core/src/event-store/postgres/migrations.ts b/packages/core/src/event-store/postgres/migrations.ts index 039b14f90..2b556ac3d 100644 --- a/packages/core/src/event-store/postgres/migrations.ts +++ b/packages/core/src/event-store/postgres/migrations.ts @@ -328,6 +328,10 @@ const migrations: Record = { .addColumn("address", "varchar(42)", (col) => col.notNull()) .addColumn("eventSelector", "varchar(66)", (col) => col.notNull()) .addColumn("childAddressLocation", "text", (col) => col.notNull()) // `topic${number}` or `offset${number}` + .addColumn("topic0", "varchar(66)") + .addColumn("topic1", "varchar(66)") + .addColumn("topic2", "varchar(66)") + .addColumn("topic3", "varchar(66)") .execute(); await db.schema .createTable("factoryLogFilterIntervals") diff --git a/packages/core/src/event-store/postgres/store.ts b/packages/core/src/event-store/postgres/store.ts index bbf9e4651..3075812ac 100644 --- a/packages/core/src/event-store/postgres/store.ts +++ b/packages/core/src/event-store/postgres/store.ts @@ -10,14 +10,17 @@ import { import type { Pool } from "pg"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import { type FactoryCriteria, buildFactoryId } from "@/config/factories"; +import { type FactoryCriteria } from "@/config/factories"; import type { LogFilterCriteria } from "@/config/logFilters"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; import type { NonNull } from "@/types/utils"; +import { + buildFactoryFragments, + buildLogFilterFragments, +} from "@/utils/fragments"; import { intervalIntersectionMany, intervalUnion } from "@/utils/interval"; -import { buildLogFilterFragments } from "@/utils/logFilter"; import { range } from "@/utils/range"; import type { EventStore } from "../store"; @@ -185,19 +188,14 @@ export class PostgresEventStore implements EventStore { }) ); - const fragmentsWithIdx = fragments.map((f, idx) => ({ - idx, - ...f, - })); - const intervals = await this.db .with( - "logFilterFragments(fragmentIndex, fragmentAddress, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", + "logFilterFragments(fragmentId, fragmentAddress, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", () => sql`( values ${sql.join( - fragmentsWithIdx.map( + fragments.map( (f) => - sql`( ${sql.val(f.idx)}, ${sql.val(f.address)}, ${sql.val( + sql`( ${sql.val(f.id)}, ${sql.val(f.address)}, ${sql.val( f.topic0 )}, ${sql.val(f.topic1)}, ${sql.val(f.topic2)}, ${sql.val( f.topic3 @@ -226,19 +224,19 @@ export class PostgresEventStore implements EventStore { return baseJoin; }) - .select(["fragmentIndex", "startBlock", "endBlock"]) + .select(["fragmentId", "startBlock", "endBlock"]) .where("chainId", "=", chainId) .execute(); const intervalsByFragment = intervals.reduce((acc, cur) => { - const { fragmentIndex, ...rest } = cur; - acc[fragmentIndex] ||= []; - acc[fragmentIndex].push(rest); + const { fragmentId, ...rest } = cur; + acc[fragmentId] ||= []; + acc[fragmentId].push(rest); return acc; - }, {} as Record); + }, {} as Record); - const fragmentIntervals = fragmentsWithIdx.map((f) => { - return (intervalsByFragment[f.idx] ?? []).map( + const fragmentIntervals = fragments.map((f) => { + return (intervalsByFragment[f.id] ?? []).map( (r) => [Number(r.startBlock), Number(r.endBlock)] satisfies [number, number] ); @@ -373,46 +371,119 @@ export class PostgresEventStore implements EventStore { chainId: number; factory: FactoryCriteria; }) => { - return await this.db.transaction().execute(async (tx) => { - const factory_ = { - ...factory, - id: buildFactoryId({ ...factory, chainId }), - chainId, - }; - const { id: factoryId } = await tx - .insertInto("factories") - .values(factory_) - .onConflict((oc) => oc.column("id").doUpdateSet(factory_)) - .returningAll() - .executeTakeFirstOrThrow(); - - const existingIntervals = await tx - .deleteFrom("factoryLogFilterIntervals") - .where("factoryId", "=", factoryId) - .returningAll() - .execute(); + const fragments = buildFactoryFragments({ + ...factory, + chainId, + }); - const mergedIntervals = intervalUnion( - existingIntervals.map((i) => [Number(i.startBlock), Number(i.endBlock)]) - ); + await Promise.all( + fragments.map(async (fragment) => { + await this.db.transaction().execute(async (tx) => { + const { id: factoryId } = await tx + .insertInto("factories") + .values(fragment) + .onConflict((oc) => oc.column("id").doUpdateSet(fragment)) + .returningAll() + .executeTakeFirstOrThrow(); - const mergedIntervalRows = mergedIntervals.map( - ([startBlock, endBlock]) => ({ - factoryId, - startBlock: BigInt(startBlock), - endBlock: BigInt(endBlock), - }) - ); + const existingIntervals = await tx + .deleteFrom("factoryLogFilterIntervals") + .where("factoryId", "=", factoryId) + .returningAll() + .execute(); - if (mergedIntervalRows.length > 0) { - await tx - .insertInto("factoryLogFilterIntervals") - .values(mergedIntervalRows) - .execute(); - } + const mergedIntervals = intervalUnion( + existingIntervals.map((i) => [ + Number(i.startBlock), + Number(i.endBlock), + ]) + ); + + const mergedIntervalRows = mergedIntervals.map( + ([startBlock, endBlock]) => ({ + factoryId, + startBlock: BigInt(startBlock), + endBlock: BigInt(endBlock), + }) + ); - return mergedIntervals; + if (mergedIntervalRows.length > 0) { + await tx + .insertInto("factoryLogFilterIntervals") + .values(mergedIntervalRows) + .execute(); + } + + return mergedIntervals; + }); + }) + ); + + const intervals = await this.db + .with( + "factoryFilterFragments(fragmentId, fragmentAddress, fragmentEventSelector, fragmentChildAddressLocation, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", + () => + sql`( values ${sql.join( + fragments.map( + (f) => + sql`( ${sql.val(f.id)}, ${sql.val(f.address)}, ${sql.val( + f.eventSelector + )}, ${sql.val(f.childAddressLocation)}, ${sql.val( + f.topic0 + )}, ${sql.val(f.topic1)}, ${sql.val(f.topic2)}, ${sql.val( + f.topic3 + )} )` + ) + )} )` + ) + .selectFrom("factoryLogFilterIntervals") + .leftJoin("factories", "factoryId", "factories.id") + .innerJoin("factoryFilterFragments", (join) => { + let baseJoin = join.on(({ and, cmpr }) => + and([ + cmpr("fragmentAddress", "=", sql.ref("address")), + cmpr("fragmentEventSelector", "=", sql.ref("eventSelector")), + cmpr( + "fragmentChildAddressLocation", + "=", + sql.ref("childAddressLocation") + ), + ]) + ); + for (const idx_ of range(0, 4)) { + baseJoin = baseJoin.on(({ or, cmpr }) => { + const idx = idx_ as 0 | 1 | 2 | 3; + return or([ + cmpr(`topic${idx}`, "is", null), + cmpr(`fragmentTopic${idx}`, "=", sql.ref(`topic${idx}`)), + ]); + }); + } + + return baseJoin; + }) + .select(["fragmentId", "startBlock", "endBlock"]) + .where("chainId", "=", chainId) + .execute(); + + const intervalsByFragment = intervals.reduce((acc, cur) => { + const { fragmentId, ...rest } = cur; + acc[fragmentId] ||= []; + acc[fragmentId].push({ + startBlock: rest.startBlock, + endBlock: rest.endBlock, + }); + return acc; + }, {} as Record); + + const fragmentIntervals = fragments.map((f) => { + return (intervalsByFragment[f.id] ?? []).map( + (r) => + [Number(r.startBlock), Number(r.endBlock)] satisfies [number, number] + ); }); + + return intervalIntersectionMany(fragmentIntervals); }; insertRealtimeBlock = async ({ @@ -632,17 +703,16 @@ export class PostgresEventStore implements EventStore { factories: FactoryCriteria[]; interval: { startBlock: bigint; endBlock: bigint }; }) => { + const factoryFragments = factories + .map((factory) => buildFactoryFragments({ ...factory, chainId })) + .flat(); + await Promise.all( - factories.map(async (factory) => { - const factory_ = { - id: buildFactoryId({ chainId, ...factory }), - chainId, - ...factory, - }; + factoryFragments.map(async (fragment) => { const { id: factoryId } = await tx .insertInto("factories") - .values(factory_) - .onConflict((oc) => oc.column("id").doUpdateSet(factory_)) + .values(fragment) + .onConflict((oc) => oc.column("id").doUpdateSet(fragment)) .returningAll() .executeTakeFirstOrThrow(); diff --git a/packages/core/src/event-store/sqlite/format.ts b/packages/core/src/event-store/sqlite/format.ts index 387b16c81..6de1f0dc4 100644 --- a/packages/core/src/event-store/sqlite/format.ts +++ b/packages/core/src/event-store/sqlite/format.ts @@ -191,6 +191,10 @@ type FactoriesTable = { address: Hex; eventSelector: Hex; childAddressLocation: `topic${1 | 2 | 3}` | `offset${number}`; + topic0: Hex | null; + topic1: Hex | null; + topic2: Hex | null; + topic3: Hex | null; }; type FactoryLogFilterIntervalsTable = { diff --git a/packages/core/src/event-store/sqlite/migrations.ts b/packages/core/src/event-store/sqlite/migrations.ts index a19c038e3..3ef4fc61b 100644 --- a/packages/core/src/event-store/sqlite/migrations.ts +++ b/packages/core/src/event-store/sqlite/migrations.ts @@ -328,6 +328,10 @@ const migrations: Record = { .addColumn("address", "varchar(42)", (col) => col.notNull()) .addColumn("eventSelector", "varchar(66)", (col) => col.notNull()) .addColumn("childAddressLocation", "text", (col) => col.notNull()) // `topic${number}` or `offset${number}` + .addColumn("topic0", "varchar(66)") + .addColumn("topic1", "varchar(66)") + .addColumn("topic2", "varchar(66)") + .addColumn("topic3", "varchar(66)") .execute(); await db.schema .createTable("factoryLogFilterIntervals") diff --git a/packages/core/src/event-store/sqlite/store.ts b/packages/core/src/event-store/sqlite/store.ts index 25001ac52..ab04ae122 100644 --- a/packages/core/src/event-store/sqlite/store.ts +++ b/packages/core/src/event-store/sqlite/store.ts @@ -9,15 +9,18 @@ import { } from "kysely"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import { type FactoryCriteria, buildFactoryId } from "@/config/factories"; +import { type FactoryCriteria } from "@/config/factories"; import type { LogFilterCriteria } from "@/config/logFilters"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; import type { NonNull } from "@/types/utils"; import { decodeToBigInt, encodeAsText } from "@/utils/encoding"; +import { + buildFactoryFragments, + buildLogFilterFragments, +} from "@/utils/fragments"; import { intervalIntersectionMany, intervalUnion } from "@/utils/interval"; -import { buildLogFilterFragments } from "@/utils/logFilter"; import { range } from "@/utils/range"; import type { EventStore } from "../store"; @@ -155,16 +158,14 @@ export class SqliteEventStore implements EventStore { }) ); - const fragmentsWithIdx = fragments.map((f, idx) => ({ idx, ...f })); - const intervals = await this.db .with( - "logFilterFragments(fragmentIndex, fragmentAddress, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", + "logFilterFragments(fragmentId, fragmentAddress, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", () => sql`( values ${sql.join( - fragmentsWithIdx.map( + fragments.map( (f) => - sql`( ${sql.val(f.idx)}, ${sql.val(f.address)}, ${sql.val( + sql`( ${sql.val(f.id)}, ${sql.val(f.address)}, ${sql.val( f.topic0 )}, ${sql.val(f.topic1)}, ${sql.val(f.topic2)}, ${sql.val( f.topic3 @@ -193,22 +194,22 @@ export class SqliteEventStore implements EventStore { return baseJoin; }) - .select(["fragmentIndex", "startBlock", "endBlock"]) + .select(["fragmentId", "startBlock", "endBlock"]) .where("chainId", "=", chainId) .execute(); const intervalsByFragment = intervals.reduce((acc, cur) => { - const { fragmentIndex, ...rest } = cur; - acc[fragmentIndex] ||= []; - acc[fragmentIndex].push({ + const { fragmentId, ...rest } = cur; + acc[fragmentId] ||= []; + acc[fragmentId].push({ startBlock: decodeToBigInt(rest.startBlock), endBlock: decodeToBigInt(rest.endBlock), }); return acc; - }, {} as Record); + }, {} as Record); - const fragmentIntervals = fragmentsWithIdx.map((f) => { - return (intervalsByFragment[f.idx] ?? []).map( + const fragmentIntervals = fragments.map((f) => { + return (intervalsByFragment[f.id] ?? []).map( (r) => [Number(r.startBlock), Number(r.endBlock)] satisfies [number, number] ); @@ -338,49 +339,119 @@ export class SqliteEventStore implements EventStore { chainId: number; factory: FactoryCriteria; }) => { - return await this.db.transaction().execute(async (tx) => { - const factory_ = { - ...factory, - id: buildFactoryId({ ...factory, chainId }), - chainId, - }; - const { id: factoryId } = await tx - .insertInto("factories") - .values(factory_) - .onConflict((oc) => oc.doUpdateSet(factory_)) - .returningAll() - .executeTakeFirstOrThrow(); - - const existingIntervals = await tx - .deleteFrom("factoryLogFilterIntervals") - .where("factoryId", "=", factoryId) - .returningAll() - .execute(); + const fragments = buildFactoryFragments({ + ...factory, + chainId, + }); - const mergedIntervals = intervalUnion( - existingIntervals.map((i) => [ - Number(decodeToBigInt(i.startBlock)), - Number(decodeToBigInt(i.endBlock)), - ]) - ); + await Promise.all( + fragments.map(async (fragment) => { + return await this.db.transaction().execute(async (tx) => { + const { id: factoryId } = await tx + .insertInto("factories") + .values(fragment) + .onConflict((oc) => oc.doUpdateSet(fragment)) + .returningAll() + .executeTakeFirstOrThrow(); - const mergedIntervalRows = mergedIntervals.map( - ([startBlock, endBlock]) => ({ - factoryId, - startBlock: encodeAsText(startBlock), - endBlock: encodeAsText(endBlock), - }) - ); + const existingIntervals = await tx + .deleteFrom("factoryLogFilterIntervals") + .where("factoryId", "=", factoryId) + .returningAll() + .execute(); - if (mergedIntervalRows.length > 0) { - await tx - .insertInto("factoryLogFilterIntervals") - .values(mergedIntervalRows) - .execute(); - } + const mergedIntervals = intervalUnion( + existingIntervals.map((i) => [ + Number(decodeToBigInt(i.startBlock)), + Number(decodeToBigInt(i.endBlock)), + ]) + ); + + const mergedIntervalRows = mergedIntervals.map( + ([startBlock, endBlock]) => ({ + factoryId, + startBlock: encodeAsText(startBlock), + endBlock: encodeAsText(endBlock), + }) + ); + + if (mergedIntervalRows.length > 0) { + await tx + .insertInto("factoryLogFilterIntervals") + .values(mergedIntervalRows) + .execute(); + } + + return mergedIntervals; + }); + }) + ); - return mergedIntervals; + const intervals = await this.db + .with( + "factoryFilterFragments(fragmentId, fragmentAddress, fragmentEventSelector, fragmentChildAddressLocation, fragmentTopic0, fragmentTopic1, fragmentTopic2, fragmentTopic3)", + () => + sql`( values ${sql.join( + fragments.map( + (f) => + sql`( ${sql.val(f.id)}, ${sql.val(f.address)}, ${sql.val( + f.eventSelector + )}, ${sql.val(f.childAddressLocation)}, ${sql.val( + f.topic0 + )}, ${sql.val(f.topic1)}, ${sql.val(f.topic2)}, ${sql.val( + f.topic3 + )} )` + ) + )} )` + ) + .selectFrom("factoryLogFilterIntervals") + .leftJoin("factories", "factoryId", "factories.id") + .innerJoin("factoryFilterFragments", (join) => { + let baseJoin = join.on(({ and, cmpr }) => + and([ + cmpr("fragmentAddress", "=", sql.ref("address")), + cmpr("fragmentEventSelector", "=", sql.ref("eventSelector")), + cmpr( + "fragmentChildAddressLocation", + "=", + sql.ref("childAddressLocation") + ), + ]) + ); + for (const idx_ of range(0, 4)) { + baseJoin = baseJoin.on(({ or, cmpr }) => { + const idx = idx_ as 0 | 1 | 2 | 3; + return or([ + cmpr(`topic${idx}`, "is", null), + cmpr(`fragmentTopic${idx}`, "=", sql.ref(`topic${idx}`)), + ]); + }); + } + + return baseJoin; + }) + .select(["fragmentId", "startBlock", "endBlock"]) + .where("chainId", "=", chainId) + .execute(); + + const intervalsByFragment = intervals.reduce((acc, cur) => { + const { fragmentId, ...rest } = cur; + acc[fragmentId] ||= []; + acc[fragmentId].push({ + startBlock: decodeToBigInt(rest.startBlock), + endBlock: decodeToBigInt(rest.endBlock), + }); + return acc; + }, {} as Record); + + const fragmentIntervals = fragments.map((f) => { + return (intervalsByFragment[f.id] ?? []).map( + (r) => + [Number(r.startBlock), Number(r.endBlock)] satisfies [number, number] + ); }); + + return intervalIntersectionMany(fragmentIntervals); }; insertRealtimeBlock = async ({ @@ -606,17 +677,16 @@ export class SqliteEventStore implements EventStore { factories: FactoryCriteria[]; interval: { startBlock: bigint; endBlock: bigint }; }) => { + const factoryFragments = factories + .map((factory) => buildFactoryFragments({ ...factory, chainId })) + .flat(); + await Promise.all( - factories.map(async (factory) => { - const factory_ = { - id: buildFactoryId({ chainId, ...factory }), - chainId, - ...factory, - }; + factoryFragments.map(async (fragment) => { const { id: factoryId } = await tx .insertInto("factories") - .values(factory_) - .onConflict((oc) => oc.doUpdateSet(factory_)) + .values(fragment) + .onConflict((oc) => oc.doUpdateSet(fragment)) .returningAll() .executeTakeFirstOrThrow(); diff --git a/packages/core/src/event-store/store.test.ts b/packages/core/src/event-store/store.test.ts index 169ca031b..b1273fc15 100644 --- a/packages/core/src/event-store/store.test.ts +++ b/packages/core/src/event-store/store.test.ts @@ -639,6 +639,45 @@ test("insertFactoryLogFilterInterval inserts and merges child contract intervals expect(intervals).toMatchObject([[0, 1000]]); }); +test("getFactoryLogFilterIntervals handles topic filtering rules", async (context) => { + const { eventStore } = context; + + const factoryCriteria = { + address: "0xfactory", + eventSelector: + "0x0000000000000000000000000000000000000000000factoryeventsignature", + childAddressLocation: "topic1", + } satisfies FactoryCriteria; + + await eventStore.insertFactoryLogFilterInterval({ + chainId: 1, + factory: factoryCriteria, + block: blockOne, + transactions: blockOneTransactions, + logs: blockOneLogs, + interval: { startBlock: 0n, endBlock: 500n }, + }); + + let intervals = await eventStore.getFactoryLogFilterIntervals({ + chainId: 1, + factory: factoryCriteria, + }); + + expect(intervals).toMatchObject([[0, 500]]); + + intervals = await eventStore.getFactoryLogFilterIntervals({ + chainId: 1, + factory: { + ...factoryCriteria, + topics: [ + "0x0000000000000000000000000000000000000000000factoryeventsignature", + ], + } as FactoryCriteria, + }); + + expect(intervals).toMatchObject([[0, 500]]); +}); + test("insertRealtimeBlock inserts data", async (context) => { const { eventStore } = context; diff --git a/packages/core/src/historical-sync/service.ts b/packages/core/src/historical-sync/service.ts index 041408a14..caa350cd8 100644 --- a/packages/core/src/historical-sync/service.ts +++ b/packages/core/src/historical-sync/service.ts @@ -697,6 +697,7 @@ export class HistoricalSyncService extends Emittery { for await (const childContractAddressBatch of iterator) { const batchLogs = await this._eth_getLogs({ address: childContractAddressBatch, + topics: factory.criteria.topics, fromBlock: toHex(fromBlock), toBlock: toHex(toBlock), }); diff --git a/packages/core/src/realtime-sync/filter.test.ts b/packages/core/src/realtime-sync/filter.test.ts index 4d6d80877..a60e46cdc 100644 --- a/packages/core/src/realtime-sync/filter.test.ts +++ b/packages/core/src/realtime-sync/filter.test.ts @@ -86,6 +86,15 @@ test("filterLogs handles one logFilter, two addresses", () => { ); }); +test("filterLogs handles empty array of addresses", () => { + const filteredLogs = filterLogs({ + logs, + logFilters: [{ address: [] }], + }); + + expect(filteredLogs).toStrictEqual(logs); +}); + test("filterLogs handles two logFilters, one address each", () => { const filteredLogs = filterLogs({ logs, diff --git a/packages/core/src/realtime-sync/filter.ts b/packages/core/src/realtime-sync/filter.ts index 217a78ba2..d80722a58 100644 --- a/packages/core/src/realtime-sync/filter.ts +++ b/packages/core/src/realtime-sync/filter.ts @@ -30,7 +30,7 @@ export function isLogMatchedByFilter({ address?: Address | Address[]; topics?: (Hex | Hex[] | null)[]; }) { - if (address) { + if (address !== undefined && address.length > 0) { if (Array.isArray(address)) { if (!address.includes(log.address)) return false; } else { diff --git a/packages/core/src/realtime-sync/service.ts b/packages/core/src/realtime-sync/service.ts index 8e3a1f49b..90d7b4593 100644 --- a/packages/core/src/realtime-sync/service.ts +++ b/packages/core/src/realtime-sync/service.ts @@ -343,30 +343,29 @@ export class RealtimeSyncService extends Emittery { // NOTE: It might make sense to just insert all logs rather than introduce // a potentially slow DB operation here. It's a tradeoff between sync // latency and database growth. - const allChildContractAddresses = ( - await Promise.all( - this.factories.map(async (factory) => { - const iterator = this.eventStore.getFactoryChildAddresses({ - chainId: this.network.chainId, - factory: factory.criteria, - upToBlockNumber: hexToBigInt(block.number!), - }); - const childContractAddresses: Hex[] = []; - for await (const batch of iterator) { - childContractAddresses.push(...batch); - } - return childContractAddresses; - }) - ) - ).flat(); + const factoryLogFilters = await Promise.all( + this.factories.map(async (factory) => { + const iterator = this.eventStore.getFactoryChildAddresses({ + chainId: this.network.chainId, + factory: factory.criteria, + upToBlockNumber: hexToBigInt(block.number!), + }); + const childContractAddresses: Hex[] = []; + for await (const batch of iterator) { + childContractAddresses.push(...batch); + } + return { + address: childContractAddresses, + topics: factory.criteria.topics, + }; + }) + ); matchedLogs = filterLogs({ logs, logFilters: [ ...this.logFilters.map((l) => l.criteria), - ...(allChildContractAddresses.length > 0 - ? [{ address: allChildContractAddresses }] - : []), + ...factoryLogFilters, ], }); } diff --git a/packages/core/src/utils/logFilter.test.ts b/packages/core/src/utils/fragments.test.ts similarity index 63% rename from packages/core/src/utils/logFilter.test.ts rename to packages/core/src/utils/fragments.test.ts index 8e9b1b369..11e7a8acf 100644 --- a/packages/core/src/utils/logFilter.test.ts +++ b/packages/core/src/utils/fragments.test.ts @@ -1,6 +1,13 @@ +import { parseAbiItem } from "viem"; import { expect, test } from "vitest"; -import { buildLogFilterFragments } from "./logFilter"; +import { buildFactoryCriteria } from "@/config/factories"; + +import { buildFactoryFragments, buildLogFilterFragments } from "./fragments"; + +const llamaFactoryEventAbiItem = parseAbiItem( + "event LlamaInstanceCreated(address indexed deployer, string indexed name, address llamaCore, address llamaExecutor, address llamaPolicy, uint256 chainId)" +); test("buildLogFilterFragments generates 1 log filter fragment for null filter", () => { const logFilterFragments = buildLogFilterFragments({ chainId: 1 }); @@ -87,3 +94,27 @@ test("buildLogFilterFragments generates 12 log filter fragment for 2x2x3 filter" expect(logFilterFragments.length).toBe(12); }); + +test("buildFactoryFragments builds id containing topic", () => { + const criteria = buildFactoryCriteria({ + address: "0xa", + event: llamaFactoryEventAbiItem, + parameter: "deployer", + }); + + expect(buildFactoryFragments({ chainId: 1, ...criteria })[0].id).toBe( + "1_0xa_0x00fef2d461a2fabbb523f9f42752c61336f03b17a602af52cc6c83cb8b110599_topic1_null_null_null_null" + ); +}); + +test("buildFactoryFragments builds id containing offset", () => { + const criteria = buildFactoryCriteria({ + address: "0xa", + event: llamaFactoryEventAbiItem, + parameter: "llamaPolicy", + }); + + expect(buildFactoryFragments({ chainId: 115511, ...criteria })[0].id).toBe( + "115511_0xa_0x00fef2d461a2fabbb523f9f42752c61336f03b17a602af52cc6c83cb8b110599_offset64_null_null_null_null" + ); +}); diff --git a/packages/core/src/utils/fragments.ts b/packages/core/src/utils/fragments.ts new file mode 100644 index 000000000..775b90c34 --- /dev/null +++ b/packages/core/src/utils/fragments.ts @@ -0,0 +1,125 @@ +import type { Address, Hex } from "viem"; + +import { FactoryCriteria } from "@/config/factories"; +import type { LogFilterCriteria } from "@/config/logFilters"; + +/** + * Generates log filter fragments from a log filter. + * + * @param logFilter Log filter to be decompose into fragments. + * @returns A list of log filter fragments. + */ +export function buildLogFilterFragments({ + address, + topics, + chainId, +}: LogFilterCriteria & { + chainId: number; +}) { + return buildFragments({ + address, + topics, + chainId, + idCallback: (address_, topic0_, topic1_, topic2_, topic3_) => + `${chainId}_${address_}_${topic0_}_${topic1_}_${topic2_}_${topic3_}`, + }); +} + +/** + * Generates factory fragments from a factory. + * + * @param factory Factory to be decomposed into fragments. + * @returns A list of factory fragments. + */ +export function buildFactoryFragments({ + address, + topics, + childAddressLocation, + eventSelector, + chainId, +}: FactoryCriteria & { + chainId: number; +}) { + const fragments = buildFragments({ + address, + topics, + chainId, + childAddressLocation, + eventSelector, + idCallback: (address_, topic0_, topic1_, topic2_, topic3_) => + `${chainId}_${address_}_${eventSelector}_${childAddressLocation}_${topic0_}_${topic1_}_${topic2_}_${topic3_}`, + }); + + return fragments as ((typeof fragments)[number] & + Pick< + FactoryCriteria, + "eventSelector" | "childAddressLocation" | "address" + >)[]; +} + +function buildFragments({ + address, + topics, + chainId, + idCallback, + ...rest +}: + | (LogFilterCriteria | FactoryCriteria) & { + idCallback: ( + address: Address | null, + topic0: ReturnType["topic0"], + topic1: ReturnType["topic1"], + topic2: ReturnType["topic2"], + topic3: ReturnType["topic3"] + ) => string; + chainId: number; + }) { + const fragments: { + id: string; + chainId: number; + address: Hex | null; + topic0: Hex | null; + topic1: Hex | null; + topic2: Hex | null; + topic3: Hex | null; + }[] = []; + + const { topic0, topic1, topic2, topic3 } = parseTopics(topics); + + for (const address_ of Array.isArray(address) ? address : [address ?? null]) { + for (const topic0_ of Array.isArray(topic0) ? topic0 : [topic0]) { + for (const topic1_ of Array.isArray(topic1) ? topic1 : [topic1]) { + for (const topic2_ of Array.isArray(topic2) ? topic2 : [topic2]) { + for (const topic3_ of Array.isArray(topic3) ? topic3 : [topic3]) { + fragments.push({ + id: idCallback(address_, topic0_, topic1_, topic2_, topic3_), + ...rest, + chainId, + address: address_, + topic0: topic0_, + topic1: topic1_, + topic2: topic2_, + topic3: topic3_, + }); + } + } + } + } + } + + return fragments; +} + +function parseTopics(topics: (Hex | Hex[] | null)[] | undefined) { + return { + topic0: topics?.[0] ?? null, + topic1: topics?.[1] ?? null, + topic2: topics?.[2] ?? null, + topic3: topics?.[3] ?? null, + } as { + topic0: Hex | Hex[] | null; + topic1: Hex | Hex[] | null; + topic2: Hex | Hex[] | null; + topic3: Hex | Hex[] | null; + }; +} diff --git a/packages/core/src/utils/logFilter.ts b/packages/core/src/utils/logFilter.ts deleted file mode 100644 index 7151a55f5..000000000 --- a/packages/core/src/utils/logFilter.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Hex } from "viem"; - -import type { LogFilterCriteria } from "@/config/logFilters"; - -/** - * Generates log filter fragments from a log filter. - * - * @param logFilter Log filter to be decompose into fragments. - * @returns A list of log filter fragments. - */ -export function buildLogFilterFragments({ - address, - topics, - chainId, -}: LogFilterCriteria & { - chainId: number; -}) { - const fragments: { - id: string; - chainId: number; - address: Hex | null; - topic0: Hex | null; - topic1: Hex | null; - topic2: Hex | null; - topic3: Hex | null; - }[] = []; - - const { topic0, topic1, topic2, topic3 } = parseTopics(topics); - - for (const address_ of Array.isArray(address) ? address : [address ?? null]) { - for (const topic0_ of Array.isArray(topic0) ? topic0 : [topic0]) { - for (const topic1_ of Array.isArray(topic1) ? topic1 : [topic1]) { - for (const topic2_ of Array.isArray(topic2) ? topic2 : [topic2]) { - for (const topic3_ of Array.isArray(topic3) ? topic3 : [topic3]) { - fragments.push({ - id: `${chainId}_${address_}_${topic0_}_${topic1_}_${topic2_}_${topic3_}`, - chainId, - address: address_, - topic0: topic0_, - topic1: topic1_, - topic2: topic2_, - topic3: topic3_, - }); - } - } - } - } - } - - return fragments; -} - -function parseTopics(topics: (Hex | Hex[] | null)[] | undefined) { - return { - topic0: topics?.[0] ?? null, - topic1: topics?.[1] ?? null, - topic2: topics?.[2] ?? null, - topic3: topics?.[3] ?? null, - } as { - topic0: Hex | Hex[] | null; - topic1: Hex | Hex[] | null; - topic2: Hex | Hex[] | null; - topic3: Hex | Hex[] | null; - }; -} From 4c09f909cce49f2b28e484edd6f340348ee226e9 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Thu, 2 Nov 2023 23:30:13 -0400 Subject: [PATCH 06/44] config types refactor wip --- packages/core/src/Ponder.ts | 57 +-- packages/core/src/build/functions.ts | 9 +- packages/core/src/build/service.ts | 22 +- packages/core/src/codegen/contract.ts | 33 -- packages/core/src/codegen/event.ts | 11 +- packages/core/src/codegen/service.ts | 38 +- packages/core/src/config/abi.test.ts | 506 +++++++++---------- packages/core/src/config/abi.ts | 158 +++--- packages/core/src/config/contracts.ts | 106 ++-- packages/core/src/config/factories.ts | 77 +-- packages/core/src/config/logFilters.ts | 120 ----- packages/core/src/config/sources.ts | 164 ++++++ packages/core/src/config/types.ts | 112 ++-- packages/core/src/historical-sync/service.ts | 18 +- packages/core/src/indexing/contract.test.ts | 272 +++++----- packages/core/src/indexing/contract.ts | 302 +++++------ packages/core/src/indexing/service.test.ts | 22 +- packages/core/src/indexing/service.ts | 21 +- packages/core/src/realtime-sync/service.ts | 19 +- packages/core/src/ui/app.tsx | 16 +- packages/core/src/ui/service.ts | 22 +- packages/core/src/utils/fragments.ts | 3 +- 22 files changed, 963 insertions(+), 1145 deletions(-) delete mode 100644 packages/core/src/codegen/contract.ts delete mode 100644 packages/core/src/config/logFilters.ts create mode 100644 packages/core/src/config/sources.ts diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index 7e5b452c5..7da2da157 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -3,10 +3,7 @@ import process from "node:process"; import { BuildService } from "@/build/service"; import { CodegenService } from "@/codegen/service"; -import { buildContracts } from "@/config/contracts"; import { buildDatabase } from "@/config/database"; -import { type Factory, buildFactories } from "@/config/factories"; -import { type LogFilter, buildLogFilters } from "@/config/logFilters"; import { type Network, buildNetwork } from "@/config/networks"; import { type Options } from "@/config/options"; import { type ResolvedConfig } from "@/config/types"; @@ -27,6 +24,8 @@ import { PostgresUserStore } from "@/user-store/postgres/store"; import { SqliteUserStore } from "@/user-store/sqlite/store"; import { type UserStore } from "@/user-store/store"; +import { buildSources, Source } from "./config/sources"; + export type Common = { options: Options; logger: LoggerService; @@ -37,7 +36,7 @@ export type Common = { export class Ponder { common: Common; - logFilters: LogFilter[]; + sources: Source[]; eventStore: EventStore; userStore: UserStore; @@ -45,8 +44,7 @@ export class Ponder { // List of indexing-related services. One per configured network. networkSyncServices: { network: Network; - logFilters: LogFilter[]; - factories: Factory[]; + sources: Source[]; historicalSyncService: HistoricalSyncService; realtimeSyncService: RealtimeSyncService; }[] = []; @@ -106,15 +104,14 @@ export class Ponder { const networks = config.networks.map((network) => buildNetwork({ network, common }) ); - const logFilters = buildLogFilters({ options, config }); - this.logFilters = logFilters; - const contracts = buildContracts({ options, config, networks }); - const factories = buildFactories({ options, config }); + + const sources = buildSources({ options, config }); + this.sources = sources; const networksToSync = config.networks .map((network) => buildNetwork({ network, common })) .filter((network) => { - const hasEventSources = [...logFilters, ...factories].some( + const hasEventSources = this.sources.some( (eventSource) => eventSource.network === network.name ); if (!hasEventSources) { @@ -127,29 +124,25 @@ export class Ponder { }); networksToSync.forEach((network) => { - const logFiltersForNetwork = logFilters.filter( - (logFilter) => logFilter.network === network.name - ); - const factoriesForNetwork = factories.filter( - (logFilter) => logFilter.network === network.name + const sourcesForNetwork = sources.filter( + (logSource) => logSource.network === network.name ); + this.networkSyncServices.push({ network, - logFilters: logFiltersForNetwork, - factories: factoriesForNetwork, + + sources: sourcesForNetwork, historicalSyncService: new HistoricalSyncService({ common, eventStore: this.eventStore, network, - logFilters: logFiltersForNetwork, - factories: factoriesForNetwork, + sources: sourcesForNetwork, }), realtimeSyncService: new RealtimeSyncService({ common, eventStore: this.eventStore, network, - logFilters: logFiltersForNetwork, - factories, + sources: sourcesForNetwork, }), }); }); @@ -158,8 +151,7 @@ export class Ponder { common, eventStore: this.eventStore, networks, - logFilters, - factories, + sources, }); this.indexingService = new IndexingService({ @@ -167,9 +159,7 @@ export class Ponder { eventStore: this.eventStore, userStore: this.userStore, eventAggregatorService: this.eventAggregatorService, - contracts, - logFilters, - factories, + sources, }); this.serverService = new ServerService({ @@ -178,16 +168,13 @@ export class Ponder { }); this.buildService = new BuildService({ common, - logFilters, - factories, + sources, }); this.codegenService = new CodegenService({ common, - contracts, - logFilters, - factories, + sources, }); - this.uiService = new UiService({ common, logFilters, factories }); + this.uiService = new UiService({ common, sources }); } async setup() { @@ -216,7 +203,7 @@ export class Ponder { event: "App Started", properties: { command: "ponder dev", - logFilterCount: this.logFilters.length, + logFilterCount: this.sources.length, databaseKind: this.eventStore.kind, }, }); @@ -243,7 +230,7 @@ export class Ponder { event: "App Started", properties: { command: "ponder start", - logFilterCount: this.logFilters.length, + logFilterCount: this.sources.length, databaseKind: this.eventStore.kind, }, }); diff --git a/packages/core/src/build/functions.ts b/packages/core/src/build/functions.ts index 0e4ae5d70..0503f0690 100644 --- a/packages/core/src/build/functions.ts +++ b/packages/core/src/build/functions.ts @@ -6,9 +6,8 @@ import { replaceTscAliasPaths } from "tsc-alias"; import type { Hex } from "viem"; import { LogEventMetadata } from "@/config/abi"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; import type { Options } from "@/config/options"; +import { Source } from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; @@ -207,12 +206,10 @@ export type IndexingFunctions = { export const hydrateIndexingFunctions = ({ rawIndexingFunctions, - logFilters, - factories, + sources, }: { rawIndexingFunctions: RawIndexingFunctions; - logFilters: LogFilter[]; - factories: Factory[]; + sources: Source[]; }) => { const indexingFunctions: IndexingFunctions = { _meta_: {}, diff --git a/packages/core/src/build/service.ts b/packages/core/src/build/service.ts index 2376b589b..2c8ea3d25 100644 --- a/packages/core/src/build/service.ts +++ b/packages/core/src/build/service.ts @@ -5,8 +5,7 @@ import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import path from "node:path"; -import type { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import { Source } from "@/config/sources"; import { UserError } from "@/errors/user"; import type { Common } from "@/Ponder"; import { buildSchema } from "@/schema/schema"; @@ -28,25 +27,15 @@ type BuildServiceEvents = { export class BuildService extends Emittery { private common: Common; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; private closeWatcher?: () => Promise; private latestFileHashes: Record = {}; - constructor({ - common, - logFilters, - factories, - }: { - common: Common; - logFilters: LogFilter[]; - factories: Factory[]; - }) { + constructor({ common, sources }: { common: Common; sources: Source[] }) { super(); this.common = common; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; } async kill() { @@ -102,8 +91,7 @@ export class BuildService extends Emittery { const indexingFunctions = hydrateIndexingFunctions({ rawIndexingFunctions, - logFilters: this.logFilters, - factories: this.factories, + sources: this.sources, }); if (Object.values(indexingFunctions.eventSources).length === 0) { diff --git a/packages/core/src/codegen/contract.ts b/packages/core/src/codegen/contract.ts deleted file mode 100644 index 199e32cfd..000000000 --- a/packages/core/src/codegen/contract.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { AbiParameter } from "abitype"; - -import type { Contract } from "@/config/contracts"; - -type AbiReadOrViewFunction = { - type: "function"; - stateMutability: "pure" | "view"; - inputs: readonly AbiParameter[]; - name: string; - outputs: readonly AbiParameter[]; -}; - -export const buildContractTypes = (contracts: Contract[]) => { - return contracts - .map((contract) => { - const abiReadOrViewFunctions = contract.abi.filter( - (item): item is AbiReadOrViewFunction => - item.type === "function" && - (item.stateMutability === "pure" || item.stateMutability === "view") - ); - - return ` - const ${contract.name}Abi = ${JSON.stringify( - abiReadOrViewFunctions - )} as const; - - export type ${contract.name} = ReadOnlyContract; - `; - }) - .join("\n"); -}; diff --git a/packages/core/src/codegen/event.ts b/packages/core/src/codegen/event.ts index 715992e77..b2232e7d1 100644 --- a/packages/core/src/codegen/event.ts +++ b/packages/core/src/codegen/event.ts @@ -1,14 +1,7 @@ import type { LogEventMetadata } from "@/config/abi"; -import type { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import { Source } from "@/config/sources"; -export const buildEventTypes = ({ - logFilters, - factories, -}: { - logFilters: LogFilter[]; - factories: Factory[]; -}) => { +export const buildEventTypes = ({ sources }: { sources: Source[] }) => { const allIndexingFunctions = [ ...logFilters.map((logFilter) => { return Object.values(logFilter.events) diff --git a/packages/core/src/codegen/service.ts b/packages/core/src/codegen/service.ts index 7d231b4aa..b255b808a 100644 --- a/packages/core/src/codegen/service.ts +++ b/packages/core/src/codegen/service.ts @@ -3,40 +3,23 @@ import { GraphQLSchema, printSchema } from "graphql"; import { writeFileSync } from "node:fs"; import path from "node:path"; -import type { Contract } from "@/config/contracts"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import { Source } from "@/config/sources"; import type { Common } from "@/Ponder"; import type { Schema } from "@/schema/types"; import { ensureDirExists } from "@/utils/exists"; -import { buildContractTypes } from "./contract"; import { buildEntityTypes } from "./entity"; import { buildEventTypes } from "./event"; import { formatPrettier } from "./prettier"; export class CodegenService extends Emittery { private common: Common; - private contracts: Contract[]; - private logFilters: LogFilter[]; - private factories: Factory[]; - - constructor({ - common, - contracts, - logFilters, - factories, - }: { - common: Common; - contracts: Contract[]; - logFilters: LogFilter[]; - factories: Factory[]; - }) { + private sources: Source[]; + + constructor({ common, sources }: { common: Common; sources: Source[] }) { super(); this.common = common; - this.contracts = contracts; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; } generateAppFile({ schema }: { schema?: Schema } = {}) { @@ -56,16 +39,10 @@ export class CodegenService extends Emittery { /* CONTRACT TYPES */ - ${buildContractTypes(this.contracts)} - /* CONTEXT TYPES */ export type Context = { - contracts: { - ${this.contracts - .map((contract) => `${contract.name}: ${contract.name};`) - .join("")} - }, + entities: { ${entities .map((entity) => `${entity.name}: Model<${entity.name}>;`) @@ -77,8 +54,7 @@ export class CodegenService extends Emittery { /* INDEXING FUNCTION TYPES */ ${buildEventTypes({ - logFilters: this.logFilters, - factories: this.factories, + sources: this.sources, })} export const ponder = new PonderApp(); diff --git a/packages/core/src/config/abi.test.ts b/packages/core/src/config/abi.test.ts index 487c223f4..b4c4ad645 100644 --- a/packages/core/src/config/abi.test.ts +++ b/packages/core/src/config/abi.test.ts @@ -1,280 +1,280 @@ -import { randomUUID } from "node:crypto"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { beforeEach, expect, test } from "vitest"; +// import { randomUUID } from "node:crypto"; +// import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +// import { tmpdir } from "node:os"; +// import path from "node:path"; +// import { beforeEach, expect, test } from "vitest"; -import { buildAbi, getEvents } from "./abi"; +// import { buildAbi, getEvents } from "./abi"; -const abiSimple = [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [ - { - indexed: true, - type: "address", - }, - { - indexed: true, - type: "address", - }, - { - indexed: false, - type: "uint256", - }, - ], - name: "Transfer", - type: "event", - }, - { - inputs: [ - { - indexed: true, - type: "address", - }, - { - indexed: true, - type: "address", - }, - { - indexed: false, - type: "uint256", - }, - ], - name: "Approve", - type: "event", - }, -] as const; +// const abiSimple = [ +// { +// inputs: [], +// stateMutability: "nonpayable", +// type: "constructor", +// }, +// { +// inputs: [ +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// ], +// name: "Transfer", +// type: "event", +// }, +// { +// inputs: [ +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// ], +// name: "Approve", +// type: "event", +// }, +// ] as const; -const abiWithSameEvent = [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [ - { - indexed: true, - type: "address", - }, - { - indexed: true, - type: "address", - }, - { - indexed: false, - type: "uint256", - }, - ], - name: "Approve", - type: "event", - }, -] as const; +// const abiWithSameEvent = [ +// { +// inputs: [], +// stateMutability: "nonpayable", +// type: "constructor", +// }, +// { +// inputs: [ +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// ], +// name: "Approve", +// type: "event", +// }, +// ] as const; -let tmpDir: string; -let configFilePath: string; +// let tmpDir: string; +// let configFilePath: string; -beforeEach(() => { - tmpDir = path.join(tmpdir(), randomUUID()); - configFilePath = path.join(tmpDir, "ponder.config.ts"); +// beforeEach(() => { +// tmpDir = path.join(tmpdir(), randomUUID()); +// configFilePath = path.join(tmpDir, "ponder.config.ts"); - mkdirSync(tmpDir, { recursive: true }); +// mkdirSync(tmpDir, { recursive: true }); - return () => rmSync(tmpDir, { recursive: true, force: true }); -}); +// return () => rmSync(tmpDir, { recursive: true, force: true }); +// }); -test("buildAbi handles a single ABI passed as a file path", () => { - const abiSimplePath = path.join(tmpDir, "abiSimple.json"); - writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); +// test("buildAbi handles a single ABI passed as a file path", () => { +// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); +// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - const { abi, filePaths } = buildAbi({ - abiConfig: "./abiSimple.json", - configFilePath, - }); +// const { abi, filePaths } = buildAbi({ +// abiConfig: "./abiSimple.json", +// configFilePath, +// }); - expect(abi).toMatchObject(abiSimple); - expect(filePaths).toMatchObject([abiSimplePath]); -}); +// expect(abi).toMatchObject(abiSimple); +// expect(filePaths).toMatchObject([abiSimplePath]); +// }); -test("buildAbi handles a single ABI passed as an object", () => { - const { abi, filePaths } = buildAbi({ - abiConfig: abiSimple, - configFilePath, - }); +// test("buildAbi handles a single ABI passed as an object", () => { +// const { abi, filePaths } = buildAbi({ +// abiConfig: abiSimple, +// configFilePath, +// }); - expect(abi).toMatchObject(abiSimple); - expect(filePaths).toMatchObject([]); -}); +// expect(abi).toMatchObject(abiSimple); +// expect(filePaths).toMatchObject([]); +// }); -test("buildAbi handles an array with a single ABI passed as a file path", () => { - const abiSimplePath = path.join(tmpDir, "abiSimple.json"); - writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); +// test("buildAbi handles an array with a single ABI passed as a file path", () => { +// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); +// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - const { abi, filePaths } = buildAbi({ - abiConfig: ["./abiSimple.json"], - configFilePath, - }); +// const { abi, filePaths } = buildAbi({ +// abiConfig: ["./abiSimple.json"], +// configFilePath, +// }); - expect(abi).toMatchObject(abiSimple.filter((x) => x.type !== "constructor")); - expect(filePaths).toMatchObject([abiSimplePath]); -}); +// expect(abi).toMatchObject(abiSimple.filter((x) => x.type !== "constructor")); +// expect(filePaths).toMatchObject([abiSimplePath]); +// }); -test("buildAbi handles an array of ABIs passed as file paths", () => { - const abiSimplePath = path.join(tmpDir, "abiSimple.json"); - writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - const abiWithSameEventPath = path.join(tmpDir, "abiWithSameEvent.json"); - writeFileSync(abiWithSameEventPath, JSON.stringify(abiWithSameEvent)); +// test("buildAbi handles an array of ABIs passed as file paths", () => { +// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); +// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); +// const abiWithSameEventPath = path.join(tmpDir, "abiWithSameEvent.json"); +// writeFileSync(abiWithSameEventPath, JSON.stringify(abiWithSameEvent)); - const { abi, filePaths } = buildAbi({ - abiConfig: ["./abiSimple.json", "./abiWithSameEvent.json"], - configFilePath, - }); +// const { abi, filePaths } = buildAbi({ +// abiConfig: ["./abiSimple.json", "./abiWithSameEvent.json"], +// configFilePath, +// }); - expect(abi.filter((x) => x.type === "event")).toMatchObject( - abiSimple.filter((x) => x.type === "event") - ); - expect(filePaths).toMatchObject([abiSimplePath, abiWithSameEventPath]); -}); +// expect(abi.filter((x) => x.type === "event")).toMatchObject( +// abiSimple.filter((x) => x.type === "event") +// ); +// expect(filePaths).toMatchObject([abiSimplePath, abiWithSameEventPath]); +// }); -test("buildAbi handles an array of ABIs with both file paths and objects", () => { - const abiSimplePath = path.join(tmpDir, "abiSimple.json"); - writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); +// test("buildAbi handles an array of ABIs with both file paths and objects", () => { +// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); +// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - const { abi, filePaths } = buildAbi({ - abiConfig: ["./abiSimple.json", abiWithSameEvent], - configFilePath, - }); +// const { abi, filePaths } = buildAbi({ +// abiConfig: ["./abiSimple.json", abiWithSameEvent], +// configFilePath, +// }); - expect(abi.filter((x) => x.type === "event")).toMatchObject( - abiSimple.filter((x) => x.type === "event") - ); - expect(filePaths).toMatchObject([abiSimplePath]); -}); +// expect(abi.filter((x) => x.type === "event")).toMatchObject( +// abiSimple.filter((x) => x.type === "event") +// ); +// expect(filePaths).toMatchObject([abiSimplePath]); +// }); -test("buildAbi handles an array of ABIs and removes duplicate abi items", () => { - const abiSimplePath = path.join(tmpDir, "abiSimple.json"); - writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); +// test("buildAbi handles an array of ABIs and removes duplicate abi items", () => { +// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); +// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - const { abi, filePaths } = buildAbi({ - abiConfig: ["./abiSimple.json", abiWithSameEvent], - configFilePath, - }); +// const { abi, filePaths } = buildAbi({ +// abiConfig: ["./abiSimple.json", abiWithSameEvent], +// configFilePath, +// }); - expect(abi.filter((x) => x.type === "event")).toMatchObject( - abiSimple.filter((x) => x.type === "event") - ); - expect(filePaths).toMatchObject([abiSimplePath]); -}); +// expect(abi.filter((x) => x.type === "event")).toMatchObject( +// abiSimple.filter((x) => x.type === "event") +// ); +// expect(filePaths).toMatchObject([abiSimplePath]); +// }); -const abiWithOverloadedEvents = [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [ - { - indexed: true, - type: "address", - }, - { - indexed: true, - type: "address", - }, - { - indexed: false, - type: "uint256", - }, - ], - name: "Transfer", - type: "event", - }, - { - inputs: [ - { - indexed: true, - type: "uint8", - }, - { - indexed: true, - type: "uint256", - }, - { - indexed: false, - type: "uint256", - }, - { - indexed: false, - type: "address", - }, - ], - name: "Transfer", - type: "event", - }, -] as const; +// const abiWithOverloadedEvents = [ +// { +// inputs: [], +// stateMutability: "nonpayable", +// type: "constructor", +// }, +// { +// inputs: [ +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// ], +// name: "Transfer", +// type: "event", +// }, +// { +// inputs: [ +// { +// indexed: true, +// type: "uint8", +// }, +// { +// indexed: true, +// type: "uint256", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// { +// indexed: false, +// type: "address", +// }, +// ], +// name: "Transfer", +// type: "event", +// }, +// ] as const; -test("getEvents handles overloaded events", () => { - const events = getEvents({ abi: abiWithOverloadedEvents }); +// test("getEvents handles overloaded events", () => { +// const events = getEvents({ abi: abiWithOverloadedEvents }); - expect(events).toMatchObject({ - "Transfer(address indexed, address indexed, uint256)": { - safeName: "Transfer(address indexed, address indexed, uint256)", - signature: "event Transfer(address indexed, address indexed, uint256)", - selector: - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - abiItem: { - inputs: [ - { - indexed: true, - type: "address", - }, - { - indexed: true, - type: "address", - }, - { - indexed: false, - type: "uint256", - }, - ], - name: "Transfer", - type: "event", - }, - }, - "Transfer(uint8 indexed, uint256 indexed, uint256, address)": { - safeName: "Transfer(uint8 indexed, uint256 indexed, uint256, address)", - signature: - "event Transfer(uint8 indexed, uint256 indexed, uint256, address)", - selector: - "0x7d80b356169a1ce57762f79d1bc650835653d9798678ef3691964dfcde65cd76", - abiItem: { - inputs: [ - { - indexed: true, - type: "uint8", - }, - { - indexed: true, - type: "uint256", - }, - { - indexed: false, - type: "uint256", - }, - { - indexed: false, - type: "address", - }, - ], - name: "Transfer", - type: "event", - }, - }, - }); -}); +// expect(events).toMatchObject({ +// "Transfer(address indexed, address indexed, uint256)": { +// safeName: "Transfer(address indexed, address indexed, uint256)", +// signature: "event Transfer(address indexed, address indexed, uint256)", +// selector: +// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", +// abiItem: { +// inputs: [ +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: true, +// type: "address", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// ], +// name: "Transfer", +// type: "event", +// }, +// }, +// "Transfer(uint8 indexed, uint256 indexed, uint256, address)": { +// safeName: "Transfer(uint8 indexed, uint256 indexed, uint256, address)", +// signature: +// "event Transfer(uint8 indexed, uint256 indexed, uint256, address)", +// selector: +// "0x7d80b356169a1ce57762f79d1bc650835653d9798678ef3691964dfcde65cd76", +// abiItem: { +// inputs: [ +// { +// indexed: true, +// type: "uint8", +// }, +// { +// indexed: true, +// type: "uint256", +// }, +// { +// indexed: false, +// type: "uint256", +// }, +// { +// indexed: false, +// type: "address", +// }, +// ], +// name: "Transfer", +// type: "event", +// }, +// }, +// }); +// }); diff --git a/packages/core/src/config/abi.ts b/packages/core/src/config/abi.ts index 80766415b..13e6d40cd 100644 --- a/packages/core/src/config/abi.ts +++ b/packages/core/src/config/abi.ts @@ -1,87 +1,83 @@ import { type Abi, type AbiEvent, formatAbiItem } from "abitype"; -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { getEventSelector, Hex } from "viem"; +import { type Hex, getEventSelector } from "viem"; import { getDuplicateElements } from "@/utils/duplicates"; -export const buildAbi = ({ - abiConfig, - configFilePath, -}: { - abiConfig: string | any[] | object | (string | any[] | object)[]; - configFilePath: string; -}) => { - let resolvedAbi: Abi; - const filePaths: string[] = []; - - if ( - typeof abiConfig === "string" || - (Array.isArray(abiConfig) && - (abiConfig.length === 0 || typeof abiConfig[0] === "object")) - ) { - // If abiConfig is a string or an ABI itself, treat it as a single ABI. - const { abi, filePath } = buildSingleAbi({ abiConfig, configFilePath }); - resolvedAbi = abi; - if (filePath) filePaths.push(filePath); - } else { - // Otherwise, handle as an array of of ABIs. - const results = (abiConfig as (object | any[])[]).map((a) => - buildSingleAbi({ abiConfig: a, configFilePath }) - ); - - const mergedAbi = results - .map(({ abi }) => abi.filter((item) => item.type !== "constructor")) - .flat() - .flat(); - const mergedUniqueAbi = [ - ...new Map( - mergedAbi.map((item) => [JSON.stringify(item), item]) - ).values(), - ]; - - filePaths.push( - ...results.map((r) => r.filePath).filter((f): f is string => !!f) - ); - - resolvedAbi = mergedUniqueAbi; - } - - return { - abi: resolvedAbi, - filePaths, - }; -}; - -const buildSingleAbi = ({ - abiConfig, - configFilePath, -}: { - abiConfig: string | any[] | object; - configFilePath: string; -}) => { - let filePath: string | undefined = undefined; - let abi: Abi; - - if (typeof abiConfig === "string") { - // If a string, treat it as a file path. - filePath = path.isAbsolute(abiConfig) - ? abiConfig - : path.join(path.dirname(configFilePath), abiConfig); - - const abiString = readFileSync(filePath, "utf-8"); - abi = JSON.parse(abiString); - } else { - // Otherwise, treat as the ABI itself - abi = abiConfig as unknown as Abi; - } - - // NOTE: Not currently using the filePath arg here, but eventually - // could use it to watch for changes and reload. - return { abi, filePath }; -}; - -type SafeEventName = string; +// export const buildAbi = ({ +// abiConfig, +// configFilePath, +// }: { +// abiConfig: string | any[] | object | (string | any[] | object)[]; +// configFilePath: string; +// }) => { +// let resolvedAbi: Abi; +// const filePaths: string[] = []; + +// if ( +// typeof abiConfig === "string" || +// (Array.isArray(abiConfig) && +// (abiConfig.length === 0 || typeof abiConfig[0] === "object")) +// ) { +// // If abiConfig is a string or an ABI itself, treat it as a single ABI. +// const { abi, filePath } = buildSingleAbi({ abiConfig, configFilePath }); +// resolvedAbi = abi; +// if (filePath) filePaths.push(filePath); +// } else { +// // Otherwise, handle as an array of of ABIs. +// const results = (abiConfig as (object | any[])[]).map((a) => +// buildSingleAbi({ abiConfig: a, configFilePath }) +// ); + +// const mergedAbi = results +// .map(({ abi }) => abi.filter((item) => item.type !== "constructor")) +// .flat() +// .flat(); +// const mergedUniqueAbi = [ +// ...new Map( +// mergedAbi.map((item) => [JSON.stringify(item), item]) +// ).values(), +// ]; + +// filePaths.push( +// ...results.map((r) => r.filePath).filter((f): f is string => !!f) +// ); + +// resolvedAbi = mergedUniqueAbi; +// } + +// return { +// abi: resolvedAbi, +// filePaths, +// }; +// }; + +// const buildSingleAbi = ({ +// abiConfig, +// configFilePath, +// }: { +// abiConfig: string | any[] | object; +// configFilePath: string; +// }) => { +// let filePath: string | undefined = undefined; +// let abi: Abi; + +// if (typeof abiConfig === "string") { +// // If a string, treat it as a file path. +// filePath = path.isAbsolute(abiConfig) +// ? abiConfig +// : path.join(path.dirname(configFilePath), abiConfig); + +// const abiString = readFileSync(filePath, "utf-8"); +// abi = JSON.parse(abiString); +// } else { +// // Otherwise, treat as the ABI itself +// abi = abiConfig as unknown as Abi; +// } + +// // NOTE: Not currently using the filePath arg here, but eventually +// // could use it to watch for changes and reload. +// return { abi, filePath }; +// }; export type LogEventMetadata = { // Event name (if no overloads) or full event signature (if name is overloaded). @@ -95,6 +91,8 @@ export type LogEventMetadata = { abiItem: AbiEvent; }; +type SafeEventName = string; + export type AbiEvents = { [key: SafeEventName]: LogEventMetadata | undefined }; export const getEvents = ({ abi }: { abi: Abi }) => { diff --git a/packages/core/src/config/contracts.ts b/packages/core/src/config/contracts.ts index 38120e986..90e753490 100644 --- a/packages/core/src/config/contracts.ts +++ b/packages/core/src/config/contracts.ts @@ -1,53 +1,53 @@ -import type { Abi, Address } from "abitype"; - -import type { Options } from "@/config/options"; -import type { ResolvedConfig } from "@/config/types"; -import { toLowerCase } from "@/utils/lowercase"; - -import { buildAbi } from "./abi"; -import type { Network } from "./networks"; - -export type Contract = { - name: string; - address: Address; - network: Network; - abi: Abi; -}; - -export function buildContracts({ - config, - options, - networks, -}: { - config: ResolvedConfig; - options: Options; - networks: Network[]; -}) { - const contracts = config.contracts ?? []; - - return contracts - .filter( - ( - contract - ): contract is (typeof contracts)[number] & { address: Address } => - !!contract.address - ) - .map((contract) => { - const address = toLowerCase(contract.address); - - const { abi } = buildAbi({ - abiConfig: contract.abi, - configFilePath: options.configFile, - }); - - // Get the contract network/provider. - const network = networks.find((n) => n.name === contract.network); - if (!network) { - throw new Error( - `Network [${contract.network}] not found for contract: ${contract.name}` - ); - } - - return { name: contract.name, address, network, abi } satisfies Contract; - }); -} +// import type { Abi, Address } from "abitype"; + +// import type { Options } from "@/config/options"; +// import type { ResolvedConfig } from "@/config/types"; +// import { toLowerCase } from "@/utils/lowercase"; + +// import { buildAbi } from "./abi"; +// import type { Network } from "./networks"; + +// export type Contract = { +// name: string; +// address: Address; +// network: Network; +// abi: Abi; +// }; + +// export function buildContracts({ +// config, +// options, +// networks, +// }: { +// config: ResolvedConfig; +// options: Options; +// networks: Network[]; +// }) { +// const contracts = config.contracts ?? []; + +// return contracts +// .filter( +// ( +// contract +// ): contract is (typeof contracts)[number] & { address: Address } => +// !!contract.address +// ) +// .map((contract) => { +// const address = toLowerCase(contract.address); + +// const { abi } = buildAbi({ +// abiConfig: contract.abi, +// configFilePath: options.configFile, +// }); + +// // Get the contract network/provider. +// const network = networks.find((n) => n.name === contract.network); +// if (!network) { +// throw new Error( +// `Network [${contract.network}] not found for contract: ${contract.name}` +// ); +// } + +// return { name: contract.name, address, network, abi } satisfies Contract; +// }); +// } diff --git a/packages/core/src/config/factories.ts b/packages/core/src/config/factories.ts index 3a373ee7e..0c6ad5389 100644 --- a/packages/core/src/config/factories.ts +++ b/packages/core/src/config/factories.ts @@ -1,81 +1,10 @@ -import type { Abi, AbiEvent, Address } from "abitype"; -import { getEventSelector, Hex, RpcLog } from "viem"; +import type { AbiEvent } from "abitype"; +import { getEventSelector, RpcLog } from "viem"; -import type { Options } from "@/config/options"; -import type { ResolvedConfig } from "@/config/types"; import { toLowerCase } from "@/utils/lowercase"; import { getBytesConsumedByParam } from "@/utils/offset"; -import { AbiEvents, buildAbi, getEvents } from "./abi"; - -export type FactoryCriteria = { - address: Address; - eventSelector: Hex; - childAddressLocation: "topic1" | "topic2" | "topic3" | `offset${number}`; - topics?: (Hex | Hex[] | null)[]; -}; - -export type Factory = { - name: string; - network: string; - chainId: number; - criteria: FactoryCriteria; - abi: Abi; - events: AbiEvents; - startBlock: number; - endBlock?: number; - maxBlockRange?: number; -}; - -export function buildFactories({ - config, - options, -}: { - config: ResolvedConfig; - options: Options; -}) { - const contracts = config.contracts ?? []; - - const factories = contracts - .filter( - ( - contract - ): contract is (typeof contracts)[number] & { - factory: NonNullable<(typeof contracts)[number]["factory"]>; - } => !!contract.factory - ) - .map((contract) => { - const criteria = buildFactoryCriteria(contract.factory); - - const { abi } = buildAbi({ - abiConfig: contract.abi, - configFilePath: options.configFile, - }); - - const childEvents = getEvents({ abi }); - - const network = config.networks.find((n) => n.name === contract.network); - if (!network) { - throw new Error( - `Network [${contract.network}] not found for factory contract: ${contract.name}` - ); - } - - return { - name: contract.name, - network: network.name, - chainId: network.chainId, - criteria, - abi, - events: childEvents, - startBlock: contract.startBlock ?? 0, - endBlock: contract.endBlock, - maxBlockRange: contract.maxBlockRange, - } satisfies Factory; - }); - - return factories; -} +import { FactoryCriteria } from "./sources"; export function buildFactoryCriteria({ address: _address, diff --git a/packages/core/src/config/logFilters.ts b/packages/core/src/config/logFilters.ts deleted file mode 100644 index 5d323984d..000000000 --- a/packages/core/src/config/logFilters.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { Abi, Address } from "abitype"; -import { type Hex, encodeEventTopics } from "viem"; - -import type { Options } from "@/config/options"; -import type { ResolvedConfig } from "@/config/types"; -import { toLowerCase } from "@/utils/lowercase"; - -import { AbiEvents, buildAbi, getEvents } from "./abi"; - -export type LogFilterCriteria = { - address?: Address | Address[]; - topics?: (Hex | Hex[] | null)[]; -}; - -export type LogFilter = { - name: string; - network: string; - chainId: number; - criteria: LogFilterCriteria; - abi: Abi; - events: AbiEvents; - startBlock: number; - endBlock?: number; - maxBlockRange?: number; -}; - -export function buildLogFilters({ - config, - options, -}: { - config: ResolvedConfig; - options: Options; -}) { - const contracts = config.contracts ?? []; - - const contractLogFilters = contracts - .filter( - ( - contract - ): contract is (typeof contracts)[number] & { address: Address } => - !!contract.address - ) - .filter((contract) => contract.isLogEventSource ?? true) - .map((contract) => { - const { abi } = buildAbi({ - abiConfig: contract.abi, - configFilePath: options.configFile, - }); - - const events = getEvents({ abi }); - - // Get the contract network/provider. - const network = config.networks.find((n) => n.name === contract.network); - if (!network) { - throw new Error( - `Network [${contract.network}] not found for contract: ${contract.name}` - ); - } - - const address = toLowerCase(contract.address); - const topics = undefined; - - return { - name: contract.name, - network: network.name, - chainId: network.chainId, - abi, - events, - criteria: { address, topics }, - startBlock: contract.startBlock ?? 0, - endBlock: contract.endBlock, - maxBlockRange: contract.maxBlockRange, - } satisfies LogFilter; - }); - - const filterLogFilters = (config.filters ?? []).map((filter) => { - const { abi } = buildAbi({ - abiConfig: filter.abi, - configFilePath: options.configFile, - }); - - const events = getEvents({ abi }); - - // Get the contract network/provider. - const network = config.networks.find((n) => n.name === filter.network); - if (!network) { - throw new Error( - `Network [${filter.network}] not found for filter: ${filter.name}` - ); - } - - const address = Array.isArray(filter.filter.address) - ? filter.filter.address.map(toLowerCase) - : typeof filter.filter.address === "string" - ? toLowerCase(filter.filter.address) - : undefined; - - const topics = filter.filter.event - ? encodeEventTopics({ - abi: [filter.filter.event], - eventName: filter.filter.event.name, - args: filter.filter.args as any, - }) - : undefined; - - return { - name: filter.name, - network: network.name, - chainId: network.chainId, - abi, - events, - criteria: { address, topics }, - startBlock: filter.startBlock ?? 0, - endBlock: filter.endBlock, - maxBlockRange: filter.maxBlockRange, - } satisfies LogFilter; - }); - - return (contractLogFilters as LogFilter[]).concat(filterLogFilters); -} diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts new file mode 100644 index 000000000..830d7419d --- /dev/null +++ b/packages/core/src/config/sources.ts @@ -0,0 +1,164 @@ +import { Abi, Address, encodeEventTopics, Hex } from "viem"; + +import { AbiEvents, getEvents } from "./abi"; +import { buildFactoryCriteria } from "./factories"; +import { Options } from "./options"; +import { ResolvedConfig } from "./types"; + +/** + * There are up to 4 topics in an EVM event + * + * Technically, only the element could be an array + */ +type Topics = [ + Hex | Hex[] | null, + Hex | Hex[] | null, + Hex | Hex[] | null, + Hex | Hex[] | null +]; + +export type LogFilterCriteria = { + address?: Address | Address[]; + topics?: Topics; +}; + +export type FactoryCriteria = { + address: Address; + eventSelector: Hex; + childAddressLocation: "topic1" | "topic2" | "topic3" | `offset${number}`; + topics?: Topics; +}; + +type BaseSource = { + name: string; + network: string; + chainId: number; + abi: Abi; + events: AbiEvents; + startBlock: number; + endBlock?: number; + maxBlockRange?: number; +}; + +export type LogFilter = BaseSource & { + type: "logFilter"; + criteria: LogFilterCriteria; +}; + +export type Factory = BaseSource & { + type: "factory"; + criteria: FactoryCriteria; +}; + +export type Source = LogFilter | Factory; + +export const sourceIsLogFilter = (source: Source): source is LogFilter => + source.type === "logFilter"; + +export const sourceIsFactory = (source: Source): source is Factory => + source.type === "factory"; + +export const buildSources = ({ + config, +}: { + config: ResolvedConfig; + options: Options; +}): Source[] => { + const contracts = config.contracts ?? []; + + return contracts + .map((contract) => { + // Note: should we filter down which indexing functions are available based on the filters + const events = getEvents({ abi: contract.abi }); + + // Resolve the contract per network, filling in default values where applicable + return contract.network + .map((networkContract) => { + // Note: this is missing config validation for checking if the network is valid + const network = config.networks.find( + (n) => n.name === networkContract.name + )!; + + const resolvedEvents = networkContract.event ?? contract.event; + + const topics = resolvedEvents + ? buildTopics(resolvedEvents) + : undefined; + + const sharedSource = { + // constants + name: contract.name, + abi: contract.abi, + network: network.name, + chainId: network.chainId, + events, + // optionally overridden properties + startBlock: networkContract.startBlock ?? contract.startBlock ?? 0, + endBlock: networkContract.endBlock ?? contract.endBlock, + maxBlockRange: + networkContract.maxBlockRange ?? contract.maxBlockRange, + } as const; + + if ("factory" in contract) { + // factory + + return { + ...sharedSource, + type: "factory", + criteria: { + ...buildFactoryCriteria(contract.factory), + topics, + }, + } as const satisfies Factory; + } else { + // log filter + + return { + ...sharedSource, + type: "logFilter", + criteria: { + address: contract.address, + topics, + }, + } as const satisfies LogFilter; + } + }) + .flat(); + }) + .flat(); +}; + +const buildTopics = ( + events: NonNullable[number]["event"]> +): Topics => { + if (Array.isArray(events)) { + // List of event signatures + return [ + events + .map((event) => + encodeEventTopics({ + abi: [event], + eventName: event.name, + }) + ) + .flat(), + null, + null, + null, + ]; + } else { + // Single event with args + const singleTopics = encodeEventTopics({ + abi: [events.signature], + eventName: events.signature.name, + args: events.args, + }); + + return [ + singleTopics[0] ?? null, + singleTopics[1] ?? null, + singleTopics[2] ?? null, + singleTopics[3] ?? null, + ]; + } +}; diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 23f02e201..5c3045375 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -1,6 +1,49 @@ -import type { AbiEvent } from "abitype"; +import type { Abi, AbiEvent } from "abitype"; import type { Transport } from "viem"; +type ContractRequired = { + /** Contract name. Must be unique across `contracts` and `filters`. */ + name: string; + /** + * Network that this contract is deployed to. Must match a network name in `networks`. + * Any filter information overrides the values in the higher level "contracts" property. Factories cannot override an address and vice versa. + */ + network: ({ name: string } & Partial)[]; + abi: Abi; +}; + +type ContractFilter = ( + | { + /** Contract address. */ + address?: `0x${string}`; + } + | { + /** Factory contract configuration. */ + factory: { + /** Address of the factory contract that creates this contract. */ + address: `0x${string}`; + /** ABI event that announces the creation of a new instance of this contract. */ + event: AbiEvent; + /** Name of the factory event parameter that contains the new child contract address. */ + parameter: string; // TODO: Narrow type to known parameter names from `event`. + }; + } +) & { + /** Block number at which to start indexing events (inclusive). Default: `0`. */ + startBlock?: number; + /** Block number at which to stop indexing events (inclusive). If `undefined`, events will be processed in real-time. Default: `undefined`. */ + endBlock?: number; + /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ + maxBlockRange?: number; + + event?: + | { + signature: AbiEvent; + args: any[]; + } + | AbiEvent[]; +}; + export type ResolvedConfig = { /** Database to use for storing blockchain & entity data. Default: `"postgres"` if `DATABASE_URL` env var is present, otherwise `"sqlite"`. */ database?: @@ -41,72 +84,7 @@ export type ResolvedConfig = { maxRpcRequestConcurrency?: number; }[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: ({ - /** Contract name. Must be unique across `contracts` and `filters`. */ - name: string; - /** Network that this contract is deployed to. Must match a network name in `networks`. */ - network: string; // TODO: narrow this type to TNetworks[number]['name'] - /** Contract ABI as a file path or an Array object. Accepts a single ABI or a list of ABIs to be merged. */ - abi: string | any[] | readonly any[] | (string | any[] | readonly any[])[]; - } & ( - | { - /** Contract address. */ - address: `0x${string}`; - factory?: never; - } - | { - address?: never; - /** Factory contract configuration. */ - factory: { - /** Address of the factory contract that creates this contract. */ - address: `0x${string}`; - /** ABI event that announces the creation of a new instance of this contract. */ - event: AbiEvent; - /** Name of the factory event parameter that contains the new child contract address. */ - parameter: string; // TODO: Narrow type to known parameter names from `event`. - }; - } - ) & { - /** Block number at which to start indexing events (inclusive). Default: `0`. */ - startBlock?: number; - /** Block number at which to stop indexing events (inclusive). If `undefined`, events will be processed in real-time. Default: `undefined`. */ - endBlock?: number; - /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ - maxBlockRange?: number; - /** Whether to fetch & process event logs for this contract. If `false`, this contract will still be present in `context.contracts`. Default: `true`. */ - isLogEventSource?: boolean; - })[]; - /** List of log filters from which to sync & index event logs. */ - filters?: { - /** Filter name. Must be unique across `contracts` and `filters`. */ - name: string; - /** Network that this filter is deployed to. Must match a network name in `networks`. */ - network: string; // TODO: narrow this type to TNetworks[number]['name'] - /** Log filter ABI as a file path or an Array object. Accepts a single ABI or a list of ABIs to be merged. */ - abi: string | any[] | readonly any[] | (string | any[] | readonly any[])[]; - /** Log filter options. */ - filter: { - /** Contract addresses to include. If `undefined`, no filter will be applied. Default: `undefined`. */ - address?: `0x${string}` | `0x${string}`[]; - } & ( - | { - /** Event signature to include. If `undefined`, no filter will be applied. Default: `undefined`. */ - event?: AbiEvent; - /** Event arguments to include. If `undefined`, no filter will be applied. Default: `undefined`. */ - args?: any[]; - } - | { - event?: never; - args?: never; - } - ); - /** Block number at which to start indexing events (inclusive). Default: `0`. */ - startBlock?: number; - /** Block number at which to stop indexing events (inclusive). If `undefined`, events will be processed in real-time. Default: `undefined`. */ - endBlock?: number; - /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ - maxBlockRange?: number; - }[]; + contracts?: (ContractRequired & ContractFilter)[]; /** Configuration for Ponder internals. */ options?: { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ diff --git a/packages/core/src/historical-sync/service.ts b/packages/core/src/historical-sync/service.ts index caa350cd8..8cd06a8fd 100644 --- a/packages/core/src/historical-sync/service.ts +++ b/packages/core/src/historical-sync/service.ts @@ -15,6 +15,7 @@ import { import type { Factory } from "@/config/factories"; import type { LogFilter, LogFilterCriteria } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import { Source } from "@/config/sources"; import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import { formatEta, formatPercentage } from "@/utils/format"; @@ -87,8 +88,7 @@ export class HistoricalSyncService extends Emittery { * Service configuration. Will eventually be reloadable. */ private finalizedBlockNumber: number = null!; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; /** * Block progress trackers for each task type. @@ -126,22 +126,19 @@ export class HistoricalSyncService extends Emittery { common, eventStore, network, - logFilters = [], - factories = [], + sources = [], }: { common: Common; eventStore: EventStore; network: Network; - logFilters?: LogFilter[]; - factories?: Factory[]; + sources?: Source[]; }) { super(); this.common = common; this.eventStore = eventStore; this.network = network; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; this.queue = this.buildQueue(); @@ -966,10 +963,7 @@ export class HistoricalSyncService extends Emittery { await this.common.metrics.ponder_historical_completed_blocks.get() ).values; - const eventSourceNames = [ - ...this.logFilters.map((l) => l.name), - ...this.factories.map((f) => f.name), - ]; + const eventSourceNames = this.sources.map((s) => s.name); return eventSourceNames.map((name) => { const totalBlocks = totalBlocksMetric.find( diff --git a/packages/core/src/indexing/contract.test.ts b/packages/core/src/indexing/contract.test.ts index 6c65cf041..e2f098d53 100644 --- a/packages/core/src/indexing/contract.test.ts +++ b/packages/core/src/indexing/contract.test.ts @@ -1,136 +1,136 @@ -import { getFunctionSelector, hexToBigInt } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { usdcContractConfig } from "@/_test/constants"; -import { setupEventStore } from "@/_test/setup"; -import { publicClient } from "@/_test/utils"; -import type { Contract } from "@/config/contracts"; -import type { Network } from "@/config/networks"; - -import { buildReadOnlyContracts } from "./contract"; - -beforeEach((context) => setupEventStore(context)); - -const network: Network = { - name: "mainnet", - chainId: 1, - client: publicClient, - pollingInterval: 1_000, - defaultMaxBlockRange: 3, - finalityBlockCount: 10, - maxRpcRequestConcurrency: 10, -}; - -const contracts: Contract[] = [ - { - name: "USDC", - address: usdcContractConfig.address, - abi: usdcContractConfig.abi, - network: network, - }, -]; - -// Test data generated from Alchemy Composer. -const usdcTotalSupply16375000 = 40921687992499550n; -const usdcTotalSupply16380000 = 40695630049769550n; // This is "latest" for our test setup. - -test("getInjectedContract() returns data", async (context) => { - const { eventStore } = context; - - const readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => 16375000n, - eventStore, - }); - const contract = readOnlyContracts["USDC"]; - - const decimals = await contract.read.decimals(); - expect(decimals).toBe(6); -}); - -test("getInjectedContract() uses current block number if no overrides are provided", async (context) => { - const { eventStore } = context; - - const readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => 16375000n, - eventStore, - }); - const contract = readOnlyContracts["USDC"]; - - const totalSupply = await contract.read.totalSupply(); - - expect(totalSupply).toBe(usdcTotalSupply16375000); -}); - -test("getInjectedContract() caches the read result if no overrides are provided", async (context) => { - const { eventStore } = context; - - const callSpy = vi.spyOn(network.client, "call"); - - const readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => 16375000n, - eventStore, - }); - const contract = readOnlyContracts["USDC"]; - - await contract.read.totalSupply(); - - expect(callSpy).toHaveBeenCalledTimes(1); - - const cachedContractReadResult = await eventStore.getContractReadResult({ - address: contract.address, - blockNumber: 16375000n, - chainId: 1, - data: getFunctionSelector("totalSupply()"), - }); - - expect(cachedContractReadResult).not.toBeNull(); - expect(hexToBigInt(cachedContractReadResult!.result)).toBe( - usdcTotalSupply16375000 - ); - - expect(callSpy).toHaveBeenCalledTimes(1); -}); - -test("getInjectedContract() uses blockTag override if provided", async (context) => { - const { eventStore } = context; - - const readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => 16375000n, - eventStore, - }); - const contract = readOnlyContracts["USDC"]; - - const totalSupply = await contract.read.totalSupply({ - blockTag: "latest", - }); - - expect(totalSupply).toBe(usdcTotalSupply16380000); -}); - -test("getInjectedContract() does not cache data if blockTag override is provided", async (context) => { - const { eventStore } = context; - - const readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => 16375000n, - eventStore, - }); - const contract = readOnlyContracts["USDC"]; - - await contract.read.totalSupply({ - blockTag: "latest", - }); - - const cachedContractReadResult = await eventStore.getContractReadResult({ - address: contract.address, - blockNumber: 16375000n, - chainId: 1, - data: getFunctionSelector("totalSupply()"), - }); - - expect(cachedContractReadResult).toBeNull(); -}); +// import { getFunctionSelector, hexToBigInt } from "viem"; +// import { beforeEach, expect, test, vi } from "vitest"; + +// import { usdcContractConfig } from "@/_test/constants"; +// import { setupEventStore } from "@/_test/setup"; +// import { publicClient } from "@/_test/utils"; +// import type { Contract } from "@/config/contracts"; +// import type { Network } from "@/config/networks"; + +// import { buildReadOnlyContracts } from "./contract"; + +// beforeEach((context) => setupEventStore(context)); + +// const network: Network = { +// name: "mainnet", +// chainId: 1, +// client: publicClient, +// pollingInterval: 1_000, +// defaultMaxBlockRange: 3, +// finalityBlockCount: 10, +// maxRpcRequestConcurrency: 10, +// }; + +// const contracts: Contract[] = [ +// { +// name: "USDC", +// address: usdcContractConfig.address, +// abi: usdcContractConfig.abi, +// network: network, +// }, +// ]; + +// // Test data generated from Alchemy Composer. +// const usdcTotalSupply16375000 = 40921687992499550n; +// const usdcTotalSupply16380000 = 40695630049769550n; // This is "latest" for our test setup. + +// test("getInjectedContract() returns data", async (context) => { +// const { eventStore } = context; + +// const readOnlyContracts = buildReadOnlyContracts({ +// contracts, +// getCurrentBlockNumber: () => 16375000n, +// eventStore, +// }); +// const contract = readOnlyContracts["USDC"]; + +// const decimals = await contract.read.decimals(); +// expect(decimals).toBe(6); +// }); + +// test("getInjectedContract() uses current block number if no overrides are provided", async (context) => { +// const { eventStore } = context; + +// const readOnlyContracts = buildReadOnlyContracts({ +// contracts, +// getCurrentBlockNumber: () => 16375000n, +// eventStore, +// }); +// const contract = readOnlyContracts["USDC"]; + +// const totalSupply = await contract.read.totalSupply(); + +// expect(totalSupply).toBe(usdcTotalSupply16375000); +// }); + +// test("getInjectedContract() caches the read result if no overrides are provided", async (context) => { +// const { eventStore } = context; + +// const callSpy = vi.spyOn(network.client, "call"); + +// const readOnlyContracts = buildReadOnlyContracts({ +// contracts, +// getCurrentBlockNumber: () => 16375000n, +// eventStore, +// }); +// const contract = readOnlyContracts["USDC"]; + +// await contract.read.totalSupply(); + +// expect(callSpy).toHaveBeenCalledTimes(1); + +// const cachedContractReadResult = await eventStore.getContractReadResult({ +// address: contract.address, +// blockNumber: 16375000n, +// chainId: 1, +// data: getFunctionSelector("totalSupply()"), +// }); + +// expect(cachedContractReadResult).not.toBeNull(); +// expect(hexToBigInt(cachedContractReadResult!.result)).toBe( +// usdcTotalSupply16375000 +// ); + +// expect(callSpy).toHaveBeenCalledTimes(1); +// }); + +// test("getInjectedContract() uses blockTag override if provided", async (context) => { +// const { eventStore } = context; + +// const readOnlyContracts = buildReadOnlyContracts({ +// contracts, +// getCurrentBlockNumber: () => 16375000n, +// eventStore, +// }); +// const contract = readOnlyContracts["USDC"]; + +// const totalSupply = await contract.read.totalSupply({ +// blockTag: "latest", +// }); + +// expect(totalSupply).toBe(usdcTotalSupply16380000); +// }); + +// test("getInjectedContract() does not cache data if blockTag override is provided", async (context) => { +// const { eventStore } = context; + +// const readOnlyContracts = buildReadOnlyContracts({ +// contracts, +// getCurrentBlockNumber: () => 16375000n, +// eventStore, +// }); +// const contract = readOnlyContracts["USDC"]; + +// await contract.read.totalSupply({ +// blockTag: "latest", +// }); + +// const cachedContractReadResult = await eventStore.getContractReadResult({ +// address: contract.address, +// blockNumber: 16375000n, +// chainId: 1, +// data: getFunctionSelector("totalSupply()"), +// }); + +// expect(cachedContractReadResult).toBeNull(); +// }); diff --git a/packages/core/src/indexing/contract.ts b/packages/core/src/indexing/contract.ts index f1c7120dd..a76bf1600 100644 --- a/packages/core/src/indexing/contract.ts +++ b/packages/core/src/indexing/contract.ts @@ -1,151 +1,151 @@ -import { - type Abi, - type BaseError, - type CallParameters, - type GetContractReturnType, - type Hex, - type PublicClient, - type ReadContractParameters, - decodeFunctionResult, - encodeFunctionData, - getContract, - getContractError, -} from "viem"; - -import type { Contract } from "@/config/contracts"; -import type { EventStore } from "@/event-store/store"; - -export function buildReadOnlyContracts({ - contracts, - eventStore, - getCurrentBlockNumber, -}: { - contracts: Contract[]; - eventStore: EventStore; - getCurrentBlockNumber: () => bigint; -}): Record> { - return contracts.reduce< - Record> - >((acc, { name, abi, address, network }) => { - const { chainId, client: publicClient } = network; - - const readOnlyContract = getContract({ abi, address, publicClient }); - - readOnlyContract.read = new Proxy( - {}, - { - get(_, functionName: string) { - return async ( - ...parameters: [ - args?: readonly unknown[], - options?: Omit< - ReadContractParameters, - "abi" | "address" | "functionName" | "args" - > - ] - ) => { - const { args, options } = getFunctionParameters(parameters); - - // If the user specified a block tag, serve the request as normal (no caching). - if (options?.blockTag) { - return publicClient.readContract({ - abi, - address, - functionName, - args, - ...options, - } as ReadContractParameters); - } - - // If the user specified a block number, use it, otherwise use the - // block number of the current event being processed. - const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(); - - const calldata = encodeFunctionData({ abi, args, functionName }); - - const decodeRawResult = (rawResult: Hex) => { - try { - return decodeFunctionResult({ - abi, - args, - functionName, - data: rawResult, - }); - } catch (err) { - throw getContractError(err as BaseError, { - abi, - address, - args, - docsPath: "/docs/contract/readContract", - functionName, - }); - } - }; - - // Check if this request can be served from the cache. - const cachedContractReadResult = - await eventStore.getContractReadResult({ - address, - blockNumber, - chainId, - data: calldata, - }); - - if (cachedContractReadResult) { - return decodeRawResult(cachedContractReadResult.result); - } - - // Cache miss. Make the RPC request, then add to the cache. - let rawResult: Hex; - try { - const { data } = await publicClient.call({ - data: calldata, - to: address, - ...{ - ...options, - blockNumber, - }, - } as unknown as CallParameters); - - rawResult = data || "0x"; - } catch (err) { - throw getContractError(err as BaseError, { - abi, - address, - args, - docsPath: "/docs/contract/readContract", - functionName, - }); - } - - await eventStore.insertContractReadResult({ - address, - blockNumber, - chainId, - data: calldata, - result: rawResult, - }); - - return decodeRawResult(rawResult); - }; - }, - } - ); - - acc[name] = readOnlyContract; - - return acc; - }, {}); -} - -function getFunctionParameters( - values: [args?: readonly unknown[], options?: object] -) { - const hasArgs = values.length && Array.isArray(values[0]); - const args = hasArgs ? values[0]! : []; - const options = ((hasArgs ? values[1] : values[0]) ?? {}) as Omit< - ReadContractParameters, - "abi" | "address" | "functionName" | "args" - >; - return { args, options }; -} +// import { +// type Abi, +// type BaseError, +// type CallParameters, +// type GetContractReturnType, +// type Hex, +// type PublicClient, +// type ReadContractParameters, +// decodeFunctionResult, +// encodeFunctionData, +// getContract, +// getContractError, +// } from "viem"; + +// // import type { Contract } from "@/config/contracts"; +// import type { EventStore } from "@/event-store/store"; + +// export function buildReadOnlyContracts({ +// contracts, +// eventStore, +// getCurrentBlockNumber, +// }: { +// contracts: Contract[]; +// eventStore: EventStore; +// getCurrentBlockNumber: () => bigint; +// }): Record> { +// return contracts.reduce< +// Record> +// >((acc, { name, abi, address, network }) => { +// const { chainId, client: publicClient } = network; + +// const readOnlyContract = getContract({ abi, address, publicClient }); + +// readOnlyContract.read = new Proxy( +// {}, +// { +// get(_, functionName: string) { +// return async ( +// ...parameters: [ +// args?: readonly unknown[], +// options?: Omit< +// ReadContractParameters, +// "abi" | "address" | "functionName" | "args" +// > +// ] +// ) => { +// const { args, options } = getFunctionParameters(parameters); + +// // If the user specified a block tag, serve the request as normal (no caching). +// if (options?.blockTag) { +// return publicClient.readContract({ +// abi, +// address, +// functionName, +// args, +// ...options, +// } as ReadContractParameters); +// } + +// // If the user specified a block number, use it, otherwise use the +// // block number of the current event being processed. +// const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(); + +// const calldata = encodeFunctionData({ abi, args, functionName }); + +// const decodeRawResult = (rawResult: Hex) => { +// try { +// return decodeFunctionResult({ +// abi, +// args, +// functionName, +// data: rawResult, +// }); +// } catch (err) { +// throw getContractError(err as BaseError, { +// abi, +// address, +// args, +// docsPath: "/docs/contract/readContract", +// functionName, +// }); +// } +// }; + +// // Check if this request can be served from the cache. +// const cachedContractReadResult = +// await eventStore.getContractReadResult({ +// address, +// blockNumber, +// chainId, +// data: calldata, +// }); + +// if (cachedContractReadResult) { +// return decodeRawResult(cachedContractReadResult.result); +// } + +// // Cache miss. Make the RPC request, then add to the cache. +// let rawResult: Hex; +// try { +// const { data } = await publicClient.call({ +// data: calldata, +// to: address, +// ...{ +// ...options, +// blockNumber, +// }, +// } as unknown as CallParameters); + +// rawResult = data || "0x"; +// } catch (err) { +// throw getContractError(err as BaseError, { +// abi, +// address, +// args, +// docsPath: "/docs/contract/readContract", +// functionName, +// }); +// } + +// await eventStore.insertContractReadResult({ +// address, +// blockNumber, +// chainId, +// data: calldata, +// result: rawResult, +// }); + +// return decodeRawResult(rawResult); +// }; +// }, +// } +// ); + +// acc[name] = readOnlyContract; + +// return acc; +// }, {}); +// } + +// function getFunctionParameters( +// values: [args?: readonly unknown[], options?: object] +// ) { +// const hasArgs = values.length && Array.isArray(values[0]); +// const args = hasArgs ? values[0]! : []; +// const options = ((hasArgs ? values[1] : values[0]) ?? {}) as Omit< +// ReadContractParameters, +// "abi" | "address" | "functionName" | "args" +// >; +// return { args, options }; +// } diff --git a/packages/core/src/indexing/service.test.ts b/packages/core/src/indexing/service.test.ts index 201c8aad3..c6d67452f 100644 --- a/packages/core/src/indexing/service.test.ts +++ b/packages/core/src/indexing/service.test.ts @@ -35,7 +35,7 @@ const logFilters = [ }, ]; -const contracts = [{ name: "USDC", ...usdcContractConfig, network }]; +// const contracts = [{ name: "USDC", ...usdcContractConfig, network }]; const schema = buildSchema( buildGraphqlSchema(`${schemaHeader} @@ -123,7 +123,7 @@ test("processEvents() calls getEvents with sequential timestamp ranges", async ( eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -162,7 +162,7 @@ test("processEvents() calls indexing functions with correct arguments", async (c eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -200,7 +200,7 @@ test("processEvents() model methods insert data into the user store", async (con eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -225,7 +225,7 @@ test("processEvents() updates event count metrics", async (context) => { eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -267,7 +267,7 @@ test("reset() reloads the user store", async (context) => { eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -303,7 +303,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -335,7 +335,7 @@ test("handleReorg() reverts the user store", async (context) => { eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -361,7 +361,7 @@ test("handleReorg() does nothing if there is a user error", async (context) => { eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -391,7 +391,7 @@ test("handleReorg() processes the correct range of events after a reorg", async eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); @@ -431,7 +431,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters, }); diff --git a/packages/core/src/indexing/service.ts b/packages/core/src/indexing/service.ts index f76c3d0c5..be3ed4a19 100644 --- a/packages/core/src/indexing/service.ts +++ b/packages/core/src/indexing/service.ts @@ -3,7 +3,6 @@ import Emittery from "emittery"; import type { IndexingFunctions } from "@/build/functions"; import { LogEventMetadata } from "@/config/abi"; -import type { Contract } from "@/config/contracts"; import { Factory } from "@/config/factories"; import type { LogFilter } from "@/config/logFilters"; import { UserError } from "@/errors/user"; @@ -22,7 +21,6 @@ import { prettyPrint } from "@/utils/print"; import { type Queue, type Worker, createQueue } from "@/utils/queue"; import { wait } from "@/utils/wait"; -import { buildReadOnlyContracts } from "./contract"; import { buildModels } from "./model"; import { getStackTrace } from "./trace"; @@ -56,15 +54,15 @@ export class IndexingService extends Emittery { private eventsProcessedToTimestamp = 0; private hasError = false; - private currentEventBlockNumber = 0n; + // private currentEventBlockNumber = 0n; private currentEventTimestamp = 0; constructor({ common, - eventStore, + // eventStore, userStore, eventAggregatorService, - contracts, + // contracts, logFilters = [], factories = [], }: { @@ -72,7 +70,6 @@ export class IndexingService extends Emittery { eventStore: EventStore; userStore: UserStore; eventAggregatorService: EventAggregatorService; - contracts: Contract[]; logFilters?: LogFilter[]; factories?: Factory[]; }) { @@ -85,11 +82,11 @@ export class IndexingService extends Emittery { // The read-only contract objects only depend on config, so they can // be built in the constructor (they can't be hot-reloaded). - this.readOnlyContracts = buildReadOnlyContracts({ - contracts, - getCurrentBlockNumber: () => this.currentEventBlockNumber, - eventStore, - }); + // this.readOnlyContracts = buildReadOnlyContracts({ + // contracts, + // getCurrentBlockNumber: () => this.currentEventBlockNumber, + // eventStore, + // }); this.eventProcessingMutex = new Mutex(); } @@ -452,7 +449,7 @@ export class IndexingService extends Emittery { // This enables contract calls occurring within the // user code to use the event block number by default. - this.currentEventBlockNumber = event.block.number; + // this.currentEventBlockNumber = event.block.number; this.currentEventTimestamp = Number(event.block.timestamp); try { diff --git a/packages/core/src/realtime-sync/service.ts b/packages/core/src/realtime-sync/service.ts index 90d7b4593..04d3d6308 100644 --- a/packages/core/src/realtime-sync/service.ts +++ b/packages/core/src/realtime-sync/service.ts @@ -8,9 +8,8 @@ import { numberToHex, } from "viem"; -import type { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import type { Source } from "@/config/sources"; import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import { poll } from "@/utils/poll"; @@ -40,8 +39,7 @@ export class RealtimeSyncService extends Emittery { private common: Common; private eventStore: EventStore; private network: Network; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; // Queue of unprocessed blocks. private queue: RealtimeSyncQueue; @@ -56,22 +54,19 @@ export class RealtimeSyncService extends Emittery { common, eventStore, network, - logFilters = [], - factories = [], + sources = [], }: { common: Common; eventStore: EventStore; network: Network; - logFilters?: LogFilter[]; - factories?: Factory[]; + sources?: Source[]; }) { super(); this.common = common; this.eventStore = eventStore; this.network = network; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; this.queue = this.buildQueue(); } @@ -111,9 +106,7 @@ export class RealtimeSyncService extends Emittery { // If an endBlock is specified for every event source on this network, and the // latest end blcock is less than the finalized block number, we can stop here. // The service won't poll for new blocks and won't emit any events. - const endBlocks = [...this.logFilters, ...this.factories].map( - (f) => f.endBlock - ); + const endBlocks = this.sources.map((f) => f.endBlock); if ( endBlocks.every( (endBlock) => diff --git a/packages/core/src/ui/app.tsx b/packages/core/src/ui/app.tsx index f3b43ec34..87cbbd743 100644 --- a/packages/core/src/ui/app.tsx +++ b/packages/core/src/ui/app.tsx @@ -1,8 +1,7 @@ import { Box, Newline, render as inkRender, Text } from "ink"; import React from "react"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import { Source } from "@/config/sources"; import { HistoricalBar } from "./HistoricalBar"; import { IndexingBar } from "./IndexingBar"; @@ -25,13 +24,7 @@ export type UiState = { networks: string[]; }; -export const buildUiState = ({ - logFilters, - factories, -}: { - logFilters: LogFilter[]; - factories: Factory[]; -}) => { +export const buildUiState = ({ sources }: { sources: Source[] }) => { const ui: UiState = { port: 0, @@ -48,10 +41,7 @@ export const buildUiState = ({ networks: [], }; - const eventSourceNames = [ - ...logFilters.map((l) => l.name), - ...factories.map((f) => f.name), - ]; + const eventSourceNames = sources.map((s) => s.name); eventSourceNames.forEach((name) => { ui.historicalSyncEventSourceStats[name] = { diff --git a/packages/core/src/ui/service.ts b/packages/core/src/ui/service.ts index 301c64de0..78efaeaa6 100644 --- a/packages/core/src/ui/service.ts +++ b/packages/core/src/ui/service.ts @@ -1,35 +1,23 @@ -import type { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import { Source } from "@/config/sources"; import type { Common } from "@/Ponder"; import { type UiState, buildUiState, setupInkApp } from "./app"; export class UiService { private common: Common; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; ui: UiState; renderInterval: NodeJS.Timer; render: () => void; unmount: () => void; - constructor({ - common, - logFilters, - factories, - }: { - common: Common; - logFilters: LogFilter[]; - factories: Factory[]; - }) { + constructor({ common, sources }: { common: Common; sources: Source[] }) { this.common = common; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; this.ui = buildUiState({ - logFilters: this.logFilters, - factories: this.factories, + sources: this.sources, }); if (this.common.options.uiEnabled) { diff --git a/packages/core/src/utils/fragments.ts b/packages/core/src/utils/fragments.ts index 775b90c34..c30575613 100644 --- a/packages/core/src/utils/fragments.ts +++ b/packages/core/src/utils/fragments.ts @@ -1,7 +1,6 @@ import type { Address, Hex } from "viem"; -import { FactoryCriteria } from "@/config/factories"; -import type { LogFilterCriteria } from "@/config/logFilters"; +import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; /** * Generates log filter fragments from a log filter. From f794dae3d50b1ffb9b4c5e23599cbf4a364af0b6 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 11:03:29 -0400 Subject: [PATCH 07/44] more work more progress --- packages/core/src/build/functions.ts | 12 +- packages/core/src/codegen/event.ts | 81 +--- packages/core/src/config/sources.ts | 4 +- packages/core/src/event-aggregator/service.ts | 19 +- .../core/src/event-store/postgres/store.ts | 9 +- packages/core/src/event-store/sqlite/store.ts | 9 +- packages/core/src/event-store/store.test.ts | 34 +- packages/core/src/event-store/store.ts | 3 +- packages/core/src/historical-sync/service.ts | 452 +++++++++--------- packages/core/src/indexing/service.ts | 27 +- packages/core/src/realtime-sync/service.ts | 26 +- 11 files changed, 341 insertions(+), 335 deletions(-) diff --git a/packages/core/src/build/functions.ts b/packages/core/src/build/functions.ts index 0503f0690..a2818efdd 100644 --- a/packages/core/src/build/functions.ts +++ b/packages/core/src/build/functions.ts @@ -222,17 +222,17 @@ export const hydrateIndexingFunctions = ({ Object.entries(rawIndexingFunctions.eventSources).forEach( ([eventSourceName, eventSourceFunctions]) => { - const logFilter = logFilters.find((l) => l.name === eventSourceName); - const factory = factories.find((f) => f.name === eventSourceName); + // const logFilter = logFilters.find((l) => l.name === eventSourceName); + // const factory = factories.find((f) => f.name === eventSourceName); - if (!logFilter && !factory) { + const source = sources.find((source) => source.name === eventSourceName); + + if (!source) { throw new Error(`Event source not found in config: ${eventSourceName}`); } Object.entries(eventSourceFunctions).forEach(([eventName, fn]) => { - const eventData = logFilter - ? logFilter.events[eventName] - : factory?.events[eventName]; + const eventData = source.events[eventName]; if (!eventData) { throw new Error(`Log event not found in ABI: ${eventName}`); diff --git a/packages/core/src/codegen/event.ts b/packages/core/src/codegen/event.ts index b2232e7d1..f4180eede 100644 --- a/packages/core/src/codegen/event.ts +++ b/packages/core/src/codegen/event.ts @@ -2,62 +2,33 @@ import type { LogEventMetadata } from "@/config/abi"; import { Source } from "@/config/sources"; export const buildEventTypes = ({ sources }: { sources: Source[] }) => { - const allIndexingFunctions = [ - ...logFilters.map((logFilter) => { - return Object.values(logFilter.events) - .filter((val): val is LogEventMetadata => !!val) - .map(({ safeName, abiItem }) => { - const paramsType = `{${abiItem.inputs - .map((input, index) => { - const inputName = input.name ? input.name : `param_${index}`; - return `${inputName}: - AbiParameterToPrimitiveType<${JSON.stringify(input)}>`; - }) - .join(";")}}`; + const allIndexingFunctions = sources.map((source) => + Object.values(source.events) + .filter((val): val is LogEventMetadata => !!val) + .map(({ safeName, abiItem }) => { + const paramsType = `{${abiItem.inputs + .map((input, index) => { + const inputName = input.name ? input.name : `param_${index}`; + return `${inputName}: + AbiParameterToPrimitiveType<${JSON.stringify(input)}>`; + }) + .join(";")}}`; - return `["${logFilter.name}:${safeName}"]: ({ - event, context - }: { - event: { - name: "${abiItem.name}"; - params: ${paramsType}; - log: Log; - block: Block; - transaction: Transaction; - }; - context: Context; - }) => Promise | any;`; - }) - .join(""); - }), - ...factories.map((factory) => { - return Object.values(factory.events) - .filter((val): val is LogEventMetadata => !!val) - .map(({ safeName, abiItem }) => { - const paramsType = `{${abiItem.inputs - .map((input, index) => { - const inputName = input.name ? input.name : `param_${index}`; - return `${inputName}: - AbiParameterToPrimitiveType<${JSON.stringify(input)}>`; - }) - .join(";")}}`; - - return `["${factory.name}:${safeName}"]: ({ - event, context - }: { - event: { - name: "${abiItem.name}"; - params: ${paramsType}; - log: Log; - block: Block; - transaction: Transaction; - }; - context: Context; - }) => Promise | any;`; - }) - .join(""); - }), - ]; + return `["${source.name}:${safeName}"]: ({ + event, context + }: { + event: { + name: "${abiItem.name}"; + params: ${paramsType}; + log: Log; + block: Block; + transaction: Transaction; + }; + context: Context; + }) => Promise | any;`; + }) + .join("") + ); allIndexingFunctions.unshift( `["setup"]: ({ context }: { context: Context; }) => Promise | any;` diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 830d7419d..8a57612d1 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -8,9 +8,9 @@ import { ResolvedConfig } from "./types"; /** * There are up to 4 topics in an EVM event * - * Technically, only the element could be an array + * Technically, only the first element could be an array */ -type Topics = [ +export type Topics = [ Hex | Hex[] | null, Hex | Hex[] | null, Hex | Hex[] | null, diff --git a/packages/core/src/event-aggregator/service.ts b/packages/core/src/event-aggregator/service.ts index 97f0a9117..544d1f78b 100644 --- a/packages/core/src/event-aggregator/service.ts +++ b/packages/core/src/event-aggregator/service.ts @@ -2,9 +2,8 @@ import Emittery from "emittery"; import { type Hex, decodeEventLog } from "viem"; import { LogEventMetadata } from "@/config/abi"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import { Source, sourceIsFactory, sourceIsLogFilter } from "@/config/sources"; import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import type { Block } from "@/types/block"; @@ -44,8 +43,7 @@ export class EventAggregatorService extends Emittery { private common: Common; private eventStore: EventStore; private networks: Network[]; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; // Minimum timestamp at which events are available (across all networks). checkpoint: number; @@ -72,22 +70,19 @@ export class EventAggregatorService extends Emittery { common, eventStore, networks, - logFilters = [], - factories = [], + sources = [], }: { common: Common; eventStore: EventStore; networks: Network[]; - logFilters?: LogFilter[]; - factories?: Factory[]; + sources?: Source[]; }) { super(); this.common = common; this.eventStore = eventStore; this.networks = networks; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; this.metrics = {}; this.checkpoint = 0; @@ -129,7 +124,7 @@ export class EventAggregatorService extends Emittery { const iterator = this.eventStore.getLogEvents({ fromTimestamp, toTimestamp, - logFilters: this.logFilters.map((logFilter) => ({ + logFilters: this.sources.filter(sourceIsLogFilter).map((logFilter) => ({ name: logFilter.name, chainId: logFilter.chainId, criteria: logFilter.criteria, @@ -139,7 +134,7 @@ export class EventAggregatorService extends Emittery { indexingMetadata[logFilter.name]?.bySelector ?? {} ) as Hex[], })), - factories: this.factories.map((factory) => ({ + factories: this.sources.filter(sourceIsFactory).map((factory) => ({ name: factory.name, chainId: factory.chainId, criteria: factory.criteria, diff --git a/packages/core/src/event-store/postgres/store.ts b/packages/core/src/event-store/postgres/store.ts index 3075812ac..bddc858c1 100644 --- a/packages/core/src/event-store/postgres/store.ts +++ b/packages/core/src/event-store/postgres/store.ts @@ -10,8 +10,11 @@ import { import type { Pool } from "pg"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import { type FactoryCriteria } from "@/config/factories"; -import type { LogFilterCriteria } from "@/config/logFilters"; +import type { + FactoryCriteria, + LogFilterCriteria, + Topics, +} from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; @@ -541,7 +544,7 @@ export class PostgresEventStore implements EventStore { ...logFilters, ...factories.map((f) => ({ address: f.address, - topics: [f.eventSelector], + topics: [f.eventSelector, null, null, null] as Topics, })), ], interval, diff --git a/packages/core/src/event-store/sqlite/store.ts b/packages/core/src/event-store/sqlite/store.ts index ab04ae122..881ef2151 100644 --- a/packages/core/src/event-store/sqlite/store.ts +++ b/packages/core/src/event-store/sqlite/store.ts @@ -9,8 +9,11 @@ import { } from "kysely"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import { type FactoryCriteria } from "@/config/factories"; -import type { LogFilterCriteria } from "@/config/logFilters"; +import type { + FactoryCriteria, + LogFilterCriteria, + Topics, +} from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; @@ -509,7 +512,7 @@ export class SqliteEventStore implements EventStore { ...logFilters, ...factories.map((f) => ({ address: f.address, - topics: [f.eventSelector], + topics: [f.eventSelector, null, null, null] as Topics, })), ], interval, diff --git a/packages/core/src/event-store/store.test.ts b/packages/core/src/event-store/store.test.ts index b1273fc15..cb1766262 100644 --- a/packages/core/src/event-store/store.test.ts +++ b/packages/core/src/event-store/store.test.ts @@ -12,8 +12,7 @@ import { usdcContractConfig, } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; -import type { FactoryCriteria } from "@/config/factories"; -import type { LogFilterCriteria } from "@/config/logFilters"; +import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; beforeEach((context) => setupEventStore(context)); @@ -246,7 +245,7 @@ test("getLogFilterRanges handles complex log filter inclusivity rules", async (c await eventStore.insertLogFilterInterval({ chainId: 1, - logFilter: { topics: [null, ["0xc", "0xd"]] }, + logFilter: { topics: [null, ["0xc", "0xd"], null, null] }, block: blockOne, transactions: [], logs: [], @@ -263,7 +262,7 @@ test("getLogFilterRanges handles complex log filter inclusivity rules", async (c // Narrower criteria includes both broad and specific intervals. logFilterIntervals = await eventStore.getLogFilterIntervals({ chainId: 1, - logFilter: { topics: [null, "0xc"] }, + logFilter: { topics: [null, "0xc", null, null] }, }); expect(logFilterIntervals).toMatchObject([ [0, 100], @@ -671,6 +670,9 @@ test("getFactoryLogFilterIntervals handles topic filtering rules", async (contex ...factoryCriteria, topics: [ "0x0000000000000000000000000000000000000000000factoryeventsignature", + null, + null, + null, ], } as FactoryCriteria, }); @@ -740,7 +742,7 @@ test("insertRealtimeInterval inserts log filter intervals", async (context) => { chainId: 1, logFilter: { address: factoryCriteriaOne.address, - topics: [factoryCriteriaOne.eventSelector], + topics: [factoryCriteriaOne.eventSelector, null, null, null], }, }) ).toMatchObject([[500, 550]]); @@ -749,7 +751,7 @@ test("insertRealtimeInterval inserts log filter intervals", async (context) => { chainId: 1, logFilter: { address: factoryCriteriaOne.address, - topics: [factoryCriteriaOne.eventSelector], + topics: [factoryCriteriaOne.eventSelector, null, null, null], }, }) ).toMatchObject([[500, 550]]); @@ -1263,7 +1265,14 @@ test("getLogEvents filters on log filter with single topic", async (context) => { name: "singleTopic", chainId: 1, - criteria: { topics: [blockOneLogs[0].topics[0] as `0x${string}`] }, + criteria: { + topics: [ + blockOneLogs[0].topics[0] as `0x${string}`, + null, + null, + null, + ], + }, }, ], }); @@ -1313,6 +1322,8 @@ test("getLogEvents filters on log filter with multiple topics", async (context) topics: [ blockOneLogs[0].topics[0] as `0x${string}`, blockOneLogs[0].topics[1] as `0x${string}`, + null, + null, ], }, }, @@ -1454,7 +1465,14 @@ test("getLogEvents filters on multiple filters", async (context) => { { name: "singleTopic", // This should match blockOneLogs[0] AND blockTwoLogs[0] chainId: 1, - criteria: { topics: [blockOneLogs[0].topics[0] as `0x${string}`] }, + criteria: { + topics: [ + blockOneLogs[0].topics[0] as `0x${string}`, + null, + null, + null, + ], + }, }, ], }); diff --git a/packages/core/src/event-store/store.ts b/packages/core/src/event-store/store.ts index f5275d761..2c01c2882 100644 --- a/packages/core/src/event-store/store.ts +++ b/packages/core/src/event-store/store.ts @@ -1,8 +1,7 @@ import type { Kysely, Migrator } from "kysely"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import { FactoryCriteria } from "@/config/factories"; -import { LogFilterCriteria } from "@/config/logFilters"; +import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; diff --git a/packages/core/src/historical-sync/service.ts b/packages/core/src/historical-sync/service.ts index 8cd06a8fd..acecd6629 100644 --- a/packages/core/src/historical-sync/service.ts +++ b/packages/core/src/historical-sync/service.ts @@ -12,10 +12,14 @@ import { toHex, } from "viem"; -import type { Factory } from "@/config/factories"; -import type { LogFilter, LogFilterCriteria } from "@/config/logFilters"; import type { Network } from "@/config/networks"; -import { Source } from "@/config/sources"; +import { + type Factory, + type LogFilter, + type LogFilterCriteria, + type Source, + sourceIsLogFilter, +} from "@/config/sources"; import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import { formatEta, formatPercentage } from "@/utils/format"; @@ -154,252 +158,264 @@ export class HistoricalSyncService extends Emittery { }) { this.finalizedBlockNumber = finalizedBlockNumber; - await Promise.all([ - ...this.logFilters.map(async (logFilter) => { + await Promise.all( + this.sources.map(async (source) => { const { isHistoricalSyncRequired, startBlock, endBlock } = validateHistoricalBlockRange({ - startBlock: logFilter.startBlock, - endBlock: logFilter.endBlock, + startBlock: source.startBlock, + endBlock: source.endBlock, finalizedBlockNumber, latestBlockNumber, }); - if (!isHistoricalSyncRequired) { - this.logFilterProgressTrackers[logFilter.name] = new ProgressTracker({ - target: [startBlock, finalizedBlockNumber], - completed: [[startBlock, finalizedBlockNumber]], - }); - this.common.metrics.ponder_historical_total_blocks.set( - { network: this.network.name, eventSource: logFilter.name }, - 0 - ); - this.common.logger.warn({ - service: "historical", - msg: `Start block is in unfinalized range, skipping historical sync (eventSource=${logFilter.name})`, + if (sourceIsLogFilter(source)) { + // Log filter + + if (!isHistoricalSyncRequired) { + this.logFilterProgressTrackers[source.name] = new ProgressTracker({ + target: [startBlock, finalizedBlockNumber], + completed: [[startBlock, finalizedBlockNumber]], + }); + this.common.metrics.ponder_historical_total_blocks.set( + { network: this.network.name, eventSource: source.name }, + 0 + ); + this.common.logger.warn({ + service: "historical", + msg: `Start block is in unfinalized range, skipping historical sync (eventSource=${source.name})`, + }); + return; + } + + const completedLogFilterIntervals = + await this.eventStore.getLogFilterIntervals({ + chainId: source.chainId, + logFilter: { + address: source.criteria.address, + topics: source.criteria.topics, + }, + }); + const logFilterProgressTracker = new ProgressTracker({ + target: [startBlock, endBlock], + completed: completedLogFilterIntervals, }); - return; - } + this.logFilterProgressTrackers[source.name] = + logFilterProgressTracker; - const completedLogFilterIntervals = - await this.eventStore.getLogFilterIntervals({ - chainId: logFilter.chainId, - logFilter: { - address: logFilter.criteria.address, - topics: logFilter.criteria.topics, - }, + const requiredLogFilterIntervals = + logFilterProgressTracker.getRequired(); + + const logFilterTaskChunks = getChunks({ + intervals: requiredLogFilterIntervals, + maxChunkSize: + source.maxBlockRange ?? this.network.defaultMaxBlockRange, }); - const logFilterProgressTracker = new ProgressTracker({ - target: [startBlock, endBlock], - completed: completedLogFilterIntervals, - }); - this.logFilterProgressTrackers[logFilter.name] = - logFilterProgressTracker; - const requiredLogFilterIntervals = - logFilterProgressTracker.getRequired(); + for (const [fromBlock, toBlock] of logFilterTaskChunks) { + this.queue.addTask( + { kind: "LOG_FILTER", logFilter: source, fromBlock, toBlock }, + { priority: Number.MAX_SAFE_INTEGER - fromBlock } + ); + } + if (logFilterTaskChunks.length > 0) { + const total = intervalSum(requiredLogFilterIntervals); + this.common.logger.debug({ + service: "historical", + msg: `Added LOG_FILTER tasks for ${total}-block range (logFilter=${source.name}, network=${this.network.name})`, + }); + } - const logFilterTaskChunks = getChunks({ - intervals: requiredLogFilterIntervals, - maxChunkSize: - logFilter.maxBlockRange ?? this.network.defaultMaxBlockRange, - }); + const targetBlockCount = endBlock - startBlock + 1; + const cachedBlockCount = intervalSum(completedLogFilterIntervals); - for (const [fromBlock, toBlock] of logFilterTaskChunks) { - this.queue.addTask( - { kind: "LOG_FILTER", logFilter, fromBlock, toBlock }, - { priority: Number.MAX_SAFE_INTEGER - fromBlock } + this.common.metrics.ponder_historical_total_blocks.set( + { network: this.network.name, eventSource: source.name }, + targetBlockCount ); - } - if (logFilterTaskChunks.length > 0) { - const total = intervalSum(requiredLogFilterIntervals); - this.common.logger.debug({ + this.common.metrics.ponder_historical_cached_blocks.set( + { network: this.network.name, eventSource: source.name }, + cachedBlockCount + ); + + this.common.logger.info({ service: "historical", - msg: `Added LOG_FILTER tasks for ${total}-block range (logFilter=${logFilter.name}, network=${this.network.name})`, + msg: `Started sync with ${formatPercentage( + Math.min(1, cachedBlockCount / (targetBlockCount || 1)) + )} cached (eventSource=${source.name} network=${ + this.network.name + })`, }); - } - - const targetBlockCount = endBlock - startBlock + 1; - const cachedBlockCount = intervalSum(completedLogFilterIntervals); - - this.common.metrics.ponder_historical_total_blocks.set( - { network: this.network.name, eventSource: logFilter.name }, - targetBlockCount - ); - this.common.metrics.ponder_historical_cached_blocks.set( - { network: this.network.name, eventSource: logFilter.name }, - cachedBlockCount - ); + } else { + // Factory + + if (!isHistoricalSyncRequired) { + this.factoryChildAddressProgressTrackers[source.name] = + new ProgressTracker({ + target: [startBlock, finalizedBlockNumber], + completed: [[startBlock, finalizedBlockNumber]], + }); + this.factoryLogFilterProgressTrackers[source.name] = + new ProgressTracker({ + target: [startBlock, finalizedBlockNumber], + completed: [[startBlock, finalizedBlockNumber]], + }); + this.common.metrics.ponder_historical_total_blocks.set( + { network: this.network.name, eventSource: source.name }, + 0 + ); + this.common.logger.warn({ + service: "historical", + msg: `Start block is in unfinalized range, skipping historical sync (eventSource=${source.name})`, + }); + return; + } - this.common.logger.info({ - service: "historical", - msg: `Started sync with ${formatPercentage( - Math.min(1, cachedBlockCount / (targetBlockCount || 1)) - )} cached (eventSource=${logFilter.name} network=${ - this.network.name - })`, - }); - }), - ...this.factories.map(async (factory) => { - const { isHistoricalSyncRequired, startBlock, endBlock } = - validateHistoricalBlockRange({ - startBlock: factory.startBlock, - endBlock: factory.endBlock, - finalizedBlockNumber, - latestBlockNumber, + // Note that factory child address progress is stored using + // log intervals for the factory log. + const completedFactoryChildAddressIntervals = + await this.eventStore.getLogFilterIntervals({ + chainId: source.chainId, + logFilter: { + address: source.criteria.address, + topics: [source.criteria.eventSelector, null, null, null], + }, + }); + const factoryChildAddressProgressTracker = new ProgressTracker({ + target: [startBlock, endBlock], + completed: completedFactoryChildAddressIntervals, + }); + this.factoryChildAddressProgressTrackers[source.name] = + factoryChildAddressProgressTracker; + + const requiredFactoryChildAddressIntervals = + factoryChildAddressProgressTracker.getRequired(); + const factoryChildAddressTaskChunks = getChunks({ + intervals: requiredFactoryChildAddressIntervals, + maxChunkSize: + source.maxBlockRange ?? this.network.defaultMaxBlockRange, }); - if (!isHistoricalSyncRequired) { - this.factoryChildAddressProgressTrackers[factory.name] = - new ProgressTracker({ - target: [startBlock, finalizedBlockNumber], - completed: [[startBlock, finalizedBlockNumber]], - }); - this.factoryLogFilterProgressTrackers[factory.name] = - new ProgressTracker({ - target: [startBlock, finalizedBlockNumber], - completed: [[startBlock, finalizedBlockNumber]], + for (const [fromBlock, toBlock] of factoryChildAddressTaskChunks) { + this.queue.addTask( + { + kind: "FACTORY_CHILD_ADDRESS", + factory: source, + fromBlock, + toBlock, + }, + { priority: Number.MAX_SAFE_INTEGER - fromBlock } + ); + } + if (factoryChildAddressTaskChunks.length > 0) { + const total = intervalSum(requiredFactoryChildAddressIntervals); + this.common.logger.debug({ + service: "historical", + msg: `Added FACTORY_CHILD_ADDRESS tasks for ${total}-block range (factory=${source.name}, network=${this.network.name})`, }); - this.common.metrics.ponder_historical_total_blocks.set( - { network: this.network.name, eventSource: factory.name }, - 0 + } + + const targetFactoryChildAddressBlockCount = endBlock - startBlock + 1; + const cachedFactoryChildAddressBlockCount = intervalSum( + completedFactoryChildAddressIntervals ); - this.common.logger.warn({ - service: "historical", - msg: `Start block is in unfinalized range, skipping historical sync (eventSource=${factory.name})`, - }); - return; - } - // Note that factory child address progress is stored using - // log intervals for the factory log. - const completedFactoryChildAddressIntervals = - await this.eventStore.getLogFilterIntervals({ - chainId: factory.chainId, - logFilter: { - address: factory.criteria.address, - topics: [factory.criteria.eventSelector], + this.common.metrics.ponder_historical_total_blocks.set( + { + network: this.network.name, + eventSource: `${source.name}_factory`, }, - }); - const factoryChildAddressProgressTracker = new ProgressTracker({ - target: [startBlock, endBlock], - completed: completedFactoryChildAddressIntervals, - }); - this.factoryChildAddressProgressTrackers[factory.name] = - factoryChildAddressProgressTracker; - - const requiredFactoryChildAddressIntervals = - factoryChildAddressProgressTracker.getRequired(); - const factoryChildAddressTaskChunks = getChunks({ - intervals: requiredFactoryChildAddressIntervals, - maxChunkSize: - factory.maxBlockRange ?? this.network.defaultMaxBlockRange, - }); - - for (const [fromBlock, toBlock] of factoryChildAddressTaskChunks) { - this.queue.addTask( - { kind: "FACTORY_CHILD_ADDRESS", factory, fromBlock, toBlock }, - { priority: Number.MAX_SAFE_INTEGER - fromBlock } + targetFactoryChildAddressBlockCount ); - } - if (factoryChildAddressTaskChunks.length > 0) { - const total = intervalSum(requiredFactoryChildAddressIntervals); - this.common.logger.debug({ - service: "historical", - msg: `Added FACTORY_CHILD_ADDRESS tasks for ${total}-block range (factory=${factory.name}, network=${this.network.name})`, + this.common.metrics.ponder_historical_cached_blocks.set( + { + network: this.network.name, + eventSource: `${source.name}_factory`, + }, + cachedFactoryChildAddressBlockCount + ); + + const completedFactoryLogFilterIntervals = + await this.eventStore.getFactoryLogFilterIntervals({ + chainId: source.chainId, + factory: source.criteria, + }); + const factoryLogFilterProgressTracker = new ProgressTracker({ + target: [startBlock, endBlock], + completed: completedFactoryLogFilterIntervals, }); - } + this.factoryLogFilterProgressTrackers[source.name] = + factoryLogFilterProgressTracker; + + // Manually add factory log filter tasks for any intervals where the + // child address tasks are completed, but the child log filter tasks are not, + // because these won't be added automatically by the factory child address tasks. + const requiredFactoryLogFilterIntervals = + factoryLogFilterProgressTracker.getRequired(); + const missingFactoryLogFilterIntervals = intervalDifference( + requiredFactoryLogFilterIntervals, + requiredFactoryChildAddressIntervals + ); - const targetFactoryChildAddressBlockCount = endBlock - startBlock + 1; - const cachedFactoryChildAddressBlockCount = intervalSum( - completedFactoryChildAddressIntervals - ); + const missingFactoryLogFilterTaskChunks = getChunks({ + intervals: missingFactoryLogFilterIntervals, + maxChunkSize: + source.maxBlockRange ?? this.network.defaultMaxBlockRange, + }); - this.common.metrics.ponder_historical_total_blocks.set( - { - network: this.network.name, - eventSource: `${factory.name}_factory`, - }, - targetFactoryChildAddressBlockCount - ); - this.common.metrics.ponder_historical_cached_blocks.set( - { - network: this.network.name, - eventSource: `${factory.name}_factory`, - }, - cachedFactoryChildAddressBlockCount - ); + for (const [ + fromBlock, + toBlock, + ] of missingFactoryLogFilterTaskChunks) { + this.queue.addTask( + { + kind: "FACTORY_LOG_FILTER", + factory: source, + fromBlock, + toBlock, + }, + { priority: Number.MAX_SAFE_INTEGER - fromBlock } + ); + } + if (missingFactoryLogFilterTaskChunks.length > 0) { + const total = intervalSum(missingFactoryLogFilterIntervals); + this.common.logger.debug({ + service: "historical", + msg: `Added FACTORY_LOG_FILTER tasks for ${total}-block range (factory=${source.name}, network=${this.network.name})`, + }); + } - const completedFactoryLogFilterIntervals = - await this.eventStore.getFactoryLogFilterIntervals({ - chainId: factory.chainId, - factory: factory.criteria, - }); - const factoryLogFilterProgressTracker = new ProgressTracker({ - target: [startBlock, endBlock], - completed: completedFactoryLogFilterIntervals, - }); - this.factoryLogFilterProgressTrackers[factory.name] = - factoryLogFilterProgressTracker; - - // Manually add factory log filter tasks for any intervals where the - // child address tasks are completed, but the child log filter tasks are not, - // because these won't be added automatically by the factory child address tasks. - const requiredFactoryLogFilterIntervals = - factoryLogFilterProgressTracker.getRequired(); - const missingFactoryLogFilterIntervals = intervalDifference( - requiredFactoryLogFilterIntervals, - requiredFactoryChildAddressIntervals - ); + const targetFactoryLogFilterBlockCount = endBlock - startBlock + 1; + const cachedFactoryLogFilterBlockCount = intervalSum( + completedFactoryLogFilterIntervals + ); - const missingFactoryLogFilterTaskChunks = getChunks({ - intervals: missingFactoryLogFilterIntervals, - maxChunkSize: - factory.maxBlockRange ?? this.network.defaultMaxBlockRange, - }); + this.common.metrics.ponder_historical_total_blocks.set( + { network: this.network.name, eventSource: source.name }, + targetFactoryLogFilterBlockCount + ); + this.common.metrics.ponder_historical_cached_blocks.set( + { network: this.network.name, eventSource: source.name }, + cachedFactoryLogFilterBlockCount + ); - for (const [fromBlock, toBlock] of missingFactoryLogFilterTaskChunks) { - this.queue.addTask( - { kind: "FACTORY_LOG_FILTER", factory, fromBlock, toBlock }, - { priority: Number.MAX_SAFE_INTEGER - fromBlock } + // Use factory log filter progress for the logger because it better represents + // user-facing progress. + const cacheRate = Math.min( + 1, + cachedFactoryLogFilterBlockCount / + (targetFactoryLogFilterBlockCount || 1) ); - } - if (missingFactoryLogFilterTaskChunks.length > 0) { - const total = intervalSum(missingFactoryLogFilterIntervals); - this.common.logger.debug({ + this.common.logger.info({ service: "historical", - msg: `Added FACTORY_LOG_FILTER tasks for ${total}-block range (factory=${factory.name}, network=${this.network.name})`, + msg: `Started sync with ${formatPercentage( + cacheRate + )} cached (eventSource=${source.name} network=${ + this.network.name + })`, }); } - - const targetFactoryLogFilterBlockCount = endBlock - startBlock + 1; - const cachedFactoryLogFilterBlockCount = intervalSum( - completedFactoryLogFilterIntervals - ); - - this.common.metrics.ponder_historical_total_blocks.set( - { network: this.network.name, eventSource: factory.name }, - targetFactoryLogFilterBlockCount - ); - this.common.metrics.ponder_historical_cached_blocks.set( - { network: this.network.name, eventSource: factory.name }, - cachedFactoryLogFilterBlockCount - ); - - // Use factory log filter progress for the logger because it better represents - // user-facing progress. - const cacheRate = Math.min( - 1, - cachedFactoryLogFilterBlockCount / - (targetFactoryLogFilterBlockCount || 1) - ); - this.common.logger.info({ - service: "historical", - msg: `Started sync with ${formatPercentage( - cacheRate - )} cached (eventSource=${factory.name} network=${this.network.name})`, - }); - }), - ]); + }) + ); } start() { @@ -611,7 +627,7 @@ export class HistoricalSyncService extends Emittery { const logs = await this._eth_getLogs({ address: factory.criteria.address, - topics: [factory.criteria.eventSelector], + topics: [factory.criteria.eventSelector, null, null, null], fromBlock: toHex(fromBlock), toBlock: toHex(toBlock), }); @@ -633,7 +649,7 @@ export class HistoricalSyncService extends Emittery { chainId: factory.chainId, logFilter: { address: factory.criteria.address, - topics: [factory.criteria.eventSelector], + topics: [factory.criteria.eventSelector, null, null, null], }, block, transactions: block.transactions.filter((tx) => diff --git a/packages/core/src/indexing/service.ts b/packages/core/src/indexing/service.ts index be3ed4a19..5149c9aca 100644 --- a/packages/core/src/indexing/service.ts +++ b/packages/core/src/indexing/service.ts @@ -3,8 +3,7 @@ import Emittery from "emittery"; import type { IndexingFunctions } from "@/build/functions"; import { LogEventMetadata } from "@/config/abi"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; +import type { Source } from "@/config/sources"; import { UserError } from "@/errors/user"; import type { EventAggregatorService, @@ -37,8 +36,7 @@ export class IndexingService extends Emittery { private common: Common; private userStore: UserStore; private eventAggregatorService: EventAggregatorService; - private logFilters: LogFilter[]; - private factories: Factory[]; + private sources: Source[]; private readOnlyContracts: Record = {}; @@ -62,23 +60,20 @@ export class IndexingService extends Emittery { // eventStore, userStore, eventAggregatorService, - // contracts, - logFilters = [], - factories = [], + sources = [], }: { common: Common; eventStore: EventStore; userStore: UserStore; eventAggregatorService: EventAggregatorService; - logFilters?: LogFilter[]; - factories?: Factory[]; + + sources?: Source[]; }) { super(); this.common = common; this.userStore = userStore; this.eventAggregatorService = eventAggregatorService; - this.logFilters = logFilters; - this.factories = factories; + this.sources = sources; // The read-only contract objects only depend on config, so they can // be built in the constructor (they can't be hot-reloaded). @@ -290,12 +285,10 @@ export class IndexingService extends Emittery { // Increment the metrics for the total number of matching & indexed events in this timestamp range. if (pageIndex === 0) { metadata.counts.forEach(({ eventSourceName, selector, count }) => { - const safeName = Object.values({ - ...(this.logFilters.find((f) => f.name === eventSourceName) - ?.events || {}), - ...(this.factories.find((f) => f.name === eventSourceName) - ?.events || {}), - }) + const safeName = Object.values( + this.sources.find((s) => s.name === eventSourceName)?.events || + {} + ) .filter((m): m is LogEventMetadata => !!m) .find((m) => m.selector === selector)?.safeName; diff --git a/packages/core/src/realtime-sync/service.ts b/packages/core/src/realtime-sync/service.ts index 04d3d6308..bafd6344e 100644 --- a/packages/core/src/realtime-sync/service.ts +++ b/packages/core/src/realtime-sync/service.ts @@ -9,7 +9,11 @@ import { } from "viem"; import type { Network } from "@/config/networks"; -import type { Source } from "@/config/sources"; +import { + type Source, + sourceIsFactory, + sourceIsLogFilter, +} from "@/config/sources"; import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import { poll } from "@/utils/poll"; @@ -267,12 +271,12 @@ export class RealtimeSyncService extends Emittery { let logs: RpcLog[]; let matchedLogs: RpcLog[]; - if (this.factories.length === 0) { + if (!this.sources.some(sourceIsFactory)) { // If there are no factory contracts, we can attempt to skip calling eth_getLogs by // checking if the block logsBloom matches any of the log filters. const doesBlockHaveLogFilterLogs = isMatchedLogInBloomFilter({ bloom: newBlockWithTransactions.logsBloom!, - logFilters: this.logFilters.map((l) => l.criteria), + logFilters: this.sources.map((s) => s.criteria), }); if (!doesBlockHaveLogFilterLogs) { @@ -296,7 +300,7 @@ export class RealtimeSyncService extends Emittery { matchedLogs = filterLogs({ logs, - logFilters: this.logFilters.map((l) => l.criteria), + logFilters: this.sources.map((s) => s.criteria), }); } } else { @@ -314,7 +318,7 @@ export class RealtimeSyncService extends Emittery { // Find and insert any new child contracts. await Promise.all( - this.factories.map(async (factory) => { + this.sources.filter(sourceIsFactory).map(async (factory) => { const matchedFactoryLogs = filterLogs({ logs, logFilters: [ @@ -337,7 +341,7 @@ export class RealtimeSyncService extends Emittery { // a potentially slow DB operation here. It's a tradeoff between sync // latency and database growth. const factoryLogFilters = await Promise.all( - this.factories.map(async (factory) => { + this.sources.filter(sourceIsFactory).map(async (factory) => { const iterator = this.eventStore.getFactoryChildAddresses({ chainId: this.network.chainId, factory: factory.criteria, @@ -357,7 +361,7 @@ export class RealtimeSyncService extends Emittery { matchedLogs = filterLogs({ logs, logFilters: [ - ...this.logFilters.map((l) => l.criteria), + ...this.sources.filter(sourceIsLogFilter).map((l) => l.criteria), ...factoryLogFilters, ], }); @@ -439,8 +443,12 @@ export class RealtimeSyncService extends Emittery { // 3) Child filter intervals await this.eventStore.insertRealtimeInterval({ chainId: this.network.chainId, - logFilters: this.logFilters.map((l) => l.criteria), - factories: this.factories.map((f) => f.criteria), + logFilters: this.sources + .filter(sourceIsLogFilter) + .map((l) => l.criteria), + factories: this.sources + .filter(sourceIsFactory) + .map((f) => f.criteria), interval: { startBlock: BigInt(this.finalizedBlockNumber + 1), endBlock: BigInt(newFinalizedBlock.number), From 22cdc993be1feb888fa644829959d36895d94b14 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 11:20:11 -0400 Subject: [PATCH 08/44] fix type issues with tests --- .../core/src/event-aggregator/service.test.ts | 23 +++++----- .../core/src/historical-sync/service.test.ts | 46 +++++++++---------- packages/core/src/indexing/service.test.ts | 36 ++++++--------- .../core/src/realtime-sync/service.test.ts | 33 ++++++------- 4 files changed, 65 insertions(+), 73 deletions(-) diff --git a/packages/core/src/event-aggregator/service.test.ts b/packages/core/src/event-aggregator/service.test.ts index 84be0850a..e77aeb684 100644 --- a/packages/core/src/event-aggregator/service.test.ts +++ b/packages/core/src/event-aggregator/service.test.ts @@ -3,8 +3,8 @@ import { beforeEach, expect, test, vi } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; import { publicClient } from "@/_test/utils"; -import type { LogFilter } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import type { Source } from "@/config/sources"; import { EventAggregatorService } from "./service"; @@ -35,9 +35,10 @@ const usdcLogFilter = { chainId: mainnet.chainId, criteria: { address: usdcContractConfig.address }, startBlock: 16369950, -}; + type: "logFilter", +} as const; -const logFilters: LogFilter[] = [ +const sources: Source[] = [ usdcLogFilter, { ...usdcLogFilter, @@ -54,7 +55,7 @@ test("handleNewHistoricalCheckpoint emits new checkpoint", async (context) => { common, eventStore, networks, - logFilters, + sources, }); const emitSpy = vi.spyOn(service, "emit"); @@ -76,7 +77,7 @@ test("handleNewHistoricalCheckpoint does not emit new checkpoint if not best", a const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -106,7 +107,7 @@ test("handleHistoricalSyncComplete sets historicalSyncCompletedAt if final histo const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -134,7 +135,7 @@ test("handleNewRealtimeCheckpoint does not emit new checkpoint if historical syn const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -163,7 +164,7 @@ test("handleNewRealtimeCheckpoint emits new checkpoint if historical sync is com const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -201,7 +202,7 @@ test("handleNewFinalityCheckpoint emits newFinalityCheckpoint", async (context) const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -227,7 +228,7 @@ test("handleNewFinalityCheckpoint does not emit newFinalityCheckpoint if subsequ const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); @@ -257,7 +258,7 @@ test("handleNewFinalityCheckpoint emits newFinalityCheckpoint if subsequent even const service = new EventAggregatorService({ common, eventStore, - logFilters, + sources, networks, }); const emitSpy = vi.spyOn(service, "emit"); diff --git a/packages/core/src/historical-sync/service.test.ts b/packages/core/src/historical-sync/service.test.ts index c0e9189ec..edae2ab8c 100644 --- a/packages/core/src/historical-sync/service.test.ts +++ b/packages/core/src/historical-sync/service.test.ts @@ -11,9 +11,8 @@ import { } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; import { publicClient } from "@/_test/utils"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import type { Source } from "@/config/sources"; import { HistoricalSyncService } from "./service"; @@ -46,13 +45,15 @@ const usdcLogFilter = { criteria: { address: usdcContractConfig.address }, startBlock: 16369995, // 5 blocks maxBlockRange: 3, -} satisfies LogFilter; + type: "logFilter", +} satisfies Source; const uniswapV3Factory = { ...uniswapV3PoolFactoryConfig, network: network.name, startBlock: 16369500, // 500 blocks -} satisfies Factory; + type: "factory", +} satisfies Source; test("start() with log filter inserts log filter interval records", async (context) => { const { common, eventStore } = context; @@ -61,7 +62,7 @@ test("start() with log filter inserts log filter interval records", async (conte common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -84,7 +85,7 @@ test("start() with factory contract inserts log filter and factory log filter in common, eventStore, network, - factories: [uniswapV3Factory], + sources: [uniswapV3Factory], }); await service.setup(blockNumbers); service.start(); @@ -95,7 +96,7 @@ test("start() with factory contract inserts log filter and factory log filter in chainId: network.chainId, logFilter: { address: uniswapV3Factory.criteria.address, - topics: [uniswapV3Factory.criteria.eventSelector], + topics: [uniswapV3Factory.criteria.eventSelector, null, null, null], }, } ); @@ -118,7 +119,7 @@ test("start() with factory contract inserts child contract addresses", async (co common, eventStore, network, - factories: [uniswapV3Factory], + sources: [uniswapV3Factory], }); await service.setup(blockNumbers); service.start(); @@ -152,8 +153,7 @@ test("setup() with log filter and factory contract updates block metrics", async common, eventStore, network, - logFilters: [usdcLogFilter], - factories: [uniswapV3Factory], + sources: [usdcLogFilter, uniswapV3Factory], }); await service.setup(blockNumbers); @@ -194,8 +194,7 @@ test("start() with log filter and factory contract updates completed blocks metr common, eventStore, network, - logFilters: [usdcLogFilter], - factories: [uniswapV3Factory], + sources: [usdcLogFilter, uniswapV3Factory], }); await service.setup(blockNumbers); service.start(); @@ -226,7 +225,7 @@ test("start() with log filter and factory contract updates rpc request duration common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -264,7 +263,7 @@ test("start() adds log filter events to event store", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -318,8 +317,7 @@ test("start() adds log filter and factory contract events to event store", async common, eventStore, network, - logFilters: [usdcLogFilter], - factories: [uniswapV3Factory], + sources: [usdcLogFilter, uniswapV3Factory], }); await service.setup(blockNumbers); service.start(); @@ -363,7 +361,7 @@ test("start() retries unexpected error in log filter task", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -389,7 +387,7 @@ test("start() retries unexpected error in block task", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -421,7 +419,7 @@ test("start() handles Alchemy 'Log response size exceeded' error", async (contex common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -451,7 +449,7 @@ test("start() handles Quicknode 'eth_getLogs and eth_newFilter are limited to a common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(blockNumbers); service.start(); @@ -473,7 +471,7 @@ test("start() emits sync completed event", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); @@ -492,7 +490,7 @@ test("start() emits checkpoint and sync completed event if 100% cached", async ( let service = new HistoricalSyncService({ common, eventStore, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], network, }); @@ -504,7 +502,7 @@ test("start() emits checkpoint and sync completed event if 100% cached", async ( service = new HistoricalSyncService({ common, eventStore, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], network, }); @@ -531,7 +529,7 @@ test("start() emits historicalCheckpoint event", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); diff --git a/packages/core/src/indexing/service.test.ts b/packages/core/src/indexing/service.test.ts index c6d67452f..cf1c4ae5a 100644 --- a/packages/core/src/indexing/service.test.ts +++ b/packages/core/src/indexing/service.test.ts @@ -7,6 +7,7 @@ import { publicClient } from "@/_test/utils"; import type { IndexingFunctions } from "@/build/functions"; import { schemaHeader } from "@/build/schema"; import { LogEventMetadata } from "@/config/abi"; +import { Source } from "@/config/sources"; import { EventAggregatorService } from "@/event-aggregator/service"; import { buildSchema } from "@/schema/schema"; @@ -25,13 +26,14 @@ const network = { maxRpcRequestConcurrency: 10, }; -const logFilters = [ +const sources: Source[] = [ { name: "USDC", ...usdcContractConfig, network: network.name, criteria: { address: usdcContractConfig.address }, startBlock: 16369950, + type: "logFilter", }, ]; @@ -123,8 +125,7 @@ test("processEvents() calls getEvents with sequential timestamp ranges", async ( eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -162,8 +163,7 @@ test("processEvents() calls indexing functions with correct arguments", async (c eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -183,7 +183,7 @@ test("processEvents() calls indexing functions with correct arguments", async (c name: "Transfer", }, context: { - contracts: { USDC: expect.anything() }, + contracts: {}, entities: { TransferEvent: expect.anything() }, }, }) @@ -200,8 +200,7 @@ test("processEvents() model methods insert data into the user store", async (con eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -225,8 +224,7 @@ test("processEvents() updates event count metrics", async (context) => { eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -267,8 +265,7 @@ test("reset() reloads the user store", async (context) => { eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -303,8 +300,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -335,8 +331,7 @@ test("handleReorg() reverts the user store", async (context) => { eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); const userStoreRevertSpy = vi.spyOn(userStore, "revert"); @@ -361,8 +356,7 @@ test("handleReorg() does nothing if there is a user error", async (context) => { eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); const userStoreRevertSpy = vi.spyOn(userStore, "revert"); @@ -391,8 +385,7 @@ test("handleReorg() processes the correct range of events after a reorg", async eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); @@ -431,8 +424,7 @@ test("handleReorg() updates ponder_handlers_latest_processed_timestamp metric", eventStore, userStore, eventAggregatorService, - // contracts, - logFilters, + sources, }); await service.reset({ schema, indexingFunctions }); diff --git a/packages/core/src/realtime-sync/service.test.ts b/packages/core/src/realtime-sync/service.test.ts index c39d65969..1139cdd9a 100644 --- a/packages/core/src/realtime-sync/service.test.ts +++ b/packages/core/src/realtime-sync/service.test.ts @@ -10,9 +10,8 @@ import { } from "@/_test/constants"; import { resetTestClient, setupEventStore } from "@/_test/setup"; import { publicClient, testClient, walletClient } from "@/_test/utils"; -import { Factory } from "@/config/factories"; -import type { LogFilter } from "@/config/logFilters"; import type { Network } from "@/config/networks"; +import type { Source } from "@/config/sources"; import { decodeToBigInt } from "@/utils/encoding"; import { range } from "@/utils/range"; @@ -43,7 +42,8 @@ const usdcLogFilter = { criteria: { address: usdcContractConfig.address }, startBlock: 16369995, // 5 blocks maxBlockRange: 3, -} satisfies LogFilter; + type: "logFilter", +} satisfies Source; const sendUsdcTransferTransaction = async () => { await walletClient.writeContract({ @@ -59,7 +59,8 @@ const uniswapV3Factory = { ...uniswapV3PoolFactoryConfig, network: network.name, startBlock: 16369500, // 500 blocks -} satisfies Factory; + type: "factory", +} satisfies Source; const createAndInitializeUniswapV3Pool = async () => { await walletClient.writeContract({ @@ -105,7 +106,7 @@ test("setup() returns block numbers", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const { latestBlockNumber, finalizedBlockNumber } = await service.setup(); @@ -123,7 +124,7 @@ test("start() adds blocks to the store from finalized to latest", async (context common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -150,7 +151,7 @@ test("start() adds all required transactions to the store", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -180,7 +181,7 @@ test("start() adds all matched logs to the store", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -203,7 +204,7 @@ test("start() handles new blocks", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -250,7 +251,7 @@ test("start() handles error while fetching new latest block gracefully", async ( common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -291,7 +292,7 @@ test("start() emits realtimeCheckpoint events", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); @@ -332,7 +333,7 @@ test("start() inserts log filter interval records for finalized blocks", async ( common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); @@ -370,7 +371,7 @@ test("start() deletes data from the store after 3 block shallow reorg", async (c common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); await service.setup(); @@ -440,7 +441,7 @@ test("start() emits shallowReorg event after 3 block shallow reorg", async (cont common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); @@ -482,7 +483,7 @@ test("emits deepReorg event after deep reorg", async (context) => { common, eventStore, network, - logFilters: [usdcLogFilter], + sources: [usdcLogFilter], }); const emitSpy = vi.spyOn(service, "emit"); @@ -535,7 +536,7 @@ test("start() with factory contract inserts new child contracts records and chil common, eventStore, network, - factories: [uniswapV3Factory], + sources: [uniswapV3Factory], }); await service.setup(); From 5885d0abb12396c401d40af0146770b47cee5f7f Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 12:33:07 -0400 Subject: [PATCH 09/44] change topcis back to original shape, fix bug with address normalization, and update end to end tests --- .../_test/art-gobblers/app/ArtGobblers.abi.ts | 947 +++++++++++++++++ .../_test/art-gobblers/app/ArtGobblers.json | 989 ------------------ .../_test/art-gobblers/app/ponder.config.ts | 7 +- .../app/BaseRegistrarImplementation.abi.json | 497 --------- .../app/BaseRegistrarImplementation.abi.ts | 479 +++++++++ .../core/src/_test/ens/app/ponder.config.ts | 7 +- packages/core/src/_test/ens/app/tsconfig.json | 2 +- packages/core/src/build/config.ts | 3 +- packages/core/src/config/sources.ts | 25 +- .../core/src/event-store/postgres/store.ts | 8 +- packages/core/src/event-store/sqlite/store.ts | 8 +- packages/core/src/event-store/store.test.ts | 27 +- .../core/src/historical-sync/service.test.ts | 2 +- packages/core/src/historical-sync/service.ts | 6 +- packages/core/src/realtime-sync/bloom.ts | 4 +- packages/core/src/realtime-sync/filter.ts | 6 +- 16 files changed, 1465 insertions(+), 1552 deletions(-) create mode 100644 packages/core/src/_test/art-gobblers/app/ArtGobblers.abi.ts delete mode 100644 packages/core/src/_test/art-gobblers/app/ArtGobblers.json delete mode 100644 packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.json create mode 100644 packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.ts diff --git a/packages/core/src/_test/art-gobblers/app/ArtGobblers.abi.ts b/packages/core/src/_test/art-gobblers/app/ArtGobblers.abi.ts new file mode 100644 index 000000000..e790b12dd --- /dev/null +++ b/packages/core/src/_test/art-gobblers/app/ArtGobblers.abi.ts @@ -0,0 +1,947 @@ +export const ArtGobblersAbi = [ + { + inputs: [ + { internalType: "bytes32", name: "_merkleRoot", type: "bytes32" }, + { internalType: "uint256", name: "_mintStart", type: "uint256" }, + { internalType: "contract Goo", name: "_goo", type: "address" }, + { internalType: "contract Pages", name: "_pages", type: "address" }, + { internalType: "address", name: "_team", type: "address" }, + { internalType: "address", name: "_community", type: "address" }, + { + internalType: "contract RandProvider", + name: "_randProvider", + type: "address", + }, + { internalType: "string", name: "_baseUri", type: "string" }, + { internalType: "string", name: "_unrevealedUri", type: "string" }, + { + internalType: "bytes32", + name: "_provenanceHash", + type: "bytes32", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AlreadyClaimed", type: "error" }, + { inputs: [], name: "Cannibalism", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "CannotBurnLegendary", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "cost", type: "uint256" }], + name: "InsufficientGobblerAmount", + type: "error", + }, + { inputs: [], name: "InvalidProof", type: "error" }, + { + inputs: [ + { internalType: "uint256", name: "gobblersLeft", type: "uint256" }, + ], + name: "LegendaryAuctionNotStarted", + type: "error", + }, + { inputs: [], name: "MintStartPending", type: "error" }, + { inputs: [], name: "NoRemainingLegendaryGobblers", type: "error" }, + { + inputs: [ + { + internalType: "uint256", + name: "totalRemainingToBeRevealed", + type: "uint256", + }, + ], + name: "NotEnoughRemainingToBeRevealed", + type: "error", + }, + { inputs: [], name: "NotRandProvider", type: "error" }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "OwnerMismatch", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "currentPrice", type: "uint256" }, + ], + name: "PriceExceededMax", + type: "error", + }, + { inputs: [], name: "RequestTooEarly", type: "error" }, + { inputs: [], name: "ReserveImbalance", type: "error" }, + { inputs: [], name: "RevealsPending", type: "error" }, + { inputs: [], name: "SeedPending", type: "error" }, + { + inputs: [{ internalType: "address", name: "caller", type: "address" }], + name: "UnauthorizedCaller", + type: "error", + }, + { inputs: [], name: "ZeroToBeRevealed", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "nft", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "ArtGobbled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + ], + name: "GobblerClaimed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "price", + type: "uint256", + }, + ], + name: "GobblerPurchased", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "numGobblers", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "lastRevealedId", + type: "uint256", + }, + ], + name: "GobblersRevealed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "newGooBalance", + type: "uint256", + }, + ], + name: "GooBalanceUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256[]", + name: "burnedGobblerIds", + type: "uint256[]", + }, + ], + name: "LegendaryGobblerMinted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "contract RandProvider", + name: "newRandProvider", + type: "address", + }, + ], + name: "RandProviderUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "randomness", + type: "uint256", + }, + ], + name: "RandomnessFulfilled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "toBeRevealed", + type: "uint256", + }, + ], + name: "RandomnessRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "lastMintedGobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "numGobblersEach", + type: "uint256", + }, + ], + name: "ReservedGobblersMinted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "BASE_URI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "FIRST_LEGENDARY_GOBBLER_ID", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_AUCTION_INTERVAL", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_GOBBLER_INITIAL_START_PRICE", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_MINTABLE", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MINTLIST_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "PROVENANCE_HASH", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "RESERVED_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "UNREVEALED_URI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "", type: "bytes32" }, + { internalType: "uint256", name: "randomness", type: "uint256" }, + ], + name: "acceptRandomSeed", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gooAmount", type: "uint256" }], + name: "addGoo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "user", type: "address" }, + { internalType: "uint256", name: "gooAmount", type: "uint256" }, + ], + name: "burnGooForPages", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32[]", name: "proof", type: "bytes32[]" }], + name: "claimGobbler", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "community", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "currentNonLegendaryId", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "getCopiesOfArtGobbledByGobbler", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "getGobblerData", + outputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint64", name: "idx", type: "uint64" }, + { internalType: "uint32", name: "emissionMultiple", type: "uint32" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "getGobblerEmissionMultiple", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "int256", name: "sold", type: "int256" }], + name: "getTargetSaleTime", + outputs: [{ internalType: "int256", name: "", type: "int256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "getUserData", + outputs: [ + { internalType: "uint32", name: "gobblersOwned", type: "uint32" }, + { + internalType: "uint32", + name: "emissionMultiple", + type: "uint32", + }, + { internalType: "uint128", name: "lastBalance", type: "uint128" }, + { internalType: "uint64", name: "lastTimestamp", type: "uint64" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "getUserEmissionMultiple", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "int256", name: "timeSinceStart", type: "int256" }, + { internalType: "uint256", name: "sold", type: "uint256" }, + ], + name: "getVRGDAPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "gobblerId", type: "uint256" }, + { internalType: "address", name: "nft", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "bool", name: "isERC1155", type: "bool" }, + ], + name: "gobble", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "gobblerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "gobblerRevealsData", + outputs: [ + { internalType: "uint64", name: "randomSeed", type: "uint64" }, + { + internalType: "uint64", + name: "nextRevealTimestamp", + type: "uint64", + }, + { internalType: "uint64", name: "lastRevealedId", type: "uint64" }, + { internalType: "uint56", name: "toBeRevealed", type: "uint56" }, + { internalType: "bool", name: "waitingForSeed", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "goo", + outputs: [{ internalType: "contract Goo", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "gooBalance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "hasClaimedMintlistGobbler", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "legendaryGobblerAuctionData", + outputs: [ + { internalType: "uint128", name: "startPrice", type: "uint128" }, + { internalType: "uint128", name: "numSold", type: "uint128" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "legendaryGobblerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "merkleRoot", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "maxPrice", type: "uint256" }, + { internalType: "bool", name: "useVirtualBalance", type: "bool" }, + ], + name: "mintFromGoo", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256[]", name: "gobblerIds", type: "uint256[]" }, + ], + name: "mintLegendaryGobbler", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "numGobblersEach", + type: "uint256", + }, + ], + name: "mintReservedGobblers", + outputs: [ + { + internalType: "uint256", + name: "lastMintedGobblerId", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "mintStart", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "numMintedForReserves", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "numMintedFromGoo", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256[]", name: "", type: "uint256[]" }, + { internalType: "uint256[]", name: "", type: "uint256[]" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + name: "onERC1155BatchReceived", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + name: "onERC1155Received", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "owner", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pages", + outputs: [{ internalType: "contract Pages", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "randProvider", + outputs: [ + { internalType: "contract RandProvider", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gooAmount", type: "uint256" }], + name: "removeGoo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "requestRandomSeed", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "numGobblers", type: "uint256" }], + name: "revealGobblers", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "targetPrice", + outputs: [{ internalType: "int256", name: "", type: "int256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "team", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract RandProvider", + name: "newRandProvider", + type: "address", + }, + ], + name: "upgradeRandProvider", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/packages/core/src/_test/art-gobblers/app/ArtGobblers.json b/packages/core/src/_test/art-gobblers/app/ArtGobblers.json deleted file mode 100644 index ce916cce2..000000000 --- a/packages/core/src/_test/art-gobblers/app/ArtGobblers.json +++ /dev/null @@ -1,989 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "bytes32", "name": "_merkleRoot", "type": "bytes32" }, - { "internalType": "uint256", "name": "_mintStart", "type": "uint256" }, - { "internalType": "contract Goo", "name": "_goo", "type": "address" }, - { "internalType": "contract Pages", "name": "_pages", "type": "address" }, - { "internalType": "address", "name": "_team", "type": "address" }, - { "internalType": "address", "name": "_community", "type": "address" }, - { - "internalType": "contract RandProvider", - "name": "_randProvider", - "type": "address" - }, - { "internalType": "string", "name": "_baseUri", "type": "string" }, - { "internalType": "string", "name": "_unrevealedUri", "type": "string" }, - { - "internalType": "bytes32", - "name": "_provenanceHash", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "AlreadyClaimed", "type": "error" }, - { "inputs": [], "name": "Cannibalism", "type": "error" }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "CannotBurnLegendary", - "type": "error" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "cost", "type": "uint256" } - ], - "name": "InsufficientGobblerAmount", - "type": "error" - }, - { "inputs": [], "name": "InvalidProof", "type": "error" }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblersLeft", "type": "uint256" } - ], - "name": "LegendaryAuctionNotStarted", - "type": "error" - }, - { "inputs": [], "name": "MintStartPending", "type": "error" }, - { "inputs": [], "name": "NoRemainingLegendaryGobblers", "type": "error" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "totalRemainingToBeRevealed", - "type": "uint256" - } - ], - "name": "NotEnoughRemainingToBeRevealed", - "type": "error" - }, - { "inputs": [], "name": "NotRandProvider", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "OwnerMismatch", - "type": "error" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "currentPrice", "type": "uint256" } - ], - "name": "PriceExceededMax", - "type": "error" - }, - { "inputs": [], "name": "RequestTooEarly", "type": "error" }, - { "inputs": [], "name": "ReserveImbalance", "type": "error" }, - { "inputs": [], "name": "RevealsPending", "type": "error" }, - { "inputs": [], "name": "SeedPending", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "caller", "type": "address" } - ], - "name": "UnauthorizedCaller", - "type": "error" - }, - { "inputs": [], "name": "ZeroToBeRevealed", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "nft", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "ArtGobbled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - } - ], - "name": "GobblerClaimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "price", - "type": "uint256" - } - ], - "name": "GobblerPurchased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "numGobblers", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "lastRevealedId", - "type": "uint256" - } - ], - "name": "GobblersRevealed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newGooBalance", - "type": "uint256" - } - ], - "name": "GooBalanceUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "burnedGobblerIds", - "type": "uint256[]" - } - ], - "name": "LegendaryGobblerMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract RandProvider", - "name": "newRandProvider", - "type": "address" - } - ], - "name": "RandProviderUpgraded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "randomness", - "type": "uint256" - } - ], - "name": "RandomnessFulfilled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "toBeRevealed", - "type": "uint256" - } - ], - "name": "RandomnessRequested", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "lastMintedGobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "numGobblersEach", - "type": "uint256" - } - ], - "name": "ReservedGobblersMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "BASE_URI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "FIRST_LEGENDARY_GOBBLER_ID", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_AUCTION_INTERVAL", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_GOBBLER_INITIAL_START_PRICE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_MINTABLE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MINTLIST_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PROVENANCE_HASH", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "RESERVED_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "UNREVEALED_URI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "", "type": "bytes32" }, - { "internalType": "uint256", "name": "randomness", "type": "uint256" } - ], - "name": "acceptRandomSeed", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "addGoo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" }, - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "burnGooForPages", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" } - ], - "name": "claimGobbler", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "community", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentNonLegendaryId", - "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "getCopiesOfArtGobbledByGobbler", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "getGobblerData", - "outputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint64", "name": "idx", "type": "uint64" }, - { "internalType": "uint32", "name": "emissionMultiple", "type": "uint32" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "getGobblerEmissionMultiple", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "int256", "name": "sold", "type": "int256" }], - "name": "getTargetSaleTime", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "getUserData", - "outputs": [ - { "internalType": "uint32", "name": "gobblersOwned", "type": "uint32" }, - { - "internalType": "uint32", - "name": "emissionMultiple", - "type": "uint32" - }, - { "internalType": "uint128", "name": "lastBalance", "type": "uint128" }, - { "internalType": "uint64", "name": "lastTimestamp", "type": "uint64" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" } - ], - "name": "getUserEmissionMultiple", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "int256", "name": "timeSinceStart", "type": "int256" }, - { "internalType": "uint256", "name": "sold", "type": "uint256" } - ], - "name": "getVRGDAPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" }, - { "internalType": "address", "name": "nft", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "bool", "name": "isERC1155", "type": "bool" } - ], - "name": "gobble", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "gobblerPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "gobblerRevealsData", - "outputs": [ - { "internalType": "uint64", "name": "randomSeed", "type": "uint64" }, - { - "internalType": "uint64", - "name": "nextRevealTimestamp", - "type": "uint64" - }, - { "internalType": "uint64", "name": "lastRevealedId", "type": "uint64" }, - { "internalType": "uint56", "name": "toBeRevealed", "type": "uint56" }, - { "internalType": "bool", "name": "waitingForSeed", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "goo", - "outputs": [ - { "internalType": "contract Goo", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" } - ], - "name": "gooBalance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "hasClaimedMintlistGobbler", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "legendaryGobblerAuctionData", - "outputs": [ - { "internalType": "uint128", "name": "startPrice", "type": "uint128" }, - { "internalType": "uint128", "name": "numSold", "type": "uint128" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "legendaryGobblerPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "merkleRoot", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxPrice", "type": "uint256" }, - { "internalType": "bool", "name": "useVirtualBalance", "type": "bool" } - ], - "name": "mintFromGoo", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256[]", "name": "gobblerIds", "type": "uint256[]" } - ], - "name": "mintLegendaryGobbler", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "numGobblersEach", - "type": "uint256" - } - ], - "name": "mintReservedGobblers", - "outputs": [ - { - "internalType": "uint256", - "name": "lastMintedGobblerId", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "mintStart", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numMintedForReserves", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numMintedFromGoo", - "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155BatchReceived", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], - "name": "ownerOf", - "outputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pages", - "outputs": [ - { "internalType": "contract Pages", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "randProvider", - "outputs": [ - { "internalType": "contract RandProvider", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "removeGoo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "requestRandomSeed", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "numGobblers", "type": "uint256" } - ], - "name": "revealGobblers", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "targetPrice", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "team", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract RandProvider", - "name": "newRandProvider", - "type": "address" - } - ], - "name": "upgradeRandProvider", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/core/src/_test/art-gobblers/app/ponder.config.ts b/packages/core/src/_test/art-gobblers/app/ponder.config.ts index b4ecd5d1f..7540d1e6a 100644 --- a/packages/core/src/_test/art-gobblers/app/ponder.config.ts +++ b/packages/core/src/_test/art-gobblers/app/ponder.config.ts @@ -1,15 +1,16 @@ import { http } from "viem"; -import ArtGobblersAbi from "./ArtGobblers.json"; +import type { Config } from "../../../../dist"; +import { ArtGobblersAbi } from "./ArtGobblers.abi"; -export const config = { +export const config: Config = { networks: [ { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, ], contracts: [ { name: "ArtGobblers", - network: "mainnet", + network: [{ name: "mainnet" }], abi: ArtGobblersAbi, address: "0x60bb1e2aa1c9acafb4d34f71585d7e959f387769", startBlock: 15870400, diff --git a/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.json b/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.json deleted file mode 100644 index 93d79df9c..000000000 --- a/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.json +++ /dev/null @@ -1,497 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "contract ENS", "name": "_ens", "type": "address" }, - { "internalType": "bytes32", "name": "_baseNode", "type": "bytes32" } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "controller", - "type": "address" - } - ], - "name": "ControllerAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "controller", - "type": "address" - } - ], - "name": "ControllerRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "expires", - "type": "uint256" - } - ], - "name": "NameMigrated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "expires", - "type": "uint256" - } - ], - "name": "NameRegistered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "expires", - "type": "uint256" - } - ], - "name": "NameRenewed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "GRACE_PERIOD", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "controller", "type": "address" } - ], - "name": "addController", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], - "name": "available", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "baseNode", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "controllers", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ens", - "outputs": [ - { "internalType": "contract ENS", "name": "", "type": "address" } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "operator", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isOwner", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], - "name": "nameExpires", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "ownerOf", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "reclaim", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "duration", "type": "uint256" } - ], - "name": "register", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "duration", "type": "uint256" } - ], - "name": "registerOnly", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "controller", "type": "address" } - ], - "name": "removeController", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "uint256", "name": "duration", "type": "uint256" } - ], - "name": "renew", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "resolver", "type": "address" } - ], - "name": "setResolver", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.ts b/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.ts new file mode 100644 index 000000000..2d9718a2d --- /dev/null +++ b/packages/core/src/_test/ens/app/BaseRegistrarImplementation.abi.ts @@ -0,0 +1,479 @@ +export const BaseRegistrarImplementationAbi = [ + { + inputs: [ + { internalType: "contract ENS", name: "_ens", type: "address" }, + { internalType: "bytes32", name: "_baseNode", type: "bytes32" }, + ], + payable: false, + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + ], + name: "ControllerAdded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + ], + name: "ControllerRemoved", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameMigrated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRegistered", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRenewed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + constant: true, + inputs: [], + name: "GRACE_PERIOD", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "addController", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "approve", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "available", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "baseNode", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "controllers", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "ens", + outputs: [{ internalType: "contract ENS", name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "isOwner", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "nameExpires", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + ], + name: "reclaim", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "register", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "registerOnly", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "removeController", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "renew", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [], + name: "renounceOwnership", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ internalType: "address", name: "resolver", type: "address" }], + name: "setResolver", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ internalType: "bytes4", name: "interfaceID", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/packages/core/src/_test/ens/app/ponder.config.ts b/packages/core/src/_test/ens/app/ponder.config.ts index 7c689b2c7..0b733399f 100644 --- a/packages/core/src/_test/ens/app/ponder.config.ts +++ b/packages/core/src/_test/ens/app/ponder.config.ts @@ -1,15 +1,16 @@ import { http } from "viem"; -import BaseRegistrarImplementationAbi from "./BaseRegistrarImplementation.abi.json"; +import type { Config } from "../../../../dist"; +import { BaseRegistrarImplementationAbi } from "./BaseRegistrarImplementation.abi"; -export const config = { +export const config: Config = { networks: [ { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, ], contracts: [ { name: "BaseRegistrarImplementation", - network: "mainnet", + network: [{ name: "mainnet" }], abi: BaseRegistrarImplementationAbi, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, diff --git a/packages/core/src/_test/ens/app/tsconfig.json b/packages/core/src/_test/ens/app/tsconfig.json index b9a2419b9..dce160e47 100644 --- a/packages/core/src/_test/ens/app/tsconfig.json +++ b/packages/core/src/_test/ens/app/tsconfig.json @@ -6,6 +6,6 @@ "@/generated": ["./generated/index.ts"] } }, - "include": ["src"], + "include": ["src", "BaseRegistrarImplementation.abi.ts"], "exclude": [] } diff --git a/packages/core/src/build/config.ts b/packages/core/src/build/config.ts index ab3500953..601ee0d8d 100644 --- a/packages/core/src/build/config.ts +++ b/packages/core/src/build/config.ts @@ -22,7 +22,8 @@ export const buildConfig = async ({ configFile }: { configFile: string }) => { outfile: buildFile, platform: "node", format: "cjs", - bundle: false, + // Note: Flipped to true in order to be able to import external files into ponder.config.ts + bundle: true, logLevel: "silent", }); diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 8a57612d1..50b74b519 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -1,5 +1,7 @@ import { Abi, Address, encodeEventTopics, Hex } from "viem"; +import { toLowerCase } from "@/utils/lowercase"; + import { AbiEvents, getEvents } from "./abi"; import { buildFactoryCriteria } from "./factories"; import { Options } from "./options"; @@ -10,12 +12,7 @@ import { ResolvedConfig } from "./types"; * * Technically, only the first element could be an array */ -export type Topics = [ - Hex | Hex[] | null, - Hex | Hex[] | null, - Hex | Hex[] | null, - Hex | Hex[] | null -]; +export type Topics = (Hex | Hex[] | null)[]; export type LogFilterCriteria = { address?: Address | Address[]; @@ -117,7 +114,9 @@ export const buildSources = ({ ...sharedSource, type: "logFilter", criteria: { - address: contract.address, + address: contract.address + ? toLowerCase(contract.address) + : undefined, topics, }, } as const satisfies LogFilter; @@ -142,23 +141,13 @@ const buildTopics = ( }) ) .flat(), - null, - null, - null, ]; } else { // Single event with args - const singleTopics = encodeEventTopics({ + return encodeEventTopics({ abi: [events.signature], eventName: events.signature.name, args: events.args, }); - - return [ - singleTopics[0] ?? null, - singleTopics[1] ?? null, - singleTopics[2] ?? null, - singleTopics[3] ?? null, - ]; } }; diff --git a/packages/core/src/event-store/postgres/store.ts b/packages/core/src/event-store/postgres/store.ts index bddc858c1..8a85b0156 100644 --- a/packages/core/src/event-store/postgres/store.ts +++ b/packages/core/src/event-store/postgres/store.ts @@ -10,11 +10,7 @@ import { import type { Pool } from "pg"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import type { - FactoryCriteria, - LogFilterCriteria, - Topics, -} from "@/config/sources"; +import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; @@ -544,7 +540,7 @@ export class PostgresEventStore implements EventStore { ...logFilters, ...factories.map((f) => ({ address: f.address, - topics: [f.eventSelector, null, null, null] as Topics, + topics: [f.eventSelector], })), ], interval, diff --git a/packages/core/src/event-store/sqlite/store.ts b/packages/core/src/event-store/sqlite/store.ts index 881ef2151..e07e6aef3 100644 --- a/packages/core/src/event-store/sqlite/store.ts +++ b/packages/core/src/event-store/sqlite/store.ts @@ -9,11 +9,7 @@ import { } from "kysely"; import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; -import type { - FactoryCriteria, - LogFilterCriteria, - Topics, -} from "@/config/sources"; +import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; import type { Block } from "@/types/block"; import type { Log } from "@/types/log"; import type { Transaction } from "@/types/transaction"; @@ -512,7 +508,7 @@ export class SqliteEventStore implements EventStore { ...logFilters, ...factories.map((f) => ({ address: f.address, - topics: [f.eventSelector, null, null, null] as Topics, + topics: [f.eventSelector], })), ], interval, diff --git a/packages/core/src/event-store/store.test.ts b/packages/core/src/event-store/store.test.ts index cb1766262..0d6aa518a 100644 --- a/packages/core/src/event-store/store.test.ts +++ b/packages/core/src/event-store/store.test.ts @@ -245,7 +245,7 @@ test("getLogFilterRanges handles complex log filter inclusivity rules", async (c await eventStore.insertLogFilterInterval({ chainId: 1, - logFilter: { topics: [null, ["0xc", "0xd"], null, null] }, + logFilter: { topics: [null, ["0xc", "0xd"]] }, block: blockOne, transactions: [], logs: [], @@ -262,7 +262,7 @@ test("getLogFilterRanges handles complex log filter inclusivity rules", async (c // Narrower criteria includes both broad and specific intervals. logFilterIntervals = await eventStore.getLogFilterIntervals({ chainId: 1, - logFilter: { topics: [null, "0xc", null, null] }, + logFilter: { topics: [null, "0xc"] }, }); expect(logFilterIntervals).toMatchObject([ [0, 100], @@ -670,9 +670,6 @@ test("getFactoryLogFilterIntervals handles topic filtering rules", async (contex ...factoryCriteria, topics: [ "0x0000000000000000000000000000000000000000000factoryeventsignature", - null, - null, - null, ], } as FactoryCriteria, }); @@ -742,7 +739,7 @@ test("insertRealtimeInterval inserts log filter intervals", async (context) => { chainId: 1, logFilter: { address: factoryCriteriaOne.address, - topics: [factoryCriteriaOne.eventSelector, null, null, null], + topics: [factoryCriteriaOne.eventSelector], }, }) ).toMatchObject([[500, 550]]); @@ -751,7 +748,7 @@ test("insertRealtimeInterval inserts log filter intervals", async (context) => { chainId: 1, logFilter: { address: factoryCriteriaOne.address, - topics: [factoryCriteriaOne.eventSelector, null, null, null], + topics: [factoryCriteriaOne.eventSelector], }, }) ).toMatchObject([[500, 550]]); @@ -1266,12 +1263,7 @@ test("getLogEvents filters on log filter with single topic", async (context) => name: "singleTopic", chainId: 1, criteria: { - topics: [ - blockOneLogs[0].topics[0] as `0x${string}`, - null, - null, - null, - ], + topics: [blockOneLogs[0].topics[0] as `0x${string}`], }, }, ], @@ -1322,8 +1314,6 @@ test("getLogEvents filters on log filter with multiple topics", async (context) topics: [ blockOneLogs[0].topics[0] as `0x${string}`, blockOneLogs[0].topics[1] as `0x${string}`, - null, - null, ], }, }, @@ -1466,12 +1456,7 @@ test("getLogEvents filters on multiple filters", async (context) => { name: "singleTopic", // This should match blockOneLogs[0] AND blockTwoLogs[0] chainId: 1, criteria: { - topics: [ - blockOneLogs[0].topics[0] as `0x${string}`, - null, - null, - null, - ], + topics: [blockOneLogs[0].topics[0] as `0x${string}`], }, }, ], diff --git a/packages/core/src/historical-sync/service.test.ts b/packages/core/src/historical-sync/service.test.ts index edae2ab8c..2971b650f 100644 --- a/packages/core/src/historical-sync/service.test.ts +++ b/packages/core/src/historical-sync/service.test.ts @@ -96,7 +96,7 @@ test("start() with factory contract inserts log filter and factory log filter in chainId: network.chainId, logFilter: { address: uniswapV3Factory.criteria.address, - topics: [uniswapV3Factory.criteria.eventSelector, null, null, null], + topics: [uniswapV3Factory.criteria.eventSelector], }, } ); diff --git a/packages/core/src/historical-sync/service.ts b/packages/core/src/historical-sync/service.ts index acecd6629..21c146b60 100644 --- a/packages/core/src/historical-sync/service.ts +++ b/packages/core/src/historical-sync/service.ts @@ -277,7 +277,7 @@ export class HistoricalSyncService extends Emittery { chainId: source.chainId, logFilter: { address: source.criteria.address, - topics: [source.criteria.eventSelector, null, null, null], + topics: [source.criteria.eventSelector], }, }); const factoryChildAddressProgressTracker = new ProgressTracker({ @@ -627,7 +627,7 @@ export class HistoricalSyncService extends Emittery { const logs = await this._eth_getLogs({ address: factory.criteria.address, - topics: [factory.criteria.eventSelector, null, null, null], + topics: [factory.criteria.eventSelector], fromBlock: toHex(fromBlock), toBlock: toHex(toBlock), }); @@ -649,7 +649,7 @@ export class HistoricalSyncService extends Emittery { chainId: factory.chainId, logFilter: { address: factory.criteria.address, - topics: [factory.criteria.eventSelector, null, null, null], + topics: [factory.criteria.eventSelector], }, block, transactions: block.transactions.filter((tx) => diff --git a/packages/core/src/realtime-sync/bloom.ts b/packages/core/src/realtime-sync/bloom.ts index dad97aef7..544c49909 100644 --- a/packages/core/src/realtime-sync/bloom.ts +++ b/packages/core/src/realtime-sync/bloom.ts @@ -4,6 +4,8 @@ import { } from "ethereum-bloom-filters"; import type { Address, Hex } from "viem"; +import { Topics } from "@/config/sources"; + export function isMatchedLogInBloomFilter({ bloom, logFilters, @@ -11,7 +13,7 @@ export function isMatchedLogInBloomFilter({ bloom: Hex; logFilters: { address?: Address | Address[]; - topics?: (Hex | Hex[] | null)[]; + topics?: Topics; }[]; }) { const allAddresses: Address[] = []; diff --git a/packages/core/src/realtime-sync/filter.ts b/packages/core/src/realtime-sync/filter.ts index d80722a58..01171b03e 100644 --- a/packages/core/src/realtime-sync/filter.ts +++ b/packages/core/src/realtime-sync/filter.ts @@ -1,5 +1,7 @@ import type { Address, Hex, RpcLog } from "viem"; +import { Topics } from "@/config/sources"; + export function filterLogs({ logs, logFilters, @@ -7,7 +9,7 @@ export function filterLogs({ logs: RpcLog[]; logFilters: { address?: Address | Address[]; - topics?: (Hex | Hex[] | null)[]; + topics?: Topics; }[]; }) { return logs.filter((log) => { @@ -28,7 +30,7 @@ export function isLogMatchedByFilter({ topics: Hex[]; }; address?: Address | Address[]; - topics?: (Hex | Hex[] | null)[]; + topics?: Topics; }) { if (address !== undefined && address.length > 0) { if (Array.isArray(address)) { From 7a9a2529a30bef2b939be9850d3e371502727f8b Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 12:36:39 -0400 Subject: [PATCH 10/44] cleanup --- packages/core/src/config/abi.test.ts | 280 -------------------- packages/core/src/config/abi.ts | 76 ------ packages/core/src/config/contracts.ts | 53 ---- packages/core/src/indexing/contract.test.ts | 136 ---------- 4 files changed, 545 deletions(-) delete mode 100644 packages/core/src/config/abi.test.ts delete mode 100644 packages/core/src/config/contracts.ts delete mode 100644 packages/core/src/indexing/contract.test.ts diff --git a/packages/core/src/config/abi.test.ts b/packages/core/src/config/abi.test.ts deleted file mode 100644 index b4c4ad645..000000000 --- a/packages/core/src/config/abi.test.ts +++ /dev/null @@ -1,280 +0,0 @@ -// import { randomUUID } from "node:crypto"; -// import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -// import { tmpdir } from "node:os"; -// import path from "node:path"; -// import { beforeEach, expect, test } from "vitest"; - -// import { buildAbi, getEvents } from "./abi"; - -// const abiSimple = [ -// { -// inputs: [], -// stateMutability: "nonpayable", -// type: "constructor", -// }, -// { -// inputs: [ -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// ], -// name: "Transfer", -// type: "event", -// }, -// { -// inputs: [ -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// ], -// name: "Approve", -// type: "event", -// }, -// ] as const; - -// const abiWithSameEvent = [ -// { -// inputs: [], -// stateMutability: "nonpayable", -// type: "constructor", -// }, -// { -// inputs: [ -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// ], -// name: "Approve", -// type: "event", -// }, -// ] as const; - -// let tmpDir: string; -// let configFilePath: string; - -// beforeEach(() => { -// tmpDir = path.join(tmpdir(), randomUUID()); -// configFilePath = path.join(tmpDir, "ponder.config.ts"); - -// mkdirSync(tmpDir, { recursive: true }); - -// return () => rmSync(tmpDir, { recursive: true, force: true }); -// }); - -// test("buildAbi handles a single ABI passed as a file path", () => { -// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); -// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - -// const { abi, filePaths } = buildAbi({ -// abiConfig: "./abiSimple.json", -// configFilePath, -// }); - -// expect(abi).toMatchObject(abiSimple); -// expect(filePaths).toMatchObject([abiSimplePath]); -// }); - -// test("buildAbi handles a single ABI passed as an object", () => { -// const { abi, filePaths } = buildAbi({ -// abiConfig: abiSimple, -// configFilePath, -// }); - -// expect(abi).toMatchObject(abiSimple); -// expect(filePaths).toMatchObject([]); -// }); - -// test("buildAbi handles an array with a single ABI passed as a file path", () => { -// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); -// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - -// const { abi, filePaths } = buildAbi({ -// abiConfig: ["./abiSimple.json"], -// configFilePath, -// }); - -// expect(abi).toMatchObject(abiSimple.filter((x) => x.type !== "constructor")); -// expect(filePaths).toMatchObject([abiSimplePath]); -// }); - -// test("buildAbi handles an array of ABIs passed as file paths", () => { -// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); -// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); -// const abiWithSameEventPath = path.join(tmpDir, "abiWithSameEvent.json"); -// writeFileSync(abiWithSameEventPath, JSON.stringify(abiWithSameEvent)); - -// const { abi, filePaths } = buildAbi({ -// abiConfig: ["./abiSimple.json", "./abiWithSameEvent.json"], -// configFilePath, -// }); - -// expect(abi.filter((x) => x.type === "event")).toMatchObject( -// abiSimple.filter((x) => x.type === "event") -// ); -// expect(filePaths).toMatchObject([abiSimplePath, abiWithSameEventPath]); -// }); - -// test("buildAbi handles an array of ABIs with both file paths and objects", () => { -// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); -// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - -// const { abi, filePaths } = buildAbi({ -// abiConfig: ["./abiSimple.json", abiWithSameEvent], -// configFilePath, -// }); - -// expect(abi.filter((x) => x.type === "event")).toMatchObject( -// abiSimple.filter((x) => x.type === "event") -// ); -// expect(filePaths).toMatchObject([abiSimplePath]); -// }); - -// test("buildAbi handles an array of ABIs and removes duplicate abi items", () => { -// const abiSimplePath = path.join(tmpDir, "abiSimple.json"); -// writeFileSync(abiSimplePath, JSON.stringify(abiSimple)); - -// const { abi, filePaths } = buildAbi({ -// abiConfig: ["./abiSimple.json", abiWithSameEvent], -// configFilePath, -// }); - -// expect(abi.filter((x) => x.type === "event")).toMatchObject( -// abiSimple.filter((x) => x.type === "event") -// ); -// expect(filePaths).toMatchObject([abiSimplePath]); -// }); - -// const abiWithOverloadedEvents = [ -// { -// inputs: [], -// stateMutability: "nonpayable", -// type: "constructor", -// }, -// { -// inputs: [ -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// ], -// name: "Transfer", -// type: "event", -// }, -// { -// inputs: [ -// { -// indexed: true, -// type: "uint8", -// }, -// { -// indexed: true, -// type: "uint256", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// { -// indexed: false, -// type: "address", -// }, -// ], -// name: "Transfer", -// type: "event", -// }, -// ] as const; - -// test("getEvents handles overloaded events", () => { -// const events = getEvents({ abi: abiWithOverloadedEvents }); - -// expect(events).toMatchObject({ -// "Transfer(address indexed, address indexed, uint256)": { -// safeName: "Transfer(address indexed, address indexed, uint256)", -// signature: "event Transfer(address indexed, address indexed, uint256)", -// selector: -// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", -// abiItem: { -// inputs: [ -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: true, -// type: "address", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// ], -// name: "Transfer", -// type: "event", -// }, -// }, -// "Transfer(uint8 indexed, uint256 indexed, uint256, address)": { -// safeName: "Transfer(uint8 indexed, uint256 indexed, uint256, address)", -// signature: -// "event Transfer(uint8 indexed, uint256 indexed, uint256, address)", -// selector: -// "0x7d80b356169a1ce57762f79d1bc650835653d9798678ef3691964dfcde65cd76", -// abiItem: { -// inputs: [ -// { -// indexed: true, -// type: "uint8", -// }, -// { -// indexed: true, -// type: "uint256", -// }, -// { -// indexed: false, -// type: "uint256", -// }, -// { -// indexed: false, -// type: "address", -// }, -// ], -// name: "Transfer", -// type: "event", -// }, -// }, -// }); -// }); diff --git a/packages/core/src/config/abi.ts b/packages/core/src/config/abi.ts index 13e6d40cd..948893faf 100644 --- a/packages/core/src/config/abi.ts +++ b/packages/core/src/config/abi.ts @@ -3,82 +3,6 @@ import { type Hex, getEventSelector } from "viem"; import { getDuplicateElements } from "@/utils/duplicates"; -// export const buildAbi = ({ -// abiConfig, -// configFilePath, -// }: { -// abiConfig: string | any[] | object | (string | any[] | object)[]; -// configFilePath: string; -// }) => { -// let resolvedAbi: Abi; -// const filePaths: string[] = []; - -// if ( -// typeof abiConfig === "string" || -// (Array.isArray(abiConfig) && -// (abiConfig.length === 0 || typeof abiConfig[0] === "object")) -// ) { -// // If abiConfig is a string or an ABI itself, treat it as a single ABI. -// const { abi, filePath } = buildSingleAbi({ abiConfig, configFilePath }); -// resolvedAbi = abi; -// if (filePath) filePaths.push(filePath); -// } else { -// // Otherwise, handle as an array of of ABIs. -// const results = (abiConfig as (object | any[])[]).map((a) => -// buildSingleAbi({ abiConfig: a, configFilePath }) -// ); - -// const mergedAbi = results -// .map(({ abi }) => abi.filter((item) => item.type !== "constructor")) -// .flat() -// .flat(); -// const mergedUniqueAbi = [ -// ...new Map( -// mergedAbi.map((item) => [JSON.stringify(item), item]) -// ).values(), -// ]; - -// filePaths.push( -// ...results.map((r) => r.filePath).filter((f): f is string => !!f) -// ); - -// resolvedAbi = mergedUniqueAbi; -// } - -// return { -// abi: resolvedAbi, -// filePaths, -// }; -// }; - -// const buildSingleAbi = ({ -// abiConfig, -// configFilePath, -// }: { -// abiConfig: string | any[] | object; -// configFilePath: string; -// }) => { -// let filePath: string | undefined = undefined; -// let abi: Abi; - -// if (typeof abiConfig === "string") { -// // If a string, treat it as a file path. -// filePath = path.isAbsolute(abiConfig) -// ? abiConfig -// : path.join(path.dirname(configFilePath), abiConfig); - -// const abiString = readFileSync(filePath, "utf-8"); -// abi = JSON.parse(abiString); -// } else { -// // Otherwise, treat as the ABI itself -// abi = abiConfig as unknown as Abi; -// } - -// // NOTE: Not currently using the filePath arg here, but eventually -// // could use it to watch for changes and reload. -// return { abi, filePath }; -// }; - export type LogEventMetadata = { // Event name (if no overloads) or full event signature (if name is overloaded). // This is the event name used when registering indexing functions using `ponder.on("ContractName:EventName", ...)` diff --git a/packages/core/src/config/contracts.ts b/packages/core/src/config/contracts.ts deleted file mode 100644 index 90e753490..000000000 --- a/packages/core/src/config/contracts.ts +++ /dev/null @@ -1,53 +0,0 @@ -// import type { Abi, Address } from "abitype"; - -// import type { Options } from "@/config/options"; -// import type { ResolvedConfig } from "@/config/types"; -// import { toLowerCase } from "@/utils/lowercase"; - -// import { buildAbi } from "./abi"; -// import type { Network } from "./networks"; - -// export type Contract = { -// name: string; -// address: Address; -// network: Network; -// abi: Abi; -// }; - -// export function buildContracts({ -// config, -// options, -// networks, -// }: { -// config: ResolvedConfig; -// options: Options; -// networks: Network[]; -// }) { -// const contracts = config.contracts ?? []; - -// return contracts -// .filter( -// ( -// contract -// ): contract is (typeof contracts)[number] & { address: Address } => -// !!contract.address -// ) -// .map((contract) => { -// const address = toLowerCase(contract.address); - -// const { abi } = buildAbi({ -// abiConfig: contract.abi, -// configFilePath: options.configFile, -// }); - -// // Get the contract network/provider. -// const network = networks.find((n) => n.name === contract.network); -// if (!network) { -// throw new Error( -// `Network [${contract.network}] not found for contract: ${contract.name}` -// ); -// } - -// return { name: contract.name, address, network, abi } satisfies Contract; -// }); -// } diff --git a/packages/core/src/indexing/contract.test.ts b/packages/core/src/indexing/contract.test.ts deleted file mode 100644 index e2f098d53..000000000 --- a/packages/core/src/indexing/contract.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -// import { getFunctionSelector, hexToBigInt } from "viem"; -// import { beforeEach, expect, test, vi } from "vitest"; - -// import { usdcContractConfig } from "@/_test/constants"; -// import { setupEventStore } from "@/_test/setup"; -// import { publicClient } from "@/_test/utils"; -// import type { Contract } from "@/config/contracts"; -// import type { Network } from "@/config/networks"; - -// import { buildReadOnlyContracts } from "./contract"; - -// beforeEach((context) => setupEventStore(context)); - -// const network: Network = { -// name: "mainnet", -// chainId: 1, -// client: publicClient, -// pollingInterval: 1_000, -// defaultMaxBlockRange: 3, -// finalityBlockCount: 10, -// maxRpcRequestConcurrency: 10, -// }; - -// const contracts: Contract[] = [ -// { -// name: "USDC", -// address: usdcContractConfig.address, -// abi: usdcContractConfig.abi, -// network: network, -// }, -// ]; - -// // Test data generated from Alchemy Composer. -// const usdcTotalSupply16375000 = 40921687992499550n; -// const usdcTotalSupply16380000 = 40695630049769550n; // This is "latest" for our test setup. - -// test("getInjectedContract() returns data", async (context) => { -// const { eventStore } = context; - -// const readOnlyContracts = buildReadOnlyContracts({ -// contracts, -// getCurrentBlockNumber: () => 16375000n, -// eventStore, -// }); -// const contract = readOnlyContracts["USDC"]; - -// const decimals = await contract.read.decimals(); -// expect(decimals).toBe(6); -// }); - -// test("getInjectedContract() uses current block number if no overrides are provided", async (context) => { -// const { eventStore } = context; - -// const readOnlyContracts = buildReadOnlyContracts({ -// contracts, -// getCurrentBlockNumber: () => 16375000n, -// eventStore, -// }); -// const contract = readOnlyContracts["USDC"]; - -// const totalSupply = await contract.read.totalSupply(); - -// expect(totalSupply).toBe(usdcTotalSupply16375000); -// }); - -// test("getInjectedContract() caches the read result if no overrides are provided", async (context) => { -// const { eventStore } = context; - -// const callSpy = vi.spyOn(network.client, "call"); - -// const readOnlyContracts = buildReadOnlyContracts({ -// contracts, -// getCurrentBlockNumber: () => 16375000n, -// eventStore, -// }); -// const contract = readOnlyContracts["USDC"]; - -// await contract.read.totalSupply(); - -// expect(callSpy).toHaveBeenCalledTimes(1); - -// const cachedContractReadResult = await eventStore.getContractReadResult({ -// address: contract.address, -// blockNumber: 16375000n, -// chainId: 1, -// data: getFunctionSelector("totalSupply()"), -// }); - -// expect(cachedContractReadResult).not.toBeNull(); -// expect(hexToBigInt(cachedContractReadResult!.result)).toBe( -// usdcTotalSupply16375000 -// ); - -// expect(callSpy).toHaveBeenCalledTimes(1); -// }); - -// test("getInjectedContract() uses blockTag override if provided", async (context) => { -// const { eventStore } = context; - -// const readOnlyContracts = buildReadOnlyContracts({ -// contracts, -// getCurrentBlockNumber: () => 16375000n, -// eventStore, -// }); -// const contract = readOnlyContracts["USDC"]; - -// const totalSupply = await contract.read.totalSupply({ -// blockTag: "latest", -// }); - -// expect(totalSupply).toBe(usdcTotalSupply16380000); -// }); - -// test("getInjectedContract() does not cache data if blockTag override is provided", async (context) => { -// const { eventStore } = context; - -// const readOnlyContracts = buildReadOnlyContracts({ -// contracts, -// getCurrentBlockNumber: () => 16375000n, -// eventStore, -// }); -// const contract = readOnlyContracts["USDC"]; - -// await contract.read.totalSupply({ -// blockTag: "latest", -// }); - -// const cachedContractReadResult = await eventStore.getContractReadResult({ -// address: contract.address, -// blockNumber: 16375000n, -// chainId: 1, -// data: getFunctionSelector("totalSupply()"), -// }); - -// expect(cachedContractReadResult).toBeNull(); -// }); From d0583b3dc55ceb840b0ae7f186d5c8ac08d36e2d Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:07:56 -0400 Subject: [PATCH 11/44] end to end not working --- packages/core/src/Ponder.ts | 2 +- .../_test/art-gobblers/app/ponder.config.ts | 6 ++--- .../core/src/_test/ens/app/ponder.config.ts | 6 ++--- packages/core/src/build/config.ts | 2 +- packages/core/src/config/config.test.ts | 23 +++++++++++++++++ .../core/src/config/{types.ts => config.ts} | 25 ++++++++++++------- packages/core/src/config/database.ts | 2 +- packages/core/src/config/networks.ts | 2 +- packages/core/src/config/options.ts | 2 +- packages/core/src/config/sources.ts | 2 +- packages/core/src/index.ts | 2 +- 11 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 packages/core/src/config/config.test.ts rename packages/core/src/config/{types.ts => config.ts} (86%) diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index 7da2da157..cd4977b4e 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -3,10 +3,10 @@ import process from "node:process"; import { BuildService } from "@/build/service"; import { CodegenService } from "@/codegen/service"; +import { type ResolvedConfig } from "@/config/config"; import { buildDatabase } from "@/config/database"; import { type Network, buildNetwork } from "@/config/networks"; import { type Options } from "@/config/options"; -import { type ResolvedConfig } from "@/config/types"; import { UserErrorService } from "@/errors/service"; import { EventAggregatorService } from "@/event-aggregator/service"; import { PostgresEventStore } from "@/event-store/postgres/store"; diff --git a/packages/core/src/_test/art-gobblers/app/ponder.config.ts b/packages/core/src/_test/art-gobblers/app/ponder.config.ts index 7540d1e6a..f460890b3 100644 --- a/packages/core/src/_test/art-gobblers/app/ponder.config.ts +++ b/packages/core/src/_test/art-gobblers/app/ponder.config.ts @@ -1,9 +1,9 @@ import { http } from "viem"; -import type { Config } from "../../../../dist"; +import { createConfig } from "../../../../dist"; import { ArtGobblersAbi } from "./ArtGobblers.abi"; -export const config: Config = { +export const config = createConfig({ networks: [ { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, ], @@ -17,4 +17,4 @@ export const config: Config = { endBlock: 15870405, // 5 blocks }, ], -}; +}); diff --git a/packages/core/src/_test/ens/app/ponder.config.ts b/packages/core/src/_test/ens/app/ponder.config.ts index 0b733399f..c58f7909f 100644 --- a/packages/core/src/_test/ens/app/ponder.config.ts +++ b/packages/core/src/_test/ens/app/ponder.config.ts @@ -1,9 +1,9 @@ import { http } from "viem"; -import type { Config } from "../../../../dist"; +import { createConfig } from "../../../../dist"; import { BaseRegistrarImplementationAbi } from "./BaseRegistrarImplementation.abi"; -export const config: Config = { +export const config = createConfig({ networks: [ { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, ], @@ -18,4 +18,4 @@ export const config: Config = { maxBlockRange: 10, }, ], -}; +}); diff --git a/packages/core/src/build/config.ts b/packages/core/src/build/config.ts index 601ee0d8d..ae03efc90 100644 --- a/packages/core/src/build/config.ts +++ b/packages/core/src/build/config.ts @@ -2,7 +2,7 @@ import { build } from "esbuild"; import { existsSync, rmSync } from "node:fs"; import path from "node:path"; -import type { ResolvedConfig } from "@/config/types"; +import type { ResolvedConfig } from "@/config/config"; import { ensureDirExists } from "@/utils/exists"; export const buildConfig = async ({ configFile }: { configFile: string }) => { diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts new file mode 100644 index 000000000..9d66e85ca --- /dev/null +++ b/packages/core/src/config/config.test.ts @@ -0,0 +1,23 @@ +import { http } from "viem"; +import { test } from "vitest"; + +import { createConfig } from "./config"; + +test("createConfig enforces matching network names", () => { + createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: [], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); +}); diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/config.ts similarity index 86% rename from packages/core/src/config/types.ts rename to packages/core/src/config/config.ts index 5c3045375..cf3b48100 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/config.ts @@ -1,14 +1,16 @@ import type { Abi, AbiEvent } from "abitype"; import type { Transport } from "viem"; -type ContractRequired = { +type ContractRequired< + TNetworkName extends string | unknown = string | unknown +> = { /** Contract name. Must be unique across `contracts` and `filters`. */ name: string; /** * Network that this contract is deployed to. Must match a network name in `networks`. * Any filter information overrides the values in the higher level "contracts" property. Factories cannot override an address and vice versa. */ - network: ({ name: string } & Partial)[]; + network: ({ name: TNetworkName } & Partial)[]; abi: Abi; }; @@ -44,7 +46,9 @@ type ContractFilter = ( | AbiEvent[]; }; -export type ResolvedConfig = { +export type ResolvedConfig< + TNetworkName extends string | unknown = string | unknown +> = { /** Database to use for storing blockchain & entity data. Default: `"postgres"` if `DATABASE_URL` env var is present, otherwise `"sqlite"`. */ database?: | { @@ -84,7 +88,7 @@ export type ResolvedConfig = { maxRpcRequestConcurrency?: number; }[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: (ContractRequired & ContractFilter)[]; + contracts?: (ContractRequired & ContractFilter)[]; /** Configuration for Ponder internals. */ options?: { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ @@ -92,8 +96,11 @@ export type ResolvedConfig = { }; }; -export type Config = - | ResolvedConfig - | Promise - | (() => ResolvedConfig) - | (() => Promise); +/** + * Identity function for type-level validation of config + */ +export const createConfig = < + const TConfig extends ResolvedConfig +>( + config: TConfig +) => config; diff --git a/packages/core/src/config/database.ts b/packages/core/src/config/database.ts index a4eaad923..2366bfbeb 100644 --- a/packages/core/src/config/database.ts +++ b/packages/core/src/config/database.ts @@ -2,8 +2,8 @@ import Sqlite from "better-sqlite3"; import path from "node:path"; import pg, { Client, DatabaseError, Pool } from "pg"; +import type { ResolvedConfig } from "@/config/config"; import type { Options } from "@/config/options"; -import type { ResolvedConfig } from "@/config/types"; import { PostgresError } from "@/errors/postgres"; import { SqliteError } from "@/errors/sqlite"; import { ensureDirExists } from "@/utils/exists"; diff --git a/packages/core/src/config/networks.ts b/packages/core/src/config/networks.ts index 39b1a363f..8ecd9bd61 100644 --- a/packages/core/src/config/networks.ts +++ b/packages/core/src/config/networks.ts @@ -1,7 +1,7 @@ import { type Client, type PublicClient, createPublicClient } from "viem"; import * as chains from "viem/chains"; -import type { ResolvedConfig } from "@/config/types"; +import type { ResolvedConfig } from "@/config/config"; import type { Common } from "@/Ponder"; export type Network = { diff --git a/packages/core/src/config/options.ts b/packages/core/src/config/options.ts index 8382ecba2..d67114a91 100644 --- a/packages/core/src/config/options.ts +++ b/packages/core/src/config/options.ts @@ -3,7 +3,7 @@ import type { LevelWithSilent } from "pino"; import type { CliOptions } from "@/bin/ponder"; -import type { ResolvedConfig } from "./types"; +import type { ResolvedConfig } from "./config"; export type Options = { configFile: string; diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 50b74b519..7e59e39f6 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -3,9 +3,9 @@ import { Abi, Address, encodeEventTopics, Hex } from "viem"; import { toLowerCase } from "@/utils/lowercase"; import { AbiEvents, getEvents } from "./abi"; +import { ResolvedConfig } from "./config"; import { buildFactoryCriteria } from "./factories"; import { Options } from "./options"; -import { ResolvedConfig } from "./types"; /** * There are up to 4 topics in an EVM event diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d26b8e021..09dc15b00 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,5 @@ export { PonderApp } from "@/build/functions"; -export type { Config, ResolvedConfig } from "@/config/types"; +export { createConfig } from "@/config/config"; export type { Block } from "@/types/block"; export type { ReadOnlyContract } from "@/types/contract"; export type { Log } from "@/types/log"; From 820c8793a7eb1954f930c18cda4617cf7aa90ce9 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:14:37 -0400 Subject: [PATCH 12/44] fix end to end issue --- packages/core/.gitignore | 3 ++- packages/core/src/build/config.ts | 44 ++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/core/.gitignore b/packages/core/.gitignore index 49dea9499..4c13059ce 100644 --- a/packages/core/.gitignore +++ b/packages/core/.gitignore @@ -1,4 +1,5 @@ dist/ .ponder/ generated/ -.env.local \ No newline at end of file +.env.local +**/*.node \ No newline at end of file diff --git a/packages/core/src/build/config.ts b/packages/core/src/build/config.ts index ae03efc90..7ffd50c1c 100644 --- a/packages/core/src/build/config.ts +++ b/packages/core/src/build/config.ts @@ -1,10 +1,51 @@ -import { build } from "esbuild"; +import { build, Plugin } from "esbuild"; import { existsSync, rmSync } from "node:fs"; import path from "node:path"; import type { ResolvedConfig } from "@/config/config"; import { ensureDirExists } from "@/utils/exists"; +/** + * Fixes issue with createConfig() being built + * + * {@link} https://github.com/evanw/esbuild/issues/1051 + */ +const nativeNodeModulesPlugin: Plugin = { + name: "native-node-modules", + setup(build) { + // If a ".node" file is imported within a module in the "file" namespace, resolve + // it to an absolute path and put it into the "node-file" virtual namespace. + build.onResolve({ filter: /\.node$/, namespace: "file" }, (args) => ({ + path: require.resolve(args.path, { paths: [args.resolveDir] }), + namespace: "node-file", + })); + + // Files in the "node-file" virtual namespace call "require()" on the + // path from esbuild of the ".node" file in the output directory. + build.onLoad({ filter: /.*/, namespace: "node-file" }, (args) => ({ + contents: ` + import path from ${JSON.stringify(args.path)} + try { module.exports = require(path) } + catch {} + `, + })); + + // If a ".node" file is imported within a module in the "node-file" namespace, put + // it in the "file" namespace where esbuild's default loading behavior will handle + // it. It is already an absolute path since we resolved it to one above. + build.onResolve({ filter: /\.node$/, namespace: "node-file" }, (args) => ({ + path: args.path, + namespace: "file", + })); + + // Tell esbuild's default loading behavior to use the "file" loader for + // these ".node" files. + const opts = build.initialOptions; + opts.loader = opts.loader || {}; + opts.loader[".node"] = "file"; + }, +}; + export const buildConfig = async ({ configFile }: { configFile: string }) => { if (!existsSync(configFile)) { throw new Error(`Ponder config file not found, expected: ${configFile}`); @@ -22,6 +63,7 @@ export const buildConfig = async ({ configFile }: { configFile: string }) => { outfile: buildFile, platform: "node", format: "cjs", + plugins: [nativeNodeModulesPlugin], // Note: Flipped to true in order to be able to import external files into ponder.config.ts bundle: true, logLevel: "silent", From 9eb46883088fc063cc15017e4b7336870caff6dd Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:21:44 -0400 Subject: [PATCH 13/44] handle all types of config --- packages/core/src/config/config.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index cf3b48100..1946ccfa9 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -10,7 +10,7 @@ type ContractRequired< * Network that this contract is deployed to. Must match a network name in `networks`. * Any filter information overrides the values in the higher level "contracts" property. Factories cannot override an address and vice versa. */ - network: ({ name: TNetworkName } & Partial)[]; + network: readonly ({ name: TNetworkName } & Partial)[]; abi: Abi; }; @@ -62,7 +62,7 @@ export type ResolvedConfig< connectionString?: string; }; /** List of blockchain networks. */ - networks: { + networks: readonly { /** Network name. Must be unique across all networks. */ name: string; /** Chain ID of the network. */ @@ -88,7 +88,7 @@ export type ResolvedConfig< maxRpcRequestConcurrency?: number; }[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: (ContractRequired & ContractFilter)[]; + contracts?: readonly (ContractRequired & ContractFilter)[]; /** Configuration for Ponder internals. */ options?: { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ @@ -102,5 +102,9 @@ export type ResolvedConfig< export const createConfig = < const TConfig extends ResolvedConfig >( - config: TConfig + config: + | TConfig + | Promise + | (() => TConfig) + | (() => Promise) ) => config; From dda40eeba7c7a41731da8005341377589bdb9d16 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:31:39 -0400 Subject: [PATCH 14/44] update vitest and vite for const type assertions --- packages/core/package.json | 4 +- pnpm-lock.yaml | 662 +++++++++++++++++++++++++++++++------ 2 files changed, 556 insertions(+), 110 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index d33ca8aee..2ed844058 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -86,7 +86,7 @@ "rimraf": "^5.0.1", "supertest": "^6.3.3", "typescript": "^5.1.3", - "vite": "^4.1.4", - "vitest": "^0.29.2" + "vite": "^4.5.0", + "vitest": "^0.34.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09e1d6cb9..322480909 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,7 +57,7 @@ importers: devDependencies: '@graphprotocol/graph-cli': specifier: ^0.51.1 - version: 0.51.1(@types/node@18.16.18)(node-fetch@2.6.11)(typescript@5.1.3) + version: 0.51.1(@types/node@18.16.18)(node-fetch@3.3.2)(typescript@5.1.3) '@graphprotocol/graph-ts': specifier: ^0.31.0 version: 0.31.0 @@ -462,11 +462,11 @@ importers: specifier: ^5.1.3 version: 5.1.3 vite: - specifier: ^4.1.4 - version: 4.1.4(@types/node@18.16.18) + specifier: ^4.5.0 + version: 4.5.0(@types/node@18.16.18) vitest: - specifier: ^0.29.2 - version: 0.29.2 + specifier: ^0.34.6 + version: 0.34.6 packages/create-ponder: dependencies: @@ -530,10 +530,10 @@ importers: dependencies: '@typescript-eslint/eslint-plugin': specifier: ^6.3.0 - version: 6.3.0(@typescript-eslint/parser@6.3.0)(eslint@8.43.0)(typescript@5.1.3) + version: 6.3.0(@typescript-eslint/parser@6.3.0)(eslint@8.43.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^6.3.0 - version: 6.3.0(eslint@8.43.0)(typescript@5.1.3) + version: 6.3.0(eslint@8.43.0)(typescript@5.2.2) eslint: specifier: '>= 3' version: 8.43.0 @@ -2029,8 +2029,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@esbuild/android-arm64@0.16.17: - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -2047,8 +2047,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.16.17: - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -2065,8 +2065,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.16.17: - resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -2083,8 +2083,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.16.17: - resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -2101,8 +2101,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.16.17: - resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -2119,8 +2119,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.16.17: - resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -2137,8 +2137,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.16.17: - resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -2155,8 +2155,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.16.17: - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -2173,8 +2173,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.16.17: - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -2191,8 +2191,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.16.17: - resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -2218,8 +2218,8 @@ packages: dev: false optional: true - /@esbuild/linux-loong64@0.16.17: - resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -2236,8 +2236,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.16.17: - resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -2254,8 +2254,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.16.17: - resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -2272,8 +2272,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.16.17: - resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -2290,8 +2290,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.16.17: - resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -2308,8 +2308,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.16.17: - resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -2326,8 +2326,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.16.17: - resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -2344,8 +2344,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.16.17: - resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -2362,8 +2362,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.16.17: - resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -2380,8 +2380,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.16.17: - resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -2398,8 +2398,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.16.17: - resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -2416,8 +2416,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.16.17: - resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -2636,7 +2636,7 @@ packages: js-yaml: 4.1.0 dev: true - /@graphprotocol/graph-cli@0.51.1(@types/node@18.16.18)(node-fetch@2.6.11)(typescript@5.1.3): + /@graphprotocol/graph-cli@0.51.1(@types/node@18.16.18)(node-fetch@3.3.2)(typescript@5.1.3): resolution: {integrity: sha512-6W1aphU/0Uq1ju8e4Ya5plmXUcjCR2N30wjDrf5Cn6QjHsk9VUqMt4x3+D3DHGSVvXve2+ThiEU2s2xktP3PTw==} engines: {node: '>=14'} hasBin: true @@ -2656,7 +2656,7 @@ packages: gluegun: 5.1.2(debug@4.3.4) graphql: 15.5.0 immutable: 4.2.1 - ipfs-http-client: 55.0.0(node-fetch@2.6.11) + ipfs-http-client: 55.0.0(node-fetch@3.3.2) jayson: 4.0.0 js-yaml: 3.14.1 prettier: 1.19.1 @@ -2745,6 +2745,13 @@ packages: wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -3266,6 +3273,10 @@ packages: - encoding dev: false + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.21.0): resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -3500,10 +3511,20 @@ packages: '@types/chai': 4.3.4 dev: true + /@types/chai-subset@1.3.4: + resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} + dependencies: + '@types/chai': 4.3.9 + dev: true + /@types/chai@4.3.4: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true + /@types/chai@4.3.9: + resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} + dev: true + /@types/cli-progress@3.11.0: resolution: {integrity: sha512-XhXhBv1R/q2ahF3BM7qT5HLzJNlIL0wbcGyZVjqOTqAybAnsLisd7gy1UCyIqpL+5Iv6XhlSyzjLCnI2sIdbCg==} dependencies: @@ -3815,6 +3836,37 @@ packages: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/eslint-plugin@6.3.0(@typescript-eslint/parser@6.3.0)(eslint@8.43.0)(typescript@5.2.2): + resolution: {integrity: sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.5.1 + '@typescript-eslint/parser': 6.3.0(eslint@8.43.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.3.0 + '@typescript-eslint/type-utils': 6.3.0(eslint@8.43.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.3.0(eslint@8.43.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.3.0 + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.43.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + natural-compare-lite: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false /@typescript-eslint/parser@5.59.11(eslint@8.43.0)(typescript@4.9.5): resolution: {integrity: sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==} @@ -3855,6 +3907,28 @@ packages: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/parser@6.3.0(eslint@8.43.0)(typescript@5.2.2): + resolution: {integrity: sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.3.0 + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/typescript-estree': 6.3.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.3.0 + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.43.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false /@typescript-eslint/scope-manager@5.59.11: resolution: {integrity: sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==} @@ -3889,6 +3963,27 @@ packages: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/type-utils@6.3.0(eslint@8.43.0)(typescript@5.2.2): + resolution: {integrity: sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.3.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.3.0(eslint@8.43.0)(typescript@5.2.2) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.43.0 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false /@typescript-eslint/types@5.59.11: resolution: {integrity: sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==} @@ -3939,6 +4034,28 @@ packages: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/typescript-estree@6.3.0(typescript@5.2.2): + resolution: {integrity: sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/visitor-keys': 6.3.0 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: false /@typescript-eslint/utils@6.3.0(eslint@8.43.0)(typescript@5.1.3): resolution: {integrity: sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==} @@ -3957,6 +4074,26 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true + + /@typescript-eslint/utils@6.3.0(eslint@8.43.0)(typescript@5.2.2): + resolution: {integrity: sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.3.0 + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/typescript-estree': 6.3.0(typescript@5.2.2) + eslint: 8.43.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: false /@typescript-eslint/visitor-keys@5.59.11: resolution: {integrity: sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==} @@ -3998,6 +4135,14 @@ packages: chai: 4.3.7 dev: true + /@vitest/expect@0.34.6: + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + dependencies: + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.3.10 + dev: true + /@vitest/runner@0.29.2: resolution: {integrity: sha512-A1P65f5+6ru36AyHWORhuQBJrOOcmDuhzl5RsaMNFe2jEkoj0faEszQS4CtPU/LxUYVIazlUtZTY0OEZmyZBnA==} dependencies: @@ -4006,12 +4151,34 @@ packages: pathe: 1.1.0 dev: true + /@vitest/runner@0.34.6: + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + dependencies: + '@vitest/utils': 0.34.6 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@0.34.6: + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + /@vitest/spy@0.29.2: resolution: {integrity: sha512-Hc44ft5kaAytlGL2PyFwdAsufjbdOvHklwjNy/gy/saRbg9Kfkxfh+PklLm1H2Ib/p586RkQeNFKYuJInUssyw==} dependencies: tinyspy: 1.1.1 dev: true + /@vitest/spy@0.34.6: + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + /@vitest/utils@0.29.2: resolution: {integrity: sha512-F14/Uc+vCdclStS2KEoXJlOLAEyqRhnw0gM27iXw9bMTcyKRPJrQ+rlC6XZ125GIPvvKYMPpVxNhiou6PsEeYQ==} dependencies: @@ -4022,6 +4189,14 @@ packages: pretty-format: 27.5.1 dev: true + /@vitest/utils@0.34.6: + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + /@wagmi/chains@1.0.0(typescript@5.1.3): resolution: {integrity: sha512-eNbqRWyHbivcMNq5tbXJks4NaOzVLHnNQauHPeE/EDT9AlpqzcrMc+v2T1/2Iw8zN4zgqB86NCsxeJHJs7+xng==} peerDependencies: @@ -4144,11 +4319,22 @@ packages: engines: {node: '>=0.4.0'} dev: true + /acorn-walk@8.3.0: + resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} hasBin: true + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -4846,6 +5032,19 @@ packages: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} @@ -4920,6 +5119,12 @@ packages: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -4932,7 +5137,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -5680,6 +5885,11 @@ packages: engines: {node: '>= 6'} dev: false + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + /dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} dev: true @@ -5859,6 +6069,11 @@ packages: wrappy: 1.0.2 dev: true + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -5874,11 +6089,11 @@ packages: dependencies: path-type: 4.0.0 - /dns-over-http-resolver@1.2.3(node-fetch@2.6.11): + /dns-over-http-resolver@1.2.3(node-fetch@3.3.2): resolution: {integrity: sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA==} dependencies: debug: 4.3.4(supports-color@8.1.1) - native-fetch: 3.0.0(node-fetch@2.6.11) + native-fetch: 3.0.0(node-fetch@3.3.2) receptacle: 1.3.2 transitivePeerDependencies: - node-fetch @@ -6384,34 +6599,34 @@ packages: esbuild-windows-arm64: 0.15.4 dev: false - /esbuild@0.16.17: - resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.16.17 - '@esbuild/android-arm64': 0.16.17 - '@esbuild/android-x64': 0.16.17 - '@esbuild/darwin-arm64': 0.16.17 - '@esbuild/darwin-x64': 0.16.17 - '@esbuild/freebsd-arm64': 0.16.17 - '@esbuild/freebsd-x64': 0.16.17 - '@esbuild/linux-arm': 0.16.17 - '@esbuild/linux-arm64': 0.16.17 - '@esbuild/linux-ia32': 0.16.17 - '@esbuild/linux-loong64': 0.16.17 - '@esbuild/linux-mips64el': 0.16.17 - '@esbuild/linux-ppc64': 0.16.17 - '@esbuild/linux-riscv64': 0.16.17 - '@esbuild/linux-s390x': 0.16.17 - '@esbuild/linux-x64': 0.16.17 - '@esbuild/netbsd-x64': 0.16.17 - '@esbuild/openbsd-x64': 0.16.17 - '@esbuild/sunos-x64': 0.16.17 - '@esbuild/win32-arm64': 0.16.17 - '@esbuild/win32-ia32': 0.16.17 - '@esbuild/win32-x64': 0.16.17 + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 dev: true /esbuild@0.18.4: @@ -7074,6 +7289,14 @@ packages: dependencies: reusify: 1.0.4 + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -7214,6 +7437,13 @@ packages: mime-types: 2.1.35 dev: true + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + /formidable@2.1.1: resolution: {integrity: sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==} dependencies: @@ -7281,8 +7511,8 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true @@ -7318,6 +7548,10 @@ packages: resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} dev: true + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -8066,19 +8300,19 @@ packages: engines: {node: '>= 0.10'} dev: false - /ipfs-core-types@0.9.0(node-fetch@2.6.11): + /ipfs-core-types@0.9.0(node-fetch@3.3.2): resolution: {integrity: sha512-VJ8vJSHvI1Zm7/SxsZo03T+zzpsg8pkgiIi5hfwSJlsrJ1E2v68QPlnLshGHUSYw89Oxq0IbETYl2pGTFHTWfg==} deprecated: js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details dependencies: interface-datastore: 6.1.1 - multiaddr: 10.0.1(node-fetch@2.6.11) + multiaddr: 10.0.1(node-fetch@3.3.2) multiformats: 9.9.0 transitivePeerDependencies: - node-fetch - supports-color dev: true - /ipfs-core-utils@0.13.0(node-fetch@2.6.11): + /ipfs-core-utils@0.13.0(node-fetch@3.3.2): resolution: {integrity: sha512-HP5EafxU4/dLW3U13CFsgqVO5Ika8N4sRSIb/dTg16NjLOozMH31TXV0Grtu2ZWo1T10ahTzMvrfT5f4mhioXw==} deprecated: js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details dependencies: @@ -8087,7 +8321,7 @@ packages: browser-readablestream-to-it: 1.0.3 debug: 4.3.4(supports-color@8.1.1) err-code: 3.0.1 - ipfs-core-types: 0.9.0(node-fetch@2.6.11) + ipfs-core-types: 0.9.0(node-fetch@3.3.2) ipfs-unixfs: 6.0.9 ipfs-utils: 9.0.14 it-all: 1.0.6 @@ -8095,8 +8329,8 @@ packages: it-peekable: 1.0.3 it-to-stream: 1.0.0 merge-options: 3.0.4 - multiaddr: 10.0.1(node-fetch@2.6.11) - multiaddr-to-uri: 8.0.0(node-fetch@2.6.11) + multiaddr: 10.0.1(node-fetch@3.3.2) + multiaddr-to-uri: 8.0.0(node-fetch@3.3.2) multiformats: 9.9.0 nanoid: 3.3.6 parse-duration: 1.1.0 @@ -8108,7 +8342,7 @@ packages: - supports-color dev: true - /ipfs-http-client@55.0.0(node-fetch@2.6.11): + /ipfs-http-client@55.0.0(node-fetch@3.3.2): resolution: {integrity: sha512-GpvEs7C7WL9M6fN/kZbjeh4Y8YN7rY8b18tVWZnKxRsVwM25cIFrRI8CwNt3Ugin9yShieI3i9sPyzYGMrLNnQ==} engines: {node: '>=14.0.0', npm: '>=3.0.0'} dependencies: @@ -8119,13 +8353,13 @@ packages: any-signal: 2.1.2 debug: 4.3.4(supports-color@8.1.1) err-code: 3.0.1 - ipfs-core-types: 0.9.0(node-fetch@2.6.11) - ipfs-core-utils: 0.13.0(node-fetch@2.6.11) + ipfs-core-types: 0.9.0(node-fetch@3.3.2) + ipfs-core-utils: 0.13.0(node-fetch@3.3.2) ipfs-utils: 9.0.14 it-first: 1.0.7 it-last: 1.0.6 merge-options: 3.0.4 - multiaddr: 10.0.1(node-fetch@2.6.11) + multiaddr: 10.0.1(node-fetch@3.3.2) multiformats: 9.9.0 native-abort-controller: 1.0.4(abort-controller@3.0.0) parse-duration: 1.1.0 @@ -8960,6 +9194,12 @@ packages: get-func-name: 2.0.0 dev: true + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -8981,6 +9221,13 @@ packages: resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} engines: {node: 14 || >=16.14} + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true @@ -9828,6 +10075,15 @@ packages: ufo: 1.1.1 dev: true + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.11.2 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.1 + dev: true + /module-alias@2.2.2: resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==} dev: true @@ -9847,21 +10103,21 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /multiaddr-to-uri@8.0.0(node-fetch@2.6.11): + /multiaddr-to-uri@8.0.0(node-fetch@3.3.2): resolution: {integrity: sha512-dq4p/vsOOUdVEd1J1gl+R2GFrXJQH8yjLtz4hodqdVbieg39LvBOdMQRdQnfbg5LSM/q1BYNVf5CBbwZFFqBgA==} deprecated: This module is deprecated, please upgrade to @multiformats/multiaddr-to-uri dependencies: - multiaddr: 10.0.1(node-fetch@2.6.11) + multiaddr: 10.0.1(node-fetch@3.3.2) transitivePeerDependencies: - node-fetch - supports-color dev: true - /multiaddr@10.0.1(node-fetch@2.6.11): + /multiaddr@10.0.1(node-fetch@3.3.2): resolution: {integrity: sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg==} deprecated: This module is deprecated, please upgrade to @multiformats/multiaddr dependencies: - dns-over-http-resolver: 1.2.3(node-fetch@2.6.11) + dns-over-http-resolver: 1.2.3(node-fetch@3.3.2) err-code: 3.0.1 is-ip: 3.1.0 multiformats: 9.9.0 @@ -9913,6 +10169,14 @@ packages: node-fetch: 2.6.11 dev: true + /native-fetch@3.0.0(node-fetch@3.3.2): + resolution: {integrity: sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==} + peerDependencies: + node-fetch: '*' + dependencies: + node-fetch: 3.3.2 + dev: true + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -10095,6 +10359,11 @@ packages: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + /node-fetch@2.6.11: resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} @@ -10106,6 +10375,15 @@ packages: dependencies: whatwg-url: 5.0.0 + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true @@ -10541,6 +10819,10 @@ packages: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} dev: true + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true @@ -10687,6 +10969,14 @@ packages: pathe: 1.1.0 dev: true + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + /pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -10729,8 +11019,8 @@ packages: source-map-js: 1.0.2 dev: false - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -10816,6 +11106,15 @@ packages: react-is: 17.0.2 dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true @@ -11027,6 +11326,10 @@ packages: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /react-native-fetch-api@3.0.0: resolution: {integrity: sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==} dependencies: @@ -11453,7 +11756,15 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 + dev: true + + /rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 dev: true /run-parallel@1.2.0: @@ -11891,6 +12202,10 @@ packages: resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} dev: true + /std-env@3.4.3: + resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} + dev: true + /stream-to-it@0.2.4: resolution: {integrity: sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ==} dependencies: @@ -12065,6 +12380,12 @@ packages: acorn: 8.10.0 dev: true + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.2 + dev: true + /style-to-object@0.4.1: resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==} dependencies: @@ -12346,16 +12667,30 @@ packages: resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} dev: true + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + /tinypool@0.3.1: resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} engines: {node: '>=14.0.0'} dev: true + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + dev: true + /tinyspy@1.1.1: resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} engines: {node: '>=14.0.0'} dev: true + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + /title@3.5.3: resolution: {integrity: sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==} hasBin: true @@ -12452,6 +12787,16 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.1.3 + dev: true + + /ts-api-utils@1.0.1(typescript@5.2.2): + resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.2.2 + dev: false /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -12674,10 +13019,20 @@ packages: engines: {node: '>=14.17'} hasBin: true + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: false + /ufo@1.1.1: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} dev: true + /ufo@1.3.1: + resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} + dev: true + /uint8arrays@3.1.1: resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} dependencies: @@ -13045,10 +13400,11 @@ packages: mlly: 1.1.1 pathe: 1.1.0 picocolors: 1.0.0 - vite: 4.1.4(@types/node@18.16.18) + vite: 4.5.0(@types/node@18.16.18) transitivePeerDependencies: - '@types/node' - less + - lightningcss - sass - stylus - sugarss @@ -13056,13 +13412,36 @@ packages: - terser dev: true - /vite@4.1.4(@types/node@18.16.18): - resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} + /vite-node@0.34.6(@types/node@18.16.18): + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + mlly: 1.4.2 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 4.5.0(@types/node@18.16.18) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@4.5.0(@types/node@18.16.18): + resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' + lightningcss: ^1.21.0 sass: '*' stylus: '*' sugarss: '*' @@ -13072,6 +13451,8 @@ packages: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true stylus: @@ -13082,12 +13463,11 @@ packages: optional: true dependencies: '@types/node': 18.16.18 - esbuild: 0.16.17 - postcss: 8.4.21 - resolve: 1.22.2 - rollup: 3.25.1 + esbuild: 0.18.20 + postcss: 8.4.31 + rollup: 3.29.4 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /vitest@0.29.2: @@ -13133,11 +13513,77 @@ packages: tinybench: 2.4.0 tinypool: 0.3.1 tinyspy: 1.1.1 - vite: 4.1.4(@types/node@18.16.18) + vite: 4.5.0(@types/node@18.16.18) vite-node: 0.29.2(@types/node@18.16.18) why-is-node-running: 2.2.2 transitivePeerDependencies: - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.9 + '@types/chai-subset': 1.3.4 + '@types/node': 18.16.18 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.11.2 + acorn-walk: 8.3.0 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4(supports-color@8.1.1) + local-pkg: 0.4.3 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.4.3 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.7.0 + vite: 4.5.0(@types/node@18.16.18) + vite-node: 0.34.6(@types/node@18.16.18) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss - sass - stylus - sugarss From 7c1ed741b227984ce8d826f3ebe9a21c09bd20b5 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:50:33 -0400 Subject: [PATCH 15/44] update examples --- examples/art-gobblers/abis/ArtGobblers.abi.ts | 947 ++++++++++++++ examples/art-gobblers/abis/ArtGobblers.json | 989 -------------- examples/art-gobblers/ponder.config.ts | 10 +- examples/ethfs/abis/FileStore.abi.ts | 306 +++++ examples/ethfs/abis/FileStore.json | 322 ----- examples/ethfs/abis/FileStoreFrontend.abi.ts | 50 + examples/ethfs/abis/FileStoreFrontend.json | 50 - examples/ethfs/ponder.config.ts | 15 +- examples/factory-llama/abis/LlamaCore.abi.ts | 1138 ++++++++++++++++ examples/factory-llama/abis/LlamaCore.json | 1160 ----------------- .../factory-llama/abis/LlamaPolicy.abi.ts | 899 +++++++++++++ examples/factory-llama/abis/LlamaPolicy.json | 911 ------------- examples/factory-llama/ponder.config.ts | 14 +- .../friendtech/abis/FriendtechSharesV1.abi.ts | 232 ++++ .../friendtech/abis/FriendtechSharesV1.json | 238 ---- examples/friendtech/ponder.config.ts | 12 +- .../token-erc20/abis/AdventureGold.abi.ts | 349 +++++ examples/token-erc20/abis/AdventureGold.json | 357 ----- examples/token-erc20/ponder.config.ts | 12 +- examples/token-erc721/abis/SmolBrain.abi.ts | 597 +++++++++ examples/token-erc721/abis/SmolBrain.json | 633 --------- examples/token-erc721/ponder.config.ts | 12 +- .../token-reth/abis/RocketTokenRETH.abi.ts | 324 +++++ examples/token-reth/abis/RocketTokenRETH.json | 332 ----- examples/token-reth/ponder.config.ts | 12 +- 25 files changed, 4889 insertions(+), 5032 deletions(-) create mode 100644 examples/art-gobblers/abis/ArtGobblers.abi.ts delete mode 100644 examples/art-gobblers/abis/ArtGobblers.json create mode 100644 examples/ethfs/abis/FileStore.abi.ts delete mode 100644 examples/ethfs/abis/FileStore.json create mode 100644 examples/ethfs/abis/FileStoreFrontend.abi.ts delete mode 100644 examples/ethfs/abis/FileStoreFrontend.json create mode 100644 examples/factory-llama/abis/LlamaCore.abi.ts delete mode 100644 examples/factory-llama/abis/LlamaCore.json create mode 100644 examples/factory-llama/abis/LlamaPolicy.abi.ts delete mode 100644 examples/factory-llama/abis/LlamaPolicy.json create mode 100644 examples/friendtech/abis/FriendtechSharesV1.abi.ts delete mode 100644 examples/friendtech/abis/FriendtechSharesV1.json create mode 100644 examples/token-erc20/abis/AdventureGold.abi.ts delete mode 100644 examples/token-erc20/abis/AdventureGold.json create mode 100644 examples/token-erc721/abis/SmolBrain.abi.ts delete mode 100644 examples/token-erc721/abis/SmolBrain.json create mode 100644 examples/token-reth/abis/RocketTokenRETH.abi.ts delete mode 100644 examples/token-reth/abis/RocketTokenRETH.json diff --git a/examples/art-gobblers/abis/ArtGobblers.abi.ts b/examples/art-gobblers/abis/ArtGobblers.abi.ts new file mode 100644 index 000000000..e790b12dd --- /dev/null +++ b/examples/art-gobblers/abis/ArtGobblers.abi.ts @@ -0,0 +1,947 @@ +export const ArtGobblersAbi = [ + { + inputs: [ + { internalType: "bytes32", name: "_merkleRoot", type: "bytes32" }, + { internalType: "uint256", name: "_mintStart", type: "uint256" }, + { internalType: "contract Goo", name: "_goo", type: "address" }, + { internalType: "contract Pages", name: "_pages", type: "address" }, + { internalType: "address", name: "_team", type: "address" }, + { internalType: "address", name: "_community", type: "address" }, + { + internalType: "contract RandProvider", + name: "_randProvider", + type: "address", + }, + { internalType: "string", name: "_baseUri", type: "string" }, + { internalType: "string", name: "_unrevealedUri", type: "string" }, + { + internalType: "bytes32", + name: "_provenanceHash", + type: "bytes32", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AlreadyClaimed", type: "error" }, + { inputs: [], name: "Cannibalism", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "CannotBurnLegendary", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "cost", type: "uint256" }], + name: "InsufficientGobblerAmount", + type: "error", + }, + { inputs: [], name: "InvalidProof", type: "error" }, + { + inputs: [ + { internalType: "uint256", name: "gobblersLeft", type: "uint256" }, + ], + name: "LegendaryAuctionNotStarted", + type: "error", + }, + { inputs: [], name: "MintStartPending", type: "error" }, + { inputs: [], name: "NoRemainingLegendaryGobblers", type: "error" }, + { + inputs: [ + { + internalType: "uint256", + name: "totalRemainingToBeRevealed", + type: "uint256", + }, + ], + name: "NotEnoughRemainingToBeRevealed", + type: "error", + }, + { inputs: [], name: "NotRandProvider", type: "error" }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "OwnerMismatch", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "currentPrice", type: "uint256" }, + ], + name: "PriceExceededMax", + type: "error", + }, + { inputs: [], name: "RequestTooEarly", type: "error" }, + { inputs: [], name: "ReserveImbalance", type: "error" }, + { inputs: [], name: "RevealsPending", type: "error" }, + { inputs: [], name: "SeedPending", type: "error" }, + { + inputs: [{ internalType: "address", name: "caller", type: "address" }], + name: "UnauthorizedCaller", + type: "error", + }, + { inputs: [], name: "ZeroToBeRevealed", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "nft", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "ArtGobbled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + ], + name: "GobblerClaimed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "price", + type: "uint256", + }, + ], + name: "GobblerPurchased", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "numGobblers", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "lastRevealedId", + type: "uint256", + }, + ], + name: "GobblersRevealed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "newGooBalance", + type: "uint256", + }, + ], + name: "GooBalanceUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "gobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256[]", + name: "burnedGobblerIds", + type: "uint256[]", + }, + ], + name: "LegendaryGobblerMinted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: true, + internalType: "contract RandProvider", + name: "newRandProvider", + type: "address", + }, + ], + name: "RandProviderUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "randomness", + type: "uint256", + }, + ], + name: "RandomnessFulfilled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "toBeRevealed", + type: "uint256", + }, + ], + name: "RandomnessRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "user", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "lastMintedGobblerId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "numGobblersEach", + type: "uint256", + }, + ], + name: "ReservedGobblersMinted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "BASE_URI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "FIRST_LEGENDARY_GOBBLER_ID", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_AUCTION_INTERVAL", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_GOBBLER_INITIAL_START_PRICE", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "LEGENDARY_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_MINTABLE", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MINTLIST_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "PROVENANCE_HASH", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "RESERVED_SUPPLY", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "UNREVEALED_URI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "", type: "bytes32" }, + { internalType: "uint256", name: "randomness", type: "uint256" }, + ], + name: "acceptRandomSeed", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gooAmount", type: "uint256" }], + name: "addGoo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "user", type: "address" }, + { internalType: "uint256", name: "gooAmount", type: "uint256" }, + ], + name: "burnGooForPages", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32[]", name: "proof", type: "bytes32[]" }], + name: "claimGobbler", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "community", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "currentNonLegendaryId", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "getCopiesOfArtGobbledByGobbler", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "getGobblerData", + outputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint64", name: "idx", type: "uint64" }, + { internalType: "uint32", name: "emissionMultiple", type: "uint32" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "getGobblerEmissionMultiple", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "int256", name: "sold", type: "int256" }], + name: "getTargetSaleTime", + outputs: [{ internalType: "int256", name: "", type: "int256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "getUserData", + outputs: [ + { internalType: "uint32", name: "gobblersOwned", type: "uint32" }, + { + internalType: "uint32", + name: "emissionMultiple", + type: "uint32", + }, + { internalType: "uint128", name: "lastBalance", type: "uint128" }, + { internalType: "uint64", name: "lastTimestamp", type: "uint64" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "getUserEmissionMultiple", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "int256", name: "timeSinceStart", type: "int256" }, + { internalType: "uint256", name: "sold", type: "uint256" }, + ], + name: "getVRGDAPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "gobblerId", type: "uint256" }, + { internalType: "address", name: "nft", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "bool", name: "isERC1155", type: "bool" }, + ], + name: "gobble", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "gobblerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "gobblerRevealsData", + outputs: [ + { internalType: "uint64", name: "randomSeed", type: "uint64" }, + { + internalType: "uint64", + name: "nextRevealTimestamp", + type: "uint64", + }, + { internalType: "uint64", name: "lastRevealedId", type: "uint64" }, + { internalType: "uint56", name: "toBeRevealed", type: "uint56" }, + { internalType: "bool", name: "waitingForSeed", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "goo", + outputs: [{ internalType: "contract Goo", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "gooBalance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "hasClaimedMintlistGobbler", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "legendaryGobblerAuctionData", + outputs: [ + { internalType: "uint128", name: "startPrice", type: "uint128" }, + { internalType: "uint128", name: "numSold", type: "uint128" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "legendaryGobblerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "merkleRoot", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "maxPrice", type: "uint256" }, + { internalType: "bool", name: "useVirtualBalance", type: "bool" }, + ], + name: "mintFromGoo", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256[]", name: "gobblerIds", type: "uint256[]" }, + ], + name: "mintLegendaryGobbler", + outputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "numGobblersEach", + type: "uint256", + }, + ], + name: "mintReservedGobblers", + outputs: [ + { + internalType: "uint256", + name: "lastMintedGobblerId", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "mintStart", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "numMintedForReserves", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "numMintedFromGoo", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256[]", name: "", type: "uint256[]" }, + { internalType: "uint256[]", name: "", type: "uint256[]" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + name: "onERC1155BatchReceived", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + name: "onERC1155Received", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "owner", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pages", + outputs: [{ internalType: "contract Pages", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "randProvider", + outputs: [ + { internalType: "contract RandProvider", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gooAmount", type: "uint256" }], + name: "removeGoo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "requestRandomSeed", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "numGobblers", type: "uint256" }], + name: "revealGobblers", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "targetPrice", + outputs: [{ internalType: "int256", name: "", type: "int256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "team", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "gobblerId", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract RandProvider", + name: "newRandProvider", + type: "address", + }, + ], + name: "upgradeRandProvider", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/art-gobblers/abis/ArtGobblers.json b/examples/art-gobblers/abis/ArtGobblers.json deleted file mode 100644 index ce916cce2..000000000 --- a/examples/art-gobblers/abis/ArtGobblers.json +++ /dev/null @@ -1,989 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "bytes32", "name": "_merkleRoot", "type": "bytes32" }, - { "internalType": "uint256", "name": "_mintStart", "type": "uint256" }, - { "internalType": "contract Goo", "name": "_goo", "type": "address" }, - { "internalType": "contract Pages", "name": "_pages", "type": "address" }, - { "internalType": "address", "name": "_team", "type": "address" }, - { "internalType": "address", "name": "_community", "type": "address" }, - { - "internalType": "contract RandProvider", - "name": "_randProvider", - "type": "address" - }, - { "internalType": "string", "name": "_baseUri", "type": "string" }, - { "internalType": "string", "name": "_unrevealedUri", "type": "string" }, - { - "internalType": "bytes32", - "name": "_provenanceHash", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "AlreadyClaimed", "type": "error" }, - { "inputs": [], "name": "Cannibalism", "type": "error" }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "CannotBurnLegendary", - "type": "error" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "cost", "type": "uint256" } - ], - "name": "InsufficientGobblerAmount", - "type": "error" - }, - { "inputs": [], "name": "InvalidProof", "type": "error" }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblersLeft", "type": "uint256" } - ], - "name": "LegendaryAuctionNotStarted", - "type": "error" - }, - { "inputs": [], "name": "MintStartPending", "type": "error" }, - { "inputs": [], "name": "NoRemainingLegendaryGobblers", "type": "error" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "totalRemainingToBeRevealed", - "type": "uint256" - } - ], - "name": "NotEnoughRemainingToBeRevealed", - "type": "error" - }, - { "inputs": [], "name": "NotRandProvider", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "OwnerMismatch", - "type": "error" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "currentPrice", "type": "uint256" } - ], - "name": "PriceExceededMax", - "type": "error" - }, - { "inputs": [], "name": "RequestTooEarly", "type": "error" }, - { "inputs": [], "name": "ReserveImbalance", "type": "error" }, - { "inputs": [], "name": "RevealsPending", "type": "error" }, - { "inputs": [], "name": "SeedPending", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "caller", "type": "address" } - ], - "name": "UnauthorizedCaller", - "type": "error" - }, - { "inputs": [], "name": "ZeroToBeRevealed", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "nft", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "ArtGobbled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - } - ], - "name": "GobblerClaimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "price", - "type": "uint256" - } - ], - "name": "GobblerPurchased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "numGobblers", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "lastRevealedId", - "type": "uint256" - } - ], - "name": "GobblersRevealed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newGooBalance", - "type": "uint256" - } - ], - "name": "GooBalanceUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "gobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "burnedGobblerIds", - "type": "uint256[]" - } - ], - "name": "LegendaryGobblerMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract RandProvider", - "name": "newRandProvider", - "type": "address" - } - ], - "name": "RandProviderUpgraded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "randomness", - "type": "uint256" - } - ], - "name": "RandomnessFulfilled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "toBeRevealed", - "type": "uint256" - } - ], - "name": "RandomnessRequested", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "lastMintedGobblerId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "numGobblersEach", - "type": "uint256" - } - ], - "name": "ReservedGobblersMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "BASE_URI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "FIRST_LEGENDARY_GOBBLER_ID", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_AUCTION_INTERVAL", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_GOBBLER_INITIAL_START_PRICE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LEGENDARY_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_MINTABLE", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MINTLIST_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PROVENANCE_HASH", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "RESERVED_SUPPLY", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "UNREVEALED_URI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "", "type": "bytes32" }, - { "internalType": "uint256", "name": "randomness", "type": "uint256" } - ], - "name": "acceptRandomSeed", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "addGoo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" }, - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "burnGooForPages", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" } - ], - "name": "claimGobbler", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "community", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentNonLegendaryId", - "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "getCopiesOfArtGobbledByGobbler", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "getGobblerData", - "outputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint64", "name": "idx", "type": "uint64" }, - { "internalType": "uint32", "name": "emissionMultiple", "type": "uint32" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "getGobblerEmissionMultiple", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "int256", "name": "sold", "type": "int256" }], - "name": "getTargetSaleTime", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "getUserData", - "outputs": [ - { "internalType": "uint32", "name": "gobblersOwned", "type": "uint32" }, - { - "internalType": "uint32", - "name": "emissionMultiple", - "type": "uint32" - }, - { "internalType": "uint128", "name": "lastBalance", "type": "uint128" }, - { "internalType": "uint64", "name": "lastTimestamp", "type": "uint64" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" } - ], - "name": "getUserEmissionMultiple", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "int256", "name": "timeSinceStart", "type": "int256" }, - { "internalType": "uint256", "name": "sold", "type": "uint256" } - ], - "name": "getVRGDAPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" }, - { "internalType": "address", "name": "nft", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "bool", "name": "isERC1155", "type": "bool" } - ], - "name": "gobble", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "gobblerPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "gobblerRevealsData", - "outputs": [ - { "internalType": "uint64", "name": "randomSeed", "type": "uint64" }, - { - "internalType": "uint64", - "name": "nextRevealTimestamp", - "type": "uint64" - }, - { "internalType": "uint64", "name": "lastRevealedId", "type": "uint64" }, - { "internalType": "uint56", "name": "toBeRevealed", "type": "uint56" }, - { "internalType": "bool", "name": "waitingForSeed", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "goo", - "outputs": [ - { "internalType": "contract Goo", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "user", "type": "address" } - ], - "name": "gooBalance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "hasClaimedMintlistGobbler", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "legendaryGobblerAuctionData", - "outputs": [ - { "internalType": "uint128", "name": "startPrice", "type": "uint128" }, - { "internalType": "uint128", "name": "numSold", "type": "uint128" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "legendaryGobblerPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "merkleRoot", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxPrice", "type": "uint256" }, - { "internalType": "bool", "name": "useVirtualBalance", "type": "bool" } - ], - "name": "mintFromGoo", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256[]", "name": "gobblerIds", "type": "uint256[]" } - ], - "name": "mintLegendaryGobbler", - "outputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "numGobblersEach", - "type": "uint256" - } - ], - "name": "mintReservedGobblers", - "outputs": [ - { - "internalType": "uint256", - "name": "lastMintedGobblerId", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "mintStart", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numMintedForReserves", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numMintedFromGoo", - "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155BatchReceived", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "onERC1155Received", - "outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], - "name": "ownerOf", - "outputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pages", - "outputs": [ - { "internalType": "contract Pages", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "randProvider", - "outputs": [ - { "internalType": "contract RandProvider", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gooAmount", "type": "uint256" } - ], - "name": "removeGoo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "requestRandomSeed", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "numGobblers", "type": "uint256" } - ], - "name": "revealGobblers", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "targetPrice", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "team", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "gobblerId", "type": "uint256" } - ], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "id", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract RandProvider", - "name": "newRandProvider", - "type": "address" - } - ], - "name": "upgradeRandProvider", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/art-gobblers/ponder.config.ts b/examples/art-gobblers/ponder.config.ts index 2da8595ce..d5fd61dbf 100644 --- a/examples/art-gobblers/ponder.config.ts +++ b/examples/art-gobblers/ponder.config.ts @@ -1,9 +1,9 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -import ArtGobblersAbi from "./abis/ArtGobblers.json"; +import { ArtGobblersAbi } from "./ArtGobblers.abi"; -export const config: Config = { +export const config = createConfig({ networks: [ { name: "mainnet", @@ -14,10 +14,10 @@ export const config: Config = { contracts: [ { name: "ArtGobblers", - network: "mainnet", + network: [{ name: "mainnet" }], abi: ArtGobblersAbi, address: "0x60bb1e2aa1c9acafb4d34f71585d7e959f387769", startBlock: 15863321, }, ], -}; +}); diff --git a/examples/ethfs/abis/FileStore.abi.ts b/examples/ethfs/abis/FileStore.abi.ts new file mode 100644 index 000000000..8863a59cc --- /dev/null +++ b/examples/ethfs/abis/FileStore.abi.ts @@ -0,0 +1,306 @@ +export const FileStoreAbi = [ + { + inputs: [ + { + internalType: "contract IContentStore", + name: "_contentStore", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "EmptyFile", type: "error" }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "FileNotFound", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "FilenameExists", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "string", + name: "indexedFilename", + type: "string", + }, + { + indexed: true, + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + { + indexed: false, + internalType: "string", + name: "filename", + type: "string", + }, + { + indexed: false, + internalType: "uint256", + name: "size", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "metadata", + type: "bytes", + }, + ], + name: "FileCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "string", + name: "indexedFilename", + type: "string", + }, + { + indexed: true, + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + { + indexed: false, + internalType: "string", + name: "filename", + type: "string", + }, + ], + name: "FileDeleted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferStarted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + inputs: [], + name: "acceptOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "contentStore", + outputs: [ + { + internalType: "contract IContentStore", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "filename", type: "string" }, + { internalType: "bytes32[]", name: "checksums", type: "bytes32[]" }, + ], + name: "createFile", + outputs: [ + { + components: [ + { internalType: "uint256", name: "size", type: "uint256" }, + { + components: [ + { + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + { + internalType: "address", + name: "pointer", + type: "address", + }, + ], + internalType: "struct Content[]", + name: "contents", + type: "tuple[]", + }, + ], + internalType: "struct File", + name: "file", + type: "tuple", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "filename", type: "string" }, + { internalType: "bytes32[]", name: "checksums", type: "bytes32[]" }, + { internalType: "bytes", name: "extraData", type: "bytes" }, + ], + name: "createFile", + outputs: [ + { + components: [ + { internalType: "uint256", name: "size", type: "uint256" }, + { + components: [ + { + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + { + internalType: "address", + name: "pointer", + type: "address", + }, + ], + internalType: "struct Content[]", + name: "contents", + type: "tuple[]", + }, + ], + internalType: "struct File", + name: "file", + type: "tuple", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "deleteFile", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "fileExists", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "", type: "string" }], + name: "files", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "getChecksum", + outputs: [{ internalType: "bytes32", name: "checksum", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "filename", type: "string" }], + name: "getFile", + outputs: [ + { + components: [ + { internalType: "uint256", name: "size", type: "uint256" }, + { + components: [ + { + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + { + internalType: "address", + name: "pointer", + type: "address", + }, + ], + internalType: "struct Content[]", + name: "contents", + type: "tuple[]", + }, + ], + internalType: "struct File", + name: "file", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/ethfs/abis/FileStore.json b/examples/ethfs/abis/FileStore.json deleted file mode 100644 index 9cd326622..000000000 --- a/examples/ethfs/abis/FileStore.json +++ /dev/null @@ -1,322 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IContentStore", - "name": "_contentStore", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "EmptyFile", "type": "error" }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "FileNotFound", - "type": "error" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "FilenameExists", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "string", - "name": "indexedFilename", - "type": "string" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "filename", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "size", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "metadata", - "type": "bytes" - } - ], - "name": "FileCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "string", - "name": "indexedFilename", - "type": "string" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "filename", - "type": "string" - } - ], - "name": "FileDeleted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferStarted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "contentStore", - "outputs": [ - { - "internalType": "contract IContentStore", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" }, - { "internalType": "bytes32[]", "name": "checksums", "type": "bytes32[]" } - ], - "name": "createFile", - "outputs": [ - { - "components": [ - { "internalType": "uint256", "name": "size", "type": "uint256" }, - { - "components": [ - { - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "pointer", - "type": "address" - } - ], - "internalType": "struct Content[]", - "name": "contents", - "type": "tuple[]" - } - ], - "internalType": "struct File", - "name": "file", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" }, - { "internalType": "bytes32[]", "name": "checksums", "type": "bytes32[]" }, - { "internalType": "bytes", "name": "extraData", "type": "bytes" } - ], - "name": "createFile", - "outputs": [ - { - "components": [ - { "internalType": "uint256", "name": "size", "type": "uint256" }, - { - "components": [ - { - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "pointer", - "type": "address" - } - ], - "internalType": "struct Content[]", - "name": "contents", - "type": "tuple[]" - } - ], - "internalType": "struct File", - "name": "file", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "deleteFile", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "fileExists", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "string", "name": "", "type": "string" }], - "name": "files", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "getChecksum", - "outputs": [ - { "internalType": "bytes32", "name": "checksum", "type": "bytes32" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "filename", "type": "string" } - ], - "name": "getFile", - "outputs": [ - { - "components": [ - { "internalType": "uint256", "name": "size", "type": "uint256" }, - { - "components": [ - { - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "pointer", - "type": "address" - } - ], - "internalType": "struct Content[]", - "name": "contents", - "type": "tuple[]" - } - ], - "internalType": "struct File", - "name": "file", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingOwner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/ethfs/abis/FileStoreFrontend.abi.ts b/examples/ethfs/abis/FileStoreFrontend.abi.ts new file mode 100644 index 000000000..9a9f300f6 --- /dev/null +++ b/examples/ethfs/abis/FileStoreFrontend.abi.ts @@ -0,0 +1,50 @@ +export const FileStoreFrontendAbi = [ + { + inputs: [ + { + internalType: "contract IContentStore", + name: "contentStore", + type: "address", + }, + { + internalType: "bytes32", + name: "checksum", + type: "bytes32", + }, + ], + name: "getContent", + outputs: [ + { + internalType: "bytes", + name: "content", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IFileStore", + name: "fileStore", + type: "address", + }, + { + internalType: "string", + name: "filename", + type: "string", + }, + ], + name: "readFile", + outputs: [ + { + internalType: "string", + name: "contents", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/examples/ethfs/abis/FileStoreFrontend.json b/examples/ethfs/abis/FileStoreFrontend.json deleted file mode 100644 index 2da532d6c..000000000 --- a/examples/ethfs/abis/FileStoreFrontend.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract IContentStore", - "name": "contentStore", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "checksum", - "type": "bytes32" - } - ], - "name": "getContent", - "outputs": [ - { - "internalType": "bytes", - "name": "content", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IFileStore", - "name": "fileStore", - "type": "address" - }, - { - "internalType": "string", - "name": "filename", - "type": "string" - } - ], - "name": "readFile", - "outputs": [ - { - "internalType": "string", - "name": "contents", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/examples/ethfs/ponder.config.ts b/examples/ethfs/ponder.config.ts index 9b96c3a79..9a06009d7 100644 --- a/examples/ethfs/ponder.config.ts +++ b/examples/ethfs/ponder.config.ts @@ -1,10 +1,10 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -import FileStoreAbi from "./abis/FileStore.json"; -import FileStoreFrontendAbi from "./abis/FileStoreFrontend.json"; +import { FileStoreAbi } from "./abis/FileStore.abi"; +import { FileStoreFrontendAbi } from "./abis/FileStoreFrontend.abi"; -export const config: Config = { +export const config = createConfig({ networks: [ { name: "mainnet", @@ -15,17 +15,16 @@ export const config: Config = { contracts: [ { name: "FileStore", - network: "mainnet", + network: [{ name: "mainnet" }], abi: FileStoreAbi, address: "0x9746fD0A77829E12F8A9DBe70D7a322412325B91", startBlock: 15963553, }, { name: "FileStoreFrontend", - network: "mainnet", + network: [{ name: "mainnet" }], address: "0xBc66C61BCF49Cc3fe4E321aeCEa307F61EC57C0b", abi: FileStoreFrontendAbi, - isLogEventSource: false, }, ], -}; +}); diff --git a/examples/factory-llama/abis/LlamaCore.abi.ts b/examples/factory-llama/abis/LlamaCore.abi.ts new file mode 100644 index 000000000..4580536fb --- /dev/null +++ b/examples/factory-llama/abis/LlamaCore.abi.ts @@ -0,0 +1,1138 @@ +export const LlamaCoreAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { inputs: [], name: "BootstrapStrategyNotAuthorized", type: "error" }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "CannotCastWithZeroQuantity", + type: "error", + }, + { + inputs: [], + name: "CannotDisapproveAfterMinExecutionTime", + type: "error", + }, + { inputs: [], name: "CannotSetExecutorAsTarget", type: "error" }, + { inputs: [], name: "DuplicateCast", type: "error" }, + { + inputs: [{ internalType: "bytes", name: "reason", type: "bytes" }], + name: "FailedActionExecution", + type: "error", + }, + { inputs: [], name: "IncorrectMsgValue", type: "error" }, + { inputs: [], name: "InfoHashMismatch", type: "error" }, + { + inputs: [ + { internalType: "enum ActionState", name: "current", type: "uint8" }, + ], + name: "InvalidActionState", + type: "error", + }, + { inputs: [], name: "InvalidPolicyholder", type: "error" }, + { inputs: [], name: "InvalidSignature", type: "error" }, + { + inputs: [], + name: "MinExecutionTimeCannotBeInThePast", + type: "error", + }, + { inputs: [], name: "MinExecutionTimeNotReached", type: "error" }, + { inputs: [], name: "NonExistentStrategy", type: "error" }, + { inputs: [], name: "OnlyLlama", type: "error" }, + { + inputs: [], + name: "PolicyholderDoesNotHavePermission", + type: "error", + }, + { inputs: [], name: "RestrictedAddress", type: "error" }, + { inputs: [], name: "UnauthorizedAccountLogic", type: "error" }, + { inputs: [], name: "UnauthorizedStrategy", type: "error" }, + { inputs: [], name: "UnauthorizedStrategyLogic", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "n", type: "uint256" }], + name: "UnsafeCast", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "contract ILlamaAccount", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "contract ILlamaAccount", + name: "accountLogic", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "initializationData", + type: "bytes", + }, + ], + name: "AccountCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ILlamaAccount", + name: "accountLogic", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "authorized", + type: "bool", + }, + ], + name: "AccountLogicAuthorizationSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "caller", + type: "address", + }, + ], + name: "ActionCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "creator", + type: "address", + }, + { + indexed: false, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "target", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + indexed: false, + internalType: "string", + name: "description", + type: "string", + }, + ], + name: "ActionCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "caller", + type: "address", + }, + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "creator", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "result", + type: "bytes", + }, + ], + name: "ActionExecuted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "target", + type: "address", + }, + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + indexed: false, + internalType: "contract ILlamaActionGuard", + name: "actionGuard", + type: "address", + }, + ], + name: "ActionGuardSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "caller", + type: "address", + }, + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "creator", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "minExecutionTime", + type: "uint256", + }, + ], + name: "ActionQueued", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "policyholder", + type: "address", + }, + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: false, + internalType: "uint256", + name: "quantity", + type: "uint256", + }, + { + indexed: false, + internalType: "string", + name: "reason", + type: "string", + }, + ], + name: "ApprovalCast", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "policyholder", + type: "address", + }, + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: false, + internalType: "uint256", + name: "quantity", + type: "uint256", + }, + { + indexed: false, + internalType: "string", + name: "reason", + type: "string", + }, + ], + name: "DisapprovalCast", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "script", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "authorized", + type: "bool", + }, + ], + name: "ScriptAuthorizationSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "authorized", + type: "bool", + }, + ], + name: "StrategyAuthorizationSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategyLogic", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "initializationData", + type: "bytes", + }, + ], + name: "StrategyCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract ILlamaStrategy", + name: "strategyLogic", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "authorized", + type: "bool", + }, + ], + name: "StrategyLogicAuthorizationSet", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + ], + name: "actionGuard", + outputs: [ + { + internalType: "contract ILlamaActionGuard", + name: "guard", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "actionsCount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "actionId", type: "uint256" }, + { internalType: "address", name: "policyholder", type: "address" }, + ], + name: "approvals", + outputs: [{ internalType: "bool", name: "hasApproved", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaAccount", + name: "accountLogic", + type: "address", + }, + ], + name: "authorizedAccountLogics", + outputs: [{ internalType: "bool", name: "isAuthorized", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "script", type: "address" }], + name: "authorizedScripts", + outputs: [{ internalType: "bool", name: "isAuthorized", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaStrategy", + name: "strategyLogic", + type: "address", + }, + ], + name: "authorizedStrategyLogics", + outputs: [{ internalType: "bool", name: "isAuthorized", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + ], + name: "cancelAction", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "cancelActionBySig", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + { internalType: "string", name: "reason", type: "string" }, + ], + name: "castApproval", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + { internalType: "string", name: "reason", type: "string" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "castApprovalBySig", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + { internalType: "string", name: "reason", type: "string" }, + ], + name: "castDisapproval", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + { internalType: "string", name: "reason", type: "string" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "castDisapprovalBySig", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaAccount", + name: "llamaAccountLogic", + type: "address", + }, + { internalType: "bytes[]", name: "accountConfigs", type: "bytes[]" }, + ], + name: "createAccounts", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + { internalType: "string", name: "description", type: "string" }, + ], + name: "createAction", + outputs: [{ internalType: "uint256", name: "actionId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + { internalType: "string", name: "description", type: "string" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "createActionBySig", + outputs: [{ internalType: "uint256", name: "actionId", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaStrategy", + name: "llamaStrategyLogic", + type: "address", + }, + { + internalType: "bytes[]", + name: "strategyConfigs", + type: "bytes[]", + }, + ], + name: "createStrategies", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "actionId", type: "uint256" }, + { internalType: "address", name: "policyholder", type: "address" }, + ], + name: "disapprovals", + outputs: [{ internalType: "bool", name: "hasDisapproved", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + ], + name: "executeAction", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "executor", + outputs: [ + { + internalType: "contract LlamaExecutor", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "actionId", type: "uint256" }], + name: "getAction", + outputs: [ + { + components: [ + { internalType: "bytes32", name: "infoHash", type: "bytes32" }, + { internalType: "bool", name: "executed", type: "bool" }, + { internalType: "bool", name: "canceled", type: "bool" }, + { internalType: "bool", name: "isScript", type: "bool" }, + { + internalType: "contract ILlamaActionGuard", + name: "guard", + type: "address", + }, + { + internalType: "uint64", + name: "creationTime", + type: "uint64", + }, + { + internalType: "uint64", + name: "minExecutionTime", + type: "uint64", + }, + { + internalType: "uint96", + name: "totalApprovals", + type: "uint96", + }, + { + internalType: "uint96", + name: "totalDisapprovals", + type: "uint96", + }, + ], + internalType: "struct Action", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + ], + name: "getActionState", + outputs: [{ internalType: "enum ActionState", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "selector", type: "bytes4" }], + name: "incrementNonce", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "string", name: "name", type: "string" }, + { + internalType: "contract ILlamaStrategy", + name: "strategyLogic", + type: "address", + }, + { + internalType: "contract ILlamaAccount", + name: "accountLogic", + type: "address", + }, + { + internalType: "bytes[]", + name: "initialStrategies", + type: "bytes[]", + }, + { + internalType: "bytes[]", + name: "initialAccounts", + type: "bytes[]", + }, + { + components: [ + { + internalType: "RoleDescription[]", + name: "roleDescriptions", + type: "bytes32[]", + }, + { + components: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + internalType: "address", + name: "policyholder", + type: "address", + }, + { + internalType: "uint96", + name: "quantity", + type: "uint96", + }, + { + internalType: "uint64", + name: "expiration", + type: "uint64", + }, + ], + internalType: "struct RoleHolderData[]", + name: "roleHolders", + type: "tuple[]", + }, + { + components: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + internalType: "struct PermissionData", + name: "permissionData", + type: "tuple", + }, + { + internalType: "bool", + name: "hasPermission", + type: "bool", + }, + ], + internalType: "struct RolePermissionData[]", + name: "rolePermissions", + type: "tuple[]", + }, + { internalType: "string", name: "color", type: "string" }, + { internalType: "string", name: "logo", type: "string" }, + ], + internalType: "struct LlamaPolicyConfig", + name: "policyConfig", + type: "tuple", + }, + ], + internalType: "struct LlamaInstanceConfig", + name: "config", + type: "tuple", + }, + { + internalType: "contract LlamaPolicy", + name: "policyLogic", + type: "address", + }, + { + internalType: "contract ILlamaPolicyMetadata", + name: "policyMetadataLogic", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + ], + name: "nonces", + outputs: [ + { internalType: "uint256", name: "currentNonce", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "policy", + outputs: [ + { internalType: "contract LlamaPolicy", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "creator", type: "address" }, + { internalType: "uint8", name: "creatorRole", type: "uint8" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "address", name: "target", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + internalType: "struct ActionInfo", + name: "actionInfo", + type: "tuple", + }, + ], + name: "queueAction", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaAccount", + name: "accountLogic", + type: "address", + }, + { internalType: "bool", name: "authorized", type: "bool" }, + ], + name: "setAccountLogicAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { + internalType: "contract ILlamaActionGuard", + name: "guard", + type: "address", + }, + ], + name: "setGuard", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "script", type: "address" }, + { internalType: "bool", name: "authorized", type: "bool" }, + ], + name: "setScriptAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + { internalType: "bool", name: "authorized", type: "bool" }, + ], + name: "setStrategyAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaStrategy", + name: "strategyLogic", + type: "address", + }, + { internalType: "bool", name: "authorized", type: "bool" }, + ], + name: "setStrategyLogicAuthorization", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + name: "strategies", + outputs: [ + { internalType: "bool", name: "deployed", type: "bool" }, + { internalType: "bool", name: "authorized", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/examples/factory-llama/abis/LlamaCore.json b/examples/factory-llama/abis/LlamaCore.json deleted file mode 100644 index b786a17c9..000000000 --- a/examples/factory-llama/abis/LlamaCore.json +++ /dev/null @@ -1,1160 +0,0 @@ -[ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "BootstrapStrategyNotAuthorized", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "CannotCastWithZeroQuantity", - "type": "error" - }, - { - "inputs": [], - "name": "CannotDisapproveAfterMinExecutionTime", - "type": "error" - }, - { "inputs": [], "name": "CannotSetExecutorAsTarget", "type": "error" }, - { "inputs": [], "name": "DuplicateCast", "type": "error" }, - { - "inputs": [{ "internalType": "bytes", "name": "reason", "type": "bytes" }], - "name": "FailedActionExecution", - "type": "error" - }, - { "inputs": [], "name": "IncorrectMsgValue", "type": "error" }, - { "inputs": [], "name": "InfoHashMismatch", "type": "error" }, - { - "inputs": [ - { "internalType": "enum ActionState", "name": "current", "type": "uint8" } - ], - "name": "InvalidActionState", - "type": "error" - }, - { "inputs": [], "name": "InvalidPolicyholder", "type": "error" }, - { "inputs": [], "name": "InvalidSignature", "type": "error" }, - { - "inputs": [], - "name": "MinExecutionTimeCannotBeInThePast", - "type": "error" - }, - { "inputs": [], "name": "MinExecutionTimeNotReached", "type": "error" }, - { "inputs": [], "name": "NonExistentStrategy", "type": "error" }, - { "inputs": [], "name": "OnlyLlama", "type": "error" }, - { - "inputs": [], - "name": "PolicyholderDoesNotHavePermission", - "type": "error" - }, - { "inputs": [], "name": "RestrictedAddress", "type": "error" }, - { "inputs": [], "name": "UnauthorizedAccountLogic", "type": "error" }, - { "inputs": [], "name": "UnauthorizedStrategy", "type": "error" }, - { "inputs": [], "name": "UnauthorizedStrategyLogic", "type": "error" }, - { - "inputs": [{ "internalType": "uint256", "name": "n", "type": "uint256" }], - "name": "UnsafeCast", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "contract ILlamaAccount", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract ILlamaAccount", - "name": "accountLogic", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "initializationData", - "type": "bytes" - } - ], - "name": "AccountCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract ILlamaAccount", - "name": "accountLogic", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "authorized", - "type": "bool" - } - ], - "name": "AccountLogicAuthorizationSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - } - ], - "name": "ActionCanceled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "indexed": false, - "internalType": "string", - "name": "description", - "type": "string" - } - ], - "name": "ActionCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "result", - "type": "bytes" - } - ], - "name": "ActionExecuted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes4", - "name": "selector", - "type": "bytes4" - }, - { - "indexed": false, - "internalType": "contract ILlamaActionGuard", - "name": "actionGuard", - "type": "address" - } - ], - "name": "ActionGuardSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "minExecutionTime", - "type": "uint256" - } - ], - "name": "ActionQueued", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "quantity", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "reason", - "type": "string" - } - ], - "name": "ApprovalCast", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "quantity", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "reason", - "type": "string" - } - ], - "name": "DisapprovalCast", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "script", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "authorized", - "type": "bool" - } - ], - "name": "ScriptAuthorizationSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "authorized", - "type": "bool" - } - ], - "name": "StrategyAuthorizationSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategyLogic", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "initializationData", - "type": "bytes" - } - ], - "name": "StrategyCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "contract ILlamaStrategy", - "name": "strategyLogic", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "authorized", - "type": "bool" - } - ], - "name": "StrategyLogicAuthorizationSet", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" } - ], - "name": "actionGuard", - "outputs": [ - { - "internalType": "contract ILlamaActionGuard", - "name": "guard", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "actionsCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "actionId", "type": "uint256" }, - { "internalType": "address", "name": "policyholder", "type": "address" } - ], - "name": "approvals", - "outputs": [ - { "internalType": "bool", "name": "hasApproved", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaAccount", - "name": "accountLogic", - "type": "address" - } - ], - "name": "authorizedAccountLogics", - "outputs": [ - { "internalType": "bool", "name": "isAuthorized", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "script", "type": "address" } - ], - "name": "authorizedScripts", - "outputs": [ - { "internalType": "bool", "name": "isAuthorized", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaStrategy", - "name": "strategyLogic", - "type": "address" - } - ], - "name": "authorizedStrategyLogics", - "outputs": [ - { "internalType": "bool", "name": "isAuthorized", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - } - ], - "name": "cancelAction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" } - ], - "name": "cancelActionBySig", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - }, - { "internalType": "string", "name": "reason", "type": "string" } - ], - "name": "castApproval", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - }, - { "internalType": "string", "name": "reason", "type": "string" }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" } - ], - "name": "castApprovalBySig", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - }, - { "internalType": "string", "name": "reason", "type": "string" } - ], - "name": "castDisapproval", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - }, - { "internalType": "string", "name": "reason", "type": "string" }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" } - ], - "name": "castDisapprovalBySig", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaAccount", - "name": "llamaAccountLogic", - "type": "address" - }, - { "internalType": "bytes[]", "name": "accountConfigs", "type": "bytes[]" } - ], - "name": "createAccounts", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" }, - { "internalType": "string", "name": "description", "type": "string" } - ], - "name": "createAction", - "outputs": [ - { "internalType": "uint256", "name": "actionId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" }, - { "internalType": "string", "name": "description", "type": "string" }, - { "internalType": "uint8", "name": "v", "type": "uint8" }, - { "internalType": "bytes32", "name": "r", "type": "bytes32" }, - { "internalType": "bytes32", "name": "s", "type": "bytes32" } - ], - "name": "createActionBySig", - "outputs": [ - { "internalType": "uint256", "name": "actionId", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaStrategy", - "name": "llamaStrategyLogic", - "type": "address" - }, - { - "internalType": "bytes[]", - "name": "strategyConfigs", - "type": "bytes[]" - } - ], - "name": "createStrategies", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "actionId", "type": "uint256" }, - { "internalType": "address", "name": "policyholder", "type": "address" } - ], - "name": "disapprovals", - "outputs": [ - { "internalType": "bool", "name": "hasDisapproved", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - } - ], - "name": "executeAction", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "executor", - "outputs": [ - { - "internalType": "contract LlamaExecutor", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "actionId", "type": "uint256" } - ], - "name": "getAction", - "outputs": [ - { - "components": [ - { "internalType": "bytes32", "name": "infoHash", "type": "bytes32" }, - { "internalType": "bool", "name": "executed", "type": "bool" }, - { "internalType": "bool", "name": "canceled", "type": "bool" }, - { "internalType": "bool", "name": "isScript", "type": "bool" }, - { - "internalType": "contract ILlamaActionGuard", - "name": "guard", - "type": "address" - }, - { - "internalType": "uint64", - "name": "creationTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "minExecutionTime", - "type": "uint64" - }, - { - "internalType": "uint96", - "name": "totalApprovals", - "type": "uint96" - }, - { - "internalType": "uint96", - "name": "totalDisapprovals", - "type": "uint96" - } - ], - "internalType": "struct Action", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - } - ], - "name": "getActionState", - "outputs": [ - { "internalType": "enum ActionState", "name": "", "type": "uint8" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "selector", "type": "bytes4" } - ], - "name": "incrementNonce", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "string", "name": "name", "type": "string" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategyLogic", - "type": "address" - }, - { - "internalType": "contract ILlamaAccount", - "name": "accountLogic", - "type": "address" - }, - { - "internalType": "bytes[]", - "name": "initialStrategies", - "type": "bytes[]" - }, - { - "internalType": "bytes[]", - "name": "initialAccounts", - "type": "bytes[]" - }, - { - "components": [ - { - "internalType": "RoleDescription[]", - "name": "roleDescriptions", - "type": "bytes32[]" - }, - { - "components": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "internalType": "uint96", - "name": "quantity", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "expiration", - "type": "uint64" - } - ], - "internalType": "struct RoleHolderData[]", - "name": "roleHolders", - "type": "tuple[]" - }, - { - "components": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "selector", - "type": "bytes4" - }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "internalType": "struct PermissionData", - "name": "permissionData", - "type": "tuple" - }, - { - "internalType": "bool", - "name": "hasPermission", - "type": "bool" - } - ], - "internalType": "struct RolePermissionData[]", - "name": "rolePermissions", - "type": "tuple[]" - }, - { "internalType": "string", "name": "color", "type": "string" }, - { "internalType": "string", "name": "logo", "type": "string" } - ], - "internalType": "struct LlamaPolicyConfig", - "name": "policyConfig", - "type": "tuple" - } - ], - "internalType": "struct LlamaInstanceConfig", - "name": "config", - "type": "tuple" - }, - { - "internalType": "contract LlamaPolicy", - "name": "policyLogic", - "type": "address" - }, - { - "internalType": "contract ILlamaPolicyMetadata", - "name": "policyMetadataLogic", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" } - ], - "name": "nonces", - "outputs": [ - { "internalType": "uint256", "name": "currentNonce", "type": "uint256" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "policy", - "outputs": [ - { "internalType": "contract LlamaPolicy", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { "internalType": "uint256", "name": "id", "type": "uint256" }, - { "internalType": "address", "name": "creator", "type": "address" }, - { "internalType": "uint8", "name": "creatorRole", "type": "uint8" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "uint256", "name": "value", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "internalType": "struct ActionInfo", - "name": "actionInfo", - "type": "tuple" - } - ], - "name": "queueAction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaAccount", - "name": "accountLogic", - "type": "address" - }, - { "internalType": "bool", "name": "authorized", "type": "bool" } - ], - "name": "setAccountLogicAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, - { - "internalType": "contract ILlamaActionGuard", - "name": "guard", - "type": "address" - } - ], - "name": "setGuard", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "script", "type": "address" }, - { "internalType": "bool", "name": "authorized", "type": "bool" } - ], - "name": "setScriptAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - }, - { "internalType": "bool", "name": "authorized", "type": "bool" } - ], - "name": "setStrategyAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaStrategy", - "name": "strategyLogic", - "type": "address" - }, - { "internalType": "bool", "name": "authorized", "type": "bool" } - ], - "name": "setStrategyLogicAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "name": "strategies", - "outputs": [ - { "internalType": "bool", "name": "deployed", "type": "bool" }, - { "internalType": "bool", "name": "authorized", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/examples/factory-llama/abis/LlamaPolicy.abi.ts b/examples/factory-llama/abis/LlamaPolicy.abi.ts new file mode 100644 index 000000000..e00afdcb8 --- /dev/null +++ b/examples/factory-llama/abis/LlamaPolicy.abi.ts @@ -0,0 +1,899 @@ +export const LlamaPolicyAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { inputs: [], name: "ActionCreationAtSameTimestamp", type: "error" }, + { + inputs: [{ internalType: "address", name: "userAddress", type: "address" }], + name: "AddressDoesNotHoldPolicy", + type: "error", + }, + { inputs: [], name: "AllHoldersRole", type: "error" }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { inputs: [], name: "InvalidIndices", type: "error" }, + { inputs: [], name: "InvalidRoleHolderInput", type: "error" }, + { inputs: [], name: "NonTransferableToken", type: "error" }, + { inputs: [], name: "OnlyLlama", type: "error" }, + { inputs: [], name: "OnlyLlamaFactory", type: "error" }, + { + inputs: [{ internalType: "uint8", name: "role", type: "uint8" }], + name: "RoleNotInitialized", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "n", type: "uint256" }], + name: "UnsafeCast", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "caller", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "policyholder", + type: "address", + }, + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + ], + name: "ExpiredRoleRevoked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "contract ILlamaPolicyMetadata", + name: "policyMetadata", + type: "address", + }, + { + indexed: true, + internalType: "contract ILlamaPolicyMetadata", + name: "policyMetadataLogic", + type: "address", + }, + { + indexed: false, + internalType: "bytes", + name: "initializationData", + type: "bytes", + }, + ], + name: "PolicyMetadataSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "policyholder", + type: "address", + }, + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: false, + internalType: "uint64", + name: "expiration", + type: "uint64", + }, + { + indexed: false, + internalType: "uint96", + name: "quantity", + type: "uint96", + }, + ], + name: "RoleAssigned", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: false, + internalType: "RoleDescription", + name: "description", + type: "bytes32", + }, + ], + name: "RoleInitialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint8", + name: "role", + type: "uint8", + }, + { + indexed: true, + internalType: "bytes32", + name: "permissionId", + type: "bytes32", + }, + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + indexed: false, + internalType: "struct PermissionData", + name: "permissionData", + type: "tuple", + }, + { + indexed: false, + internalType: "bool", + name: "hasPermission", + type: "bool", + }, + ], + name: "RolePermissionAssigned", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "bytes32", name: "permissionId", type: "bytes32" }, + ], + name: "canCreateAction", + outputs: [{ internalType: "bool", name: "hasPermission", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "contractURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "timestamp", type: "uint256" }, + ], + name: "getPastQuantity", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "timestamp", type: "uint256" }, + ], + name: "getPastRoleSupplyAsNumberOfHolders", + outputs: [ + { internalType: "uint96", name: "numberOfHolders", type: "uint96" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "timestamp", type: "uint256" }, + ], + name: "getPastRoleSupplyAsQuantitySum", + outputs: [ + { internalType: "uint96", name: "totalQuantity", type: "uint96" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "getQuantity", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint8", name: "role", type: "uint8" }], + name: "getRoleSupplyAsNumberOfHolders", + outputs: [ + { internalType: "uint96", name: "numberOfHolders", type: "uint96" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint8", name: "role", type: "uint8" }], + name: "getRoleSupplyAsQuantitySum", + outputs: [ + { internalType: "uint96", name: "totalQuantity", type: "uint96" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "bytes32", name: "permissionId", type: "bytes32" }, + ], + name: "hasPermissionId", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "hasRole", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "timestamp", type: "uint256" }, + ], + name: "hasRole", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "_name", type: "string" }, + { + components: [ + { + internalType: "RoleDescription[]", + name: "roleDescriptions", + type: "bytes32[]", + }, + { + components: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + internalType: "address", + name: "policyholder", + type: "address", + }, + { + internalType: "uint96", + name: "quantity", + type: "uint96", + }, + { + internalType: "uint64", + name: "expiration", + type: "uint64", + }, + ], + internalType: "struct RoleHolderData[]", + name: "roleHolders", + type: "tuple[]", + }, + { + components: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + internalType: "struct PermissionData", + name: "permissionData", + type: "tuple", + }, + { + internalType: "bool", + name: "hasPermission", + type: "bool", + }, + ], + internalType: "struct RolePermissionData[]", + name: "rolePermissions", + type: "tuple[]", + }, + { internalType: "string", name: "color", type: "string" }, + { internalType: "string", name: "logo", type: "string" }, + ], + internalType: "struct LlamaPolicyConfig", + name: "config", + type: "tuple", + }, + { + internalType: "contract ILlamaPolicyMetadata", + name: "policyMetadataLogic", + type: "address", + }, + { internalType: "address", name: "executor", type: "address" }, + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + internalType: "struct PermissionData", + name: "bootstrapPermissionData", + type: "tuple", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "RoleDescription", + name: "description", + type: "bytes32", + }, + ], + name: "initializeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "isRoleExpired", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "llamaExecutor", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "llamaPolicyMetadata", + outputs: [ + { + internalType: "contract ILlamaPolicyMetadata", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "numRoles", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "owner", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "address", name: "policyholder", type: "address" }, + ], + name: "revokeExpiredRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + ], + name: "revokePolicy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "start", type: "uint256" }, + { internalType: "uint256", name: "end", type: "uint256" }, + ], + name: "roleBalanceCheckpoints", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "uint64", + name: "timestamp", + type: "uint64", + }, + { + internalType: "uint64", + name: "expiration", + type: "uint64", + }, + { internalType: "uint96", name: "quantity", type: "uint96" }, + ], + internalType: "struct PolicyholderCheckpoints.Checkpoint[]", + name: "_checkpoints", + type: "tuple[]", + }, + ], + internalType: "struct PolicyholderCheckpoints.History", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "roleBalanceCheckpoints", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "uint64", + name: "timestamp", + type: "uint64", + }, + { + internalType: "uint64", + name: "expiration", + type: "uint64", + }, + { internalType: "uint96", name: "quantity", type: "uint96" }, + ], + internalType: "struct PolicyholderCheckpoints.Checkpoint[]", + name: "_checkpoints", + type: "tuple[]", + }, + ], + internalType: "struct PolicyholderCheckpoints.History", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "roleBalanceCheckpointsLength", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint8", name: "role", type: "uint8" }, + ], + name: "roleExpiration", + outputs: [{ internalType: "uint64", name: "", type: "uint64" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "uint256", name: "start", type: "uint256" }, + { internalType: "uint256", name: "end", type: "uint256" }, + ], + name: "roleSupplyCheckpoints", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "uint64", + name: "timestamp", + type: "uint64", + }, + { + internalType: "uint96", + name: "numberOfHolders", + type: "uint96", + }, + { + internalType: "uint96", + name: "totalQuantity", + type: "uint96", + }, + ], + internalType: "struct SupplyCheckpoints.Checkpoint[]", + name: "_checkpoints", + type: "tuple[]", + }, + ], + internalType: "struct SupplyCheckpoints.History", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint8", name: "role", type: "uint8" }], + name: "roleSupplyCheckpoints", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "uint64", + name: "timestamp", + type: "uint64", + }, + { + internalType: "uint96", + name: "numberOfHolders", + type: "uint96", + }, + { + internalType: "uint96", + name: "totalQuantity", + type: "uint96", + }, + ], + internalType: "struct SupplyCheckpoints.Checkpoint[]", + name: "_checkpoints", + type: "tuple[]", + }, + ], + internalType: "struct SupplyCheckpoints.History", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint8", name: "role", type: "uint8" }], + name: "roleSupplyCheckpointsLength", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "contract ILlamaPolicyMetadata", + name: "llamaPolicyMetadataLogic", + type: "address", + }, + { internalType: "bytes", name: "config", type: "bytes" }, + ], + name: "setAndInitializePolicyMetadata", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "bool", name: "", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { internalType: "address", name: "policyholder", type: "address" }, + { internalType: "uint96", name: "quantity", type: "uint96" }, + { internalType: "uint64", name: "expiration", type: "uint64" }, + ], + name: "setRoleHolder", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + components: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { + internalType: "contract ILlamaStrategy", + name: "strategy", + type: "address", + }, + ], + internalType: "struct PermissionData", + name: "permissionData", + type: "tuple", + }, + { internalType: "bool", name: "hasPermission", type: "bool" }, + ], + name: "setRolePermission", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "uint8", name: "role", type: "uint8" }, + { + internalType: "RoleDescription", + name: "description", + type: "bytes32", + }, + ], + name: "updateRoleDescription", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/factory-llama/abis/LlamaPolicy.json b/examples/factory-llama/abis/LlamaPolicy.json deleted file mode 100644 index 15dcee5e5..000000000 --- a/examples/factory-llama/abis/LlamaPolicy.json +++ /dev/null @@ -1,911 +0,0 @@ -[ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "ActionCreationAtSameTimestamp", "type": "error" }, - { - "inputs": [ - { "internalType": "address", "name": "userAddress", "type": "address" } - ], - "name": "AddressDoesNotHoldPolicy", - "type": "error" - }, - { "inputs": [], "name": "AllHoldersRole", "type": "error" }, - { "inputs": [], "name": "AlreadyInitialized", "type": "error" }, - { "inputs": [], "name": "InvalidIndices", "type": "error" }, - { "inputs": [], "name": "InvalidRoleHolderInput", "type": "error" }, - { "inputs": [], "name": "NonTransferableToken", "type": "error" }, - { "inputs": [], "name": "OnlyLlama", "type": "error" }, - { "inputs": [], "name": "OnlyLlamaFactory", "type": "error" }, - { - "inputs": [{ "internalType": "uint8", "name": "role", "type": "uint8" }], - "name": "RoleNotInitialized", - "type": "error" - }, - { - "inputs": [{ "internalType": "uint256", "name": "n", "type": "uint256" }], - "name": "UnsafeCast", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - } - ], - "name": "ExpiredRoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "contract ILlamaPolicyMetadata", - "name": "policyMetadata", - "type": "address" - }, - { - "indexed": true, - "internalType": "contract ILlamaPolicyMetadata", - "name": "policyMetadataLogic", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "initializationData", - "type": "bytes" - } - ], - "name": "PolicyMetadataSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "expiration", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "uint96", - "name": "quantity", - "type": "uint96" - } - ], - "name": "RoleAssigned", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "RoleDescription", - "name": "description", - "type": "bytes32" - } - ], - "name": "RoleInitialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint8", - "name": "role", - "type": "uint8" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "permissionId", - "type": "bytes32" - }, - { - "components": [ - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "indexed": false, - "internalType": "struct PermissionData", - "name": "permissionData", - "type": "tuple" - }, - { - "indexed": false, - "internalType": "bool", - "name": "hasPermission", - "type": "bool" - } - ], - "name": "RolePermissionAssigned", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "bytes32", "name": "permissionId", "type": "bytes32" } - ], - "name": "canCreateAction", - "outputs": [ - { "internalType": "bool", "name": "hasPermission", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "contractURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" } - ], - "name": "getPastQuantity", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" } - ], - "name": "getPastRoleSupplyAsNumberOfHolders", - "outputs": [ - { "internalType": "uint96", "name": "numberOfHolders", "type": "uint96" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" } - ], - "name": "getPastRoleSupplyAsQuantitySum", - "outputs": [ - { "internalType": "uint96", "name": "totalQuantity", "type": "uint96" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "getQuantity", - "outputs": [{ "internalType": "uint96", "name": "", "type": "uint96" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint8", "name": "role", "type": "uint8" }], - "name": "getRoleSupplyAsNumberOfHolders", - "outputs": [ - { "internalType": "uint96", "name": "numberOfHolders", "type": "uint96" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint8", "name": "role", "type": "uint8" }], - "name": "getRoleSupplyAsQuantitySum", - "outputs": [ - { "internalType": "uint96", "name": "totalQuantity", "type": "uint96" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "bytes32", "name": "permissionId", "type": "bytes32" } - ], - "name": "hasPermissionId", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "hasRole", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" } - ], - "name": "hasRole", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "_name", "type": "string" }, - { - "components": [ - { - "internalType": "RoleDescription[]", - "name": "roleDescriptions", - "type": "bytes32[]" - }, - { - "components": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "internalType": "address", - "name": "policyholder", - "type": "address" - }, - { - "internalType": "uint96", - "name": "quantity", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "expiration", - "type": "uint64" - } - ], - "internalType": "struct RoleHolderData[]", - "name": "roleHolders", - "type": "tuple[]" - }, - { - "components": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes4", - "name": "selector", - "type": "bytes4" - }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "internalType": "struct PermissionData", - "name": "permissionData", - "type": "tuple" - }, - { - "internalType": "bool", - "name": "hasPermission", - "type": "bool" - } - ], - "internalType": "struct RolePermissionData[]", - "name": "rolePermissions", - "type": "tuple[]" - }, - { "internalType": "string", "name": "color", "type": "string" }, - { "internalType": "string", "name": "logo", "type": "string" } - ], - "internalType": "struct LlamaPolicyConfig", - "name": "config", - "type": "tuple" - }, - { - "internalType": "contract ILlamaPolicyMetadata", - "name": "policyMetadataLogic", - "type": "address" - }, - { "internalType": "address", "name": "executor", "type": "address" }, - { - "components": [ - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "internalType": "struct PermissionData", - "name": "bootstrapPermissionData", - "type": "tuple" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "RoleDescription", - "name": "description", - "type": "bytes32" - } - ], - "name": "initializeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "isRoleExpired", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "llamaExecutor", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "llamaPolicyMetadata", - "outputs": [ - { - "internalType": "contract ILlamaPolicyMetadata", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "numRoles", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], - "name": "ownerOf", - "outputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "address", "name": "policyholder", "type": "address" } - ], - "name": "revokeExpiredRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" } - ], - "name": "revokePolicy", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "start", "type": "uint256" }, - { "internalType": "uint256", "name": "end", "type": "uint256" } - ], - "name": "roleBalanceCheckpoints", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "expiration", - "type": "uint64" - }, - { "internalType": "uint96", "name": "quantity", "type": "uint96" } - ], - "internalType": "struct PolicyholderCheckpoints.Checkpoint[]", - "name": "_checkpoints", - "type": "tuple[]" - } - ], - "internalType": "struct PolicyholderCheckpoints.History", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "roleBalanceCheckpoints", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "expiration", - "type": "uint64" - }, - { "internalType": "uint96", "name": "quantity", "type": "uint96" } - ], - "internalType": "struct PolicyholderCheckpoints.Checkpoint[]", - "name": "_checkpoints", - "type": "tuple[]" - } - ], - "internalType": "struct PolicyholderCheckpoints.History", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "roleBalanceCheckpointsLength", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint8", "name": "role", "type": "uint8" } - ], - "name": "roleExpiration", - "outputs": [{ "internalType": "uint64", "name": "", "type": "uint64" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "uint256", "name": "start", "type": "uint256" }, - { "internalType": "uint256", "name": "end", "type": "uint256" } - ], - "name": "roleSupplyCheckpoints", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint96", - "name": "numberOfHolders", - "type": "uint96" - }, - { - "internalType": "uint96", - "name": "totalQuantity", - "type": "uint96" - } - ], - "internalType": "struct SupplyCheckpoints.Checkpoint[]", - "name": "_checkpoints", - "type": "tuple[]" - } - ], - "internalType": "struct SupplyCheckpoints.History", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint8", "name": "role", "type": "uint8" }], - "name": "roleSupplyCheckpoints", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint96", - "name": "numberOfHolders", - "type": "uint96" - }, - { - "internalType": "uint96", - "name": "totalQuantity", - "type": "uint96" - } - ], - "internalType": "struct SupplyCheckpoints.Checkpoint[]", - "name": "_checkpoints", - "type": "tuple[]" - } - ], - "internalType": "struct SupplyCheckpoints.History", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint8", "name": "role", "type": "uint8" }], - "name": "roleSupplyCheckpointsLength", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract ILlamaPolicyMetadata", - "name": "llamaPolicyMetadataLogic", - "type": "address" - }, - { "internalType": "bytes", "name": "config", "type": "bytes" } - ], - "name": "setAndInitializePolicyMetadata", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "bool", "name": "", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { "internalType": "address", "name": "policyholder", "type": "address" }, - { "internalType": "uint96", "name": "quantity", "type": "uint96" }, - { "internalType": "uint64", "name": "expiration", "type": "uint64" } - ], - "name": "setRoleHolder", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "components": [ - { "internalType": "address", "name": "target", "type": "address" }, - { "internalType": "bytes4", "name": "selector", "type": "bytes4" }, - { - "internalType": "contract ILlamaStrategy", - "name": "strategy", - "type": "address" - } - ], - "internalType": "struct PermissionData", - "name": "permissionData", - "type": "tuple" - }, - { "internalType": "bool", "name": "hasPermission", "type": "bool" } - ], - "name": "setRolePermission", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "role", "type": "uint8" }, - { - "internalType": "RoleDescription", - "name": "description", - "type": "bytes32" - } - ], - "name": "updateRoleDescription", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/factory-llama/ponder.config.ts b/examples/factory-llama/ponder.config.ts index 437d1a4ef..8026a1048 100644 --- a/examples/factory-llama/ponder.config.ts +++ b/examples/factory-llama/ponder.config.ts @@ -1,15 +1,15 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { parseAbiItem } from "abitype"; import { http } from "viem"; -import LlamaCoreAbi from "./abis/LlamaCore.json"; -import LlamaPolicyAbi from "./abis/LlamaPolicy.json"; +import { LlamaCoreAbi } from "./abis/LlamaCore.abi"; +import { LlamaPolicyAbi } from "./abis/LlamaPolicy.abi"; const llamaFactoryEvent = parseAbiItem( "event LlamaInstanceCreated(address indexed deployer, string indexed name, address llamaCore, address llamaExecutor, address llamaPolicy, uint256 chainId)" ); -export const config: Config = { +export const config = createConfig({ networks: [ { name: "sepolia", @@ -20,7 +20,7 @@ export const config: Config = { contracts: [ { name: "LlamaCore", - network: "sepolia", + network: [{ name: "sepolia" }], abi: LlamaCoreAbi, factory: { address: "0xFf5d4E226D9A3496EECE31083a8F493edd79AbEB", @@ -31,7 +31,7 @@ export const config: Config = { }, { name: "LlamaPolicy", - network: "sepolia", + network: [{ name: "sepolia" }], abi: LlamaPolicyAbi, factory: { address: "0xFf5d4E226D9A3496EECE31083a8F493edd79AbEB", @@ -41,4 +41,4 @@ export const config: Config = { startBlock: 4121269, }, ], -}; +}); diff --git a/examples/friendtech/abis/FriendtechSharesV1.abi.ts b/examples/friendtech/abis/FriendtechSharesV1.abi.ts new file mode 100644 index 000000000..2f0a3a310 --- /dev/null +++ b/examples/friendtech/abis/FriendtechSharesV1.abi.ts @@ -0,0 +1,232 @@ +export const FriendtechSharesV1Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "trader", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "subject", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "isBuy", + type: "bool", + }, + { + indexed: false, + internalType: "uint256", + name: "shareAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "ethAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "protocolEthAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "subjectEthAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "supply", + type: "uint256", + }, + ], + name: "Trade", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "buyShares", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "getBuyPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "getBuyPriceAfterFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "supply", type: "uint256" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "getPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "getSellPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "getSellPriceAfterFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "protocolFeeDestination", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "protocolFeePercent", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sharesSubject", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "sellShares", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_feeDestination", + type: "address", + }, + ], + name: "setFeeDestination", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_feePercent", type: "uint256" }], + name: "setProtocolFeePercent", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_feePercent", type: "uint256" }], + name: "setSubjectFeePercent", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "sharesBalance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "sharesSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "subjectFeePercent", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/friendtech/abis/FriendtechSharesV1.json b/examples/friendtech/abis/FriendtechSharesV1.json deleted file mode 100644 index e2058a4df..000000000 --- a/examples/friendtech/abis/FriendtechSharesV1.json +++ /dev/null @@ -1,238 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "trader", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "subject", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "isBuy", - "type": "bool" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shareAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "ethAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "protocolEthAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "subjectEthAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "supply", - "type": "uint256" - } - ], - "name": "Trade", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "buyShares", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "getBuyPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "getBuyPriceAfterFee", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "supply", "type": "uint256" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "getPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "getSellPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "getSellPriceAfterFee", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "protocolFeeDestination", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "protocolFeePercent", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sharesSubject", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "sellShares", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_feeDestination", - "type": "address" - } - ], - "name": "setFeeDestination", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_feePercent", "type": "uint256" } - ], - "name": "setProtocolFeePercent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_feePercent", "type": "uint256" } - ], - "name": "setSubjectFeePercent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "sharesBalance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "sharesSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "subjectFeePercent", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/friendtech/ponder.config.ts b/examples/friendtech/ponder.config.ts index a5dee2d96..aa910fca3 100644 --- a/examples/friendtech/ponder.config.ts +++ b/examples/friendtech/ponder.config.ts @@ -1,7 +1,9 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -export const config: Config = { +import { FriendtechSharesV1Abi } from "./abis/FriendtechSharesV1.abi"; + +export const config = createConfig({ networks: [ { name: "base", @@ -12,11 +14,11 @@ export const config: Config = { contracts: [ { name: "FriendtechSharesV1", - network: "base", - abi: "./abis/FriendtechSharesV1.json", + network: [{ name: "base" }], + abi: FriendtechSharesV1Abi, address: "0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4", startBlock: 2430440, maxBlockRange: 100, }, ], -}; +}); diff --git a/examples/token-erc20/abis/AdventureGold.abi.ts b/examples/token-erc20/abis/AdventureGold.abi.ts new file mode 100644 index 000000000..4d21f2474 --- /dev/null +++ b/examples/token-erc20/abis/AdventureGold.abi.ts @@ -0,0 +1,349 @@ +export const AdventureGoldAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "adventureGoldPerTokenId", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "claimAllForOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "claimById", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "ownerIndexStart", + type: "uint256", + }, + { internalType: "uint256", name: "ownerIndexEnd", type: "uint256" }, + ], + name: "claimRangeForOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountDisplayValue", + type: "uint256", + }, + ], + name: "daoMint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "adventureGoldDisplayValue", + type: "uint256", + }, + ], + name: "daoSetAdventureGoldPerTokenId", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "lootContractAddress_", + type: "address", + }, + ], + name: "daoSetLootContractAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "season_", type: "uint256" }], + name: "daoSetSeason", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "season_", type: "uint256" }, + { + internalType: "uint256", + name: "adventureGoldDisplayValue", + type: "uint256", + }, + ], + name: "daoSetSeasonAndAdventureGoldPerTokenID", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "tokenIdStart_", type: "uint256" }, + { internalType: "uint256", name: "tokenIdEnd_", type: "uint256" }, + ], + name: "daoSetTokenIdRange", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { + internalType: "uint256", + name: "subtractedValue", + type: "uint256", + }, + ], + name: "decreaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "addedValue", type: "uint256" }, + ], + name: "increaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "lootContract", + outputs: [ + { + internalType: "contract IERC721Enumerable", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "lootContractAddress", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "season", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "seasonClaimedByTokenId", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tokenIdEnd", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tokenIdStart", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/token-erc20/abis/AdventureGold.json b/examples/token-erc20/abis/AdventureGold.json deleted file mode 100644 index 95231479d..000000000 --- a/examples/token-erc20/abis/AdventureGold.json +++ /dev/null @@ -1,357 +0,0 @@ -[ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "adventureGoldPerTokenId", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimAllForOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "claimById", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ownerIndexStart", - "type": "uint256" - }, - { "internalType": "uint256", "name": "ownerIndexEnd", "type": "uint256" } - ], - "name": "claimRangeForOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amountDisplayValue", - "type": "uint256" - } - ], - "name": "daoMint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "adventureGoldDisplayValue", - "type": "uint256" - } - ], - "name": "daoSetAdventureGoldPerTokenId", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "lootContractAddress_", - "type": "address" - } - ], - "name": "daoSetLootContractAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "season_", "type": "uint256" } - ], - "name": "daoSetSeason", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "season_", "type": "uint256" }, - { - "internalType": "uint256", - "name": "adventureGoldDisplayValue", - "type": "uint256" - } - ], - "name": "daoSetSeasonAndAdventureGoldPerTokenID", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenIdStart_", "type": "uint256" }, - { "internalType": "uint256", "name": "tokenIdEnd_", "type": "uint256" } - ], - "name": "daoSetTokenIdRange", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "lootContract", - "outputs": [ - { - "internalType": "contract IERC721Enumerable", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lootContractAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "season", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "seasonClaimedByTokenId", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tokenIdEnd", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tokenIdStart", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/token-erc20/ponder.config.ts b/examples/token-erc20/ponder.config.ts index a3609e439..a15524575 100644 --- a/examples/token-erc20/ponder.config.ts +++ b/examples/token-erc20/ponder.config.ts @@ -1,7 +1,9 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -export const config: Config = { +import { AdventureGoldAbi } from "./abis/AdventureGold.abi"; + +export const config = createConfig({ networks: [ { name: "mainnet", @@ -12,11 +14,11 @@ export const config: Config = { contracts: [ { name: "AdventureGold", - network: "mainnet", - abi: "./abis/AdventureGold.json", + network: [{ name: "mainnet" }], + abi: AdventureGoldAbi, address: "0x32353A6C91143bfd6C7d363B546e62a9A2489A20", startBlock: 13142655, endBlock: 13150000, }, ], -}; +}); diff --git a/examples/token-erc721/abis/SmolBrain.abi.ts b/examples/token-erc721/abis/SmolBrain.abi.ts new file mode 100644 index 000000000..4d4c8cc62 --- /dev/null +++ b/examples/token-erc721/abis/SmolBrain.abi.ts @@ -0,0 +1,597 @@ +export const SmolBrainAbi = [ + { + inputs: [ + { internalType: "address", name: "_luckyWinner", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "brainMaxLevel", + type: "uint256", + }, + ], + name: "LandMaxLevel", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "land", + type: "address", + }, + ], + name: "LandSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "levelIQCost", + type: "uint256", + }, + ], + name: "LevelIQCost", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "previousAdminRole", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "newAdminRole", + type: "bytes32", + }, + ], + name: "RoleAdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleGranted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "role", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleRevoked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "school", + type: "address", + }, + ], + name: "SchoolSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + { + indexed: false, + internalType: "enum SmolBrain.Gender", + name: "gender", + type: "uint8", + }, + ], + name: "SmolBrainMint", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "DEFAULT_ADMIN_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SMOLBRAIN_MINTER_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "SMOLBRAIN_OWNER_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "averageIQ", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "baseURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "brainMaxLevel", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "brainz", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_tokenId", type: "uint256" }], + name: "getGender", + outputs: [ + { internalType: "enum SmolBrain.Gender", name: "", type: "uint8" }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "role", type: "bytes32" }], + name: "getRoleAdmin", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_minter", type: "address" }], + name: "grantMinter", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_owner", type: "address" }], + name: "grantOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "grantRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "hasRole", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_minter", type: "address" }], + name: "isMinter", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_owner", type: "address" }], + name: "isOwner", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "land", + outputs: [{ internalType: "contract Land", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "levelIQCost", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_to", type: "address" }], + name: "mintFemale", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_to", type: "address" }], + name: "mintMale", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "renounceRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "revokeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_tokenId", type: "uint256" }], + name: "scanBrain", + outputs: [{ internalType: "uint256", name: "IQ", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "school", + outputs: [{ internalType: "contract School", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_tokenId", type: "uint256" }, + { internalType: "uint256", name: "_iqEarned", type: "uint256" }, + ], + name: "schoolDrop", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "_baseURItoSet", type: "string" }], + name: "setBaseURI", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_land", type: "address" }], + name: "setLand", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_levelIQCost", type: "uint256" }, + ], + name: "setLevelIQCost", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_brainMaxLevel", type: "uint256" }, + ], + name: "setMaxLevel", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_school", type: "address" }], + name: "setSchool", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "index", type: "uint256" }], + name: "tokenByIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "index", type: "uint256" }, + ], + name: "tokenOfOwnerByIndex", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_tokenId", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/token-erc721/abis/SmolBrain.json b/examples/token-erc721/abis/SmolBrain.json deleted file mode 100644 index 7524d4896..000000000 --- a/examples/token-erc721/abis/SmolBrain.json +++ /dev/null @@ -1,633 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "address", "name": "_luckyWinner", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "brainMaxLevel", - "type": "uint256" - } - ], - "name": "LandMaxLevel", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "land", - "type": "address" - } - ], - "name": "LandSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "levelIQCost", - "type": "uint256" - } - ], - "name": "LevelIQCost", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "previousAdminRole", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "newAdminRole", - "type": "bytes32" - } - ], - "name": "RoleAdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "school", - "type": "address" - } - ], - "name": "SchoolSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "enum SmolBrain.Gender", - "name": "gender", - "type": "uint8" - } - ], - "name": "SmolBrainMint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "DEFAULT_ADMIN_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "SMOLBRAIN_MINTER_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "SMOLBRAIN_OWNER_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "averageIQ", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "baseURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "brainMaxLevel", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "brainz", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" } - ], - "name": "getGender", - "outputs": [ - { "internalType": "enum SmolBrain.Gender", "name": "", "type": "uint8" } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" } - ], - "name": "getRoleAdmin", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_minter", "type": "address" } - ], - "name": "grantMinter", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_owner", "type": "address" } - ], - "name": "grantOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "hasRole", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "operator", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_minter", "type": "address" } - ], - "name": "isMinter", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_owner", "type": "address" } - ], - "name": "isOwner", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "land", - "outputs": [ - { "internalType": "contract Land", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "levelIQCost", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], - "name": "mintFemale", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], - "name": "mintMale", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "ownerOf", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "renounceRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" } - ], - "name": "scanBrain", - "outputs": [{ "internalType": "uint256", "name": "IQ", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "school", - "outputs": [ - { "internalType": "contract School", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" }, - { "internalType": "uint256", "name": "_iqEarned", "type": "uint256" } - ], - "name": "schoolDrop", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "_baseURItoSet", "type": "string" } - ], - "name": "setBaseURI", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_land", "type": "address" } - ], - "name": "setLand", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_levelIQCost", "type": "uint256" } - ], - "name": "setLevelIQCost", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_brainMaxLevel", "type": "uint256" } - ], - "name": "setMaxLevel", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_school", "type": "address" } - ], - "name": "setSchool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "index", "type": "uint256" } - ], - "name": "tokenByIndex", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "uint256", "name": "index", "type": "uint256" } - ], - "name": "tokenOfOwnerByIndex", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" } - ], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/token-erc721/ponder.config.ts b/examples/token-erc721/ponder.config.ts index 4c35e9de2..bcfaa1392 100644 --- a/examples/token-erc721/ponder.config.ts +++ b/examples/token-erc721/ponder.config.ts @@ -1,7 +1,9 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -export const config: Config = { +import { SmolBrainAbi } from "./abis/SmolBrain.abi"; + +export const config = createConfig({ networks: [ { name: "arbitrum", @@ -12,10 +14,10 @@ export const config: Config = { contracts: [ { name: "SmolBrain", - network: "arbitrum", - abi: "./abis/SmolBrain.json", + network: [{ name: "arbitrum" }], + abi: SmolBrainAbi, address: "0x6325439389E0797Ab35752B4F43a14C004f22A9c", startBlock: 3163146, }, ], -}; +}); diff --git a/examples/token-reth/abis/RocketTokenRETH.abi.ts b/examples/token-reth/abis/RocketTokenRETH.abi.ts new file mode 100644 index 000000000..b3c75d22e --- /dev/null +++ b/examples/token-reth/abis/RocketTokenRETH.abi.ts @@ -0,0 +1,324 @@ +export const RocketTokenRETHAbi = [ + { + inputs: [ + { + internalType: "contract RocketStorageInterface", + name: "_rocketStorageAddress", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "time", + type: "uint256", + }, + ], + name: "EtherDeposited", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "ethAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "time", + type: "uint256", + }, + ], + name: "TokensBurned", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "ethAmount", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "time", + type: "uint256", + }, + ], + name: "TokensMinted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_rethAmount", type: "uint256" }], + name: "burn", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { + internalType: "uint256", + name: "subtractedValue", + type: "uint256", + }, + ], + name: "decreaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "depositExcess", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "depositExcessCollateral", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getCollateralRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_rethAmount", type: "uint256" }], + name: "getEthValue", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getExchangeRate", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "_ethAmount", type: "uint256" }], + name: "getRethValue", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalCollateral", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "addedValue", type: "uint256" }, + ], + name: "increaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_ethAmount", type: "uint256" }, + { internalType: "address", name: "_to", type: "address" }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "version", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; diff --git a/examples/token-reth/abis/RocketTokenRETH.json b/examples/token-reth/abis/RocketTokenRETH.json deleted file mode 100644 index c2094f2c2..000000000 --- a/examples/token-reth/abis/RocketTokenRETH.json +++ /dev/null @@ -1,332 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract RocketStorageInterface", - "name": "_rocketStorageAddress", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "time", - "type": "uint256" - } - ], - "name": "EtherDeposited", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "ethAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "time", - "type": "uint256" - } - ], - "name": "TokensBurned", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "ethAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "time", - "type": "uint256" - } - ], - "name": "TokensMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_rethAmount", "type": "uint256" } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "depositExcess", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "depositExcessCollateral", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "getCollateralRate", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_rethAmount", "type": "uint256" } - ], - "name": "getEthValue", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getExchangeRate", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_ethAmount", "type": "uint256" } - ], - "name": "getRethValue", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalCollateral", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "_ethAmount", "type": "uint256" }, - { "internalType": "address", "name": "_to", "type": "address" } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { "stateMutability": "payable", "type": "receive" } -] diff --git a/examples/token-reth/ponder.config.ts b/examples/token-reth/ponder.config.ts index d5b306780..6459fc25e 100644 --- a/examples/token-reth/ponder.config.ts +++ b/examples/token-reth/ponder.config.ts @@ -1,7 +1,9 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; -export const config: Config = { +import { RocketTokenRETHAbi } from "./abis/RocketTokenRETH.abi"; + +export const config = createConfig({ networks: [ { name: "mainnet", @@ -13,10 +15,10 @@ export const config: Config = { contracts: [ { name: "RocketTokenRETH", - network: "mainnet", - abi: "./abis/RocketTokenRETH.json", + network: [{ name: "mainnet" }], + abi: RocketTokenRETHAbi, address: "0xae78736cd615f374d3085123a210448e74fc6393", startBlock: 13325304, }, ], -}; +}); From 99e62d18c403c9cad816b413fefcf28e365ef1c1 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 13:51:20 -0400 Subject: [PATCH 16/44] forgot one --- examples/art-gobblers/ponder.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/art-gobblers/ponder.config.ts b/examples/art-gobblers/ponder.config.ts index d5fd61dbf..07f747069 100644 --- a/examples/art-gobblers/ponder.config.ts +++ b/examples/art-gobblers/ponder.config.ts @@ -1,7 +1,7 @@ import { createConfig } from "@ponder/core"; import { http } from "viem"; -import { ArtGobblersAbi } from "./ArtGobblers.abi"; +import { ArtGobblersAbi } from "./abis/ArtGobblers.abi"; export const config = createConfig({ networks: [ From 3d7aa6d7218c9545588116c26402b88c91bce672 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 16:38:42 -0400 Subject: [PATCH 17/44] config types --- packages/core/src/config/config.test-d.ts | 111 ++++++++++++++++++++++ packages/core/src/config/config.ts | 45 ++++++++- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/config/config.test-d.ts diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts new file mode 100644 index 000000000..f39ba7d01 --- /dev/null +++ b/packages/core/src/config/config.test-d.ts @@ -0,0 +1,111 @@ +import { assertType, test } from "vitest"; + +import { FilterElement, FilterEvents, SafeEventNames } from "./config"; + +const abiSimple = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { + indexed: true, + type: "address", + }, + { + indexed: true, + type: "address", + }, + { + indexed: false, + type: "uint256", + }, + ], + name: "Approve", + type: "event", + }, + { + inputs: [ + { + indexed: true, + type: "address", + }, + { + indexed: true, + type: "address", + }, + { + indexed: false, + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, +] as const; + +const abiWithSameEvent = [ + ...abiSimple, + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { + indexed: true, + type: "address", + }, + { + indexed: true, + type: "bytes32", + }, + { + indexed: false, + type: "uint256", + }, + ], + name: "Approve", + type: "event", + }, +] as const; + +test("filter events", () => { + type t = FilterEvents; + // ^? + + assertType([ + abiWithSameEvent[1], + abiWithSameEvent[2], + abiWithSameEvent[4], + ] as const); +}); + +test("filter elements", () => { + type a = FilterElement<"a", readonly ["a", "b", "c"]>; + // ^? + assertType(["b", "c"] as const); +}); + +test("safe event names", () => { + type a = SafeEventNames< + // ^? + FilterEvents, + FilterEvents + >; + assertType(["Approve", "Transfer"] as const); + + type b = SafeEventNames< + // ^? + FilterEvents, + FilterEvents + >; + assertType([ + "Approve(address indexed, address indexed, uint256)", + "Transfer", + "Approve(address indexed, bytes32 indexed, uint256)", + ]); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 1946ccfa9..bd0bc9bad 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1,6 +1,49 @@ -import type { Abi, AbiEvent } from "abitype"; +import type { Abi, AbiEvent, FormatAbiItem } from "abitype"; import type { Transport } from "viem"; +/** + * Keep only AbiEvents from an Abi + */ +export type FilterEvents = T extends readonly [ + infer First, + ...infer Rest extends Abi +] + ? First extends AbiEvent + ? readonly [First, ...FilterEvents] + : FilterEvents + : []; + +/** + * Remove TElement from TArr + */ +export type FilterElement< + TElement, + TArr extends readonly unknown[] +> = TArr extends readonly [infer First, ...infer Rest] + ? TElement extends First + ? FilterElement + : readonly [First, ...FilterElement] + : []; + +/** + * Return an array of safe event names that handle multiple events with the same name + */ +export type SafeEventNames< + TAbi extends readonly AbiEvent[], + TArr extends readonly AbiEvent[] +> = TAbi extends readonly [ + infer First extends AbiEvent, + ...infer Rest extends readonly AbiEvent[] +] + ? First["name"] extends FilterElement[number]["name"] + ? // Name collisions exist, format long name + FormatAbiItem extends `event ${infer LongEvent extends string}` + ? readonly [LongEvent, ...SafeEventNames] + : never + : // Short name + readonly [First["name"], ...SafeEventNames] + : []; + type ContractRequired< TNetworkName extends string | unknown = string | unknown > = { From 2468e9bb056d6002d0be40cada0af4960f05ef4d Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 17:46:45 -0400 Subject: [PATCH 18/44] barely workingish --- packages/core/src/config/config.test-d.ts | 18 ++- packages/core/src/config/config.test.ts | 21 ++++ packages/core/src/config/config.ts | 140 +++++++++++++--------- 3 files changed, 120 insertions(+), 59 deletions(-) diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index f39ba7d01..b9b91536d 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -1,8 +1,13 @@ import { assertType, test } from "vitest"; -import { FilterElement, FilterEvents, SafeEventNames } from "./config"; +import { + ContractFilter, + FilterElement, + FilterEvents, + SafeEventNames, +} from "./config"; -const abiSimple = [ +export const abiSimple = [ { inputs: [], stateMutability: "nonpayable", @@ -46,7 +51,7 @@ const abiSimple = [ }, ] as const; -const abiWithSameEvent = [ +export const abiWithSameEvent = [ ...abiSimple, { inputs: [], @@ -109,3 +114,10 @@ test("safe event names", () => { "Approve(address indexed, bytes32 indexed, uint256)", ]); }); + +test("infer event names from abi", () => { + type a = ContractFilter["event"]; + // ^? + + assertType([] as readonly ("Approve" | "Transfer")[] | undefined); +}); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 9d66e85ca..3a655b73b 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -2,6 +2,7 @@ import { http } from "viem"; import { test } from "vitest"; import { createConfig } from "./config"; +import { abiSimple } from "./config.test-d"; test("createConfig enforces matching network names", () => { createConfig({ @@ -21,3 +22,23 @@ test("createConfig enforces matching network names", () => { ], }); }); + +test("createConfig() has strict events inferred from abi", () => { + createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiSimple, + event: ["Transfer", "Approve"], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index bd0bc9bad..4b1edd08d 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -4,7 +4,7 @@ import type { Transport } from "viem"; /** * Keep only AbiEvents from an Abi */ -export type FilterEvents = T extends readonly [ +export type FilterEvents = T extends readonly [ infer First, ...infer Rest extends Abi ] @@ -18,7 +18,7 @@ export type FilterEvents = T extends readonly [ */ export type FilterElement< TElement, - TArr extends readonly unknown[] + TArr extends readonly unknown[] | unknown > = TArr extends readonly [infer First, ...infer Rest] ? TElement extends First ? FilterElement @@ -29,8 +29,8 @@ export type FilterElement< * Return an array of safe event names that handle multiple events with the same name */ export type SafeEventNames< - TAbi extends readonly AbiEvent[], - TArr extends readonly AbiEvent[] + TAbi extends readonly AbiEvent[] | unknown, + TArr extends readonly AbiEvent[] | unknown > = TAbi extends readonly [ infer First extends AbiEvent, ...infer Rest extends readonly AbiEvent[] @@ -45,7 +45,8 @@ export type SafeEventNames< : []; type ContractRequired< - TNetworkName extends string | unknown = string | unknown + TNetworkNames extends string | unknown = string | unknown, + TAbi extends Abi | unknown = Abi | unknown > = { /** Contract name. Must be unique across `contracts` and `filters`. */ name: string; @@ -53,11 +54,11 @@ type ContractRequired< * Network that this contract is deployed to. Must match a network name in `networks`. * Any filter information overrides the values in the higher level "contracts" property. Factories cannot override an address and vice versa. */ - network: readonly ({ name: TNetworkName } & Partial)[]; + network: readonly ({ name: TNetworkNames } & Partial>)[]; abi: Abi; }; -type ContractFilter = ( +export type ContractFilter = ( | { /** Contract address. */ address?: `0x${string}`; @@ -81,69 +82,89 @@ type ContractFilter = ( /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ maxBlockRange?: number; - event?: - | { - signature: AbiEvent; - args: any[]; - } - | AbiEvent[]; + event?: readonly SafeEventNames< + FilterEvents, + FilterEvents + >[number][]; +}; + +type Database = + | { + kind: "sqlite"; + /** Path to SQLite database file. Default: `"./.ponder/cache.db"`. */ + filename?: string; + } + | { + kind: "postgres"; + /** PostgreSQL database connection string. Default: `process.env.DATABASE_URL`. */ + connectionString?: string; + }; + +type Network = { + /** Network name. Must be unique across all networks. */ + name: string; + /** Chain ID of the network. */ + chainId: number; + /** A viem `http`, `webSocket`, or `fallback` [Transport](https://viem.sh/docs/clients/transports/http.html). + * + * __To avoid rate limiting, include a custom RPC URL.__ Usage: + * + * ```ts + * import { http } from "viem"; + * + * const network = { + * name: "mainnet", + * chainId: 1, + * transport: http("https://eth-mainnet.g.alchemy.com/v2/..."), + * } + * ``` + */ + transport: Transport; + /** Polling frequency (in ms). Default: `1_000`. */ + pollingInterval?: number; + /** Maximum concurrency of RPC requests during the historical sync. Default: `10`. */ + maxRpcRequestConcurrency?: number; +}; + +type Contract< + TNetworkNames extends string | unknown = string | unknown, + TAbi extends Abi | unknown = Abi | unknown +> = ContractRequired & ContractFilter; + +type Option = { + /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ + maxHealthcheckDuration?: number; }; export type ResolvedConfig< - TNetworkName extends string | unknown = string | unknown + TNetworkNames extends string | unknown = string | unknown, + TAbi extends Abi = Abi > = { /** Database to use for storing blockchain & entity data. Default: `"postgres"` if `DATABASE_URL` env var is present, otherwise `"sqlite"`. */ - database?: - | { - kind: "sqlite"; - /** Path to SQLite database file. Default: `"./.ponder/cache.db"`. */ - filename?: string; - } - | { - kind: "postgres"; - /** PostgreSQL database connection string. Default: `process.env.DATABASE_URL`. */ - connectionString?: string; - }; + database?: Database; /** List of blockchain networks. */ - networks: readonly { - /** Network name. Must be unique across all networks. */ - name: string; - /** Chain ID of the network. */ - chainId: number; - /** A viem `http`, `webSocket`, or `fallback` [Transport](https://viem.sh/docs/clients/transports/http.html). - * - * __To avoid rate limiting, include a custom RPC URL.__ Usage: - * - * ```ts - * import { http } from "viem"; - * - * const network = { - * name: "mainnet", - * chainId: 1, - * transport: http("https://eth-mainnet.g.alchemy.com/v2/..."), - * } - * ``` - */ - transport: Transport; - /** Polling frequency (in ms). Default: `1_000`. */ - pollingInterval?: number; - /** Maximum concurrency of RPC requests during the historical sync. Default: `10`. */ - maxRpcRequestConcurrency?: number; - }[]; + networks: readonly Network[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: readonly (ContractRequired & ContractFilter)[]; + contracts?: readonly Contract[]; /** Configuration for Ponder internals. */ - options?: { - /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ - maxHealthcheckDuration?: number; - }; + options?: Option; }; /** * Identity function for type-level validation of config */ export const createConfig = < - const TConfig extends ResolvedConfig + const TConfig extends { + database?: Database; + networks: readonly Network[]; + contracts: { + [key in keyof TConfig["contracts"]]: Contract< + TConfig["networks"][number]["name"], + TConfig["contracts"][key]["abi"] + >; + }; + options?: Option; + } >( config: | TConfig @@ -151,3 +172,10 @@ export const createConfig = < | (() => TConfig) | (() => Promise) ) => config; + +// contracts: { +// [key in keyof TConfig["contracts"] & number]: Contract< +// TConfig["networks"][number]["name"], +// TConfig["contracts"][key]["abi"] +// >; +// }; From fb3a2a85225f8f1a03f90b6a66865c351530e6de Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:57:46 -0400 Subject: [PATCH 19/44] Fix the stale table bug (#405) * FIX THE STALE TABLE BUG * tolerate errors in test cleanup * chore: changeset * try removing timeout * Add logs * add another log * another log * add global shutdown * add schema to log * remove logs from migration test * use tx * add retry --- .changeset/loud-days-perform.md | 5 ++ packages/core/src/Ponder.ts | 4 +- packages/core/src/_test/globalSetup.ts | 37 +++++++++- packages/core/src/_test/setup.ts | 18 +++-- .../event-store/postgres/migrations.test.ts | 35 ++++++---- .../src/event-store/sqlite/migrations.test.ts | 32 ++++----- packages/core/src/server/service.test.ts | 5 -- .../core/src/user-store/postgres/store.ts | 29 ++++---- packages/core/src/user-store/sqlite/store.ts | 29 ++++---- packages/core/src/user-store/store.test.ts | 70 ------------------- packages/core/src/user-store/store.ts | 2 +- 11 files changed, 119 insertions(+), 147 deletions(-) create mode 100644 .changeset/loud-days-perform.md diff --git a/.changeset/loud-days-perform.md b/.changeset/loud-days-perform.md new file mode 100644 index 000000000..6ea1d31db --- /dev/null +++ b/.changeset/loud-days-perform.md @@ -0,0 +1,5 @@ +--- +"@ponder/core": patch +--- + +Fixed a bug where stale tables were left in the database after the service was stopped. diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index 7e5b452c5..f5f13ab65 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -293,13 +293,13 @@ export class Ponder { ) ); - await this.buildService.kill?.(); + await this.buildService.kill(); this.uiService.kill(); this.indexingService.kill(); await this.serverService.kill(); - await this.userStore.teardown(); await this.common.telemetry.kill(); + await this.userStore.kill(); await this.eventStore.kill(); this.common.logger.debug({ diff --git a/packages/core/src/_test/globalSetup.ts b/packages/core/src/_test/globalSetup.ts index 6fded9655..0b413ee17 100644 --- a/packages/core/src/_test/globalSetup.ts +++ b/packages/core/src/_test/globalSetup.ts @@ -1,5 +1,6 @@ import { startProxy } from "@viem/anvil"; import dotenv from "dotenv"; +import { Pool } from "pg"; import { FORK_BLOCK_NUMBER } from "./constants"; @@ -11,11 +12,45 @@ export default async function () { throw new Error('Missing environment variable "ANVIL_FORK_URL"'); } - return await startProxy({ + const shutdownProxy = await startProxy({ options: { chainId: 1, forkUrl: ANVIL_FORK_URL, forkBlockNumber: FORK_BLOCK_NUMBER, }, }); + + let cleanupDatabase: () => Promise; + if (process.env.DATABASE_URL) { + cleanupDatabase = async () => { + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + + const schemaRows = await pool.query(` + SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname ~ '^vitest_pool_'; + `); + const schemas = schemaRows.rows.map((r) => r.nspname) as string[]; + + for (const schema of schemas) { + const tableRows = await pool.query(` + SELECT table_name FROM information_schema.tables WHERE table_schema = '${schema}' + `); + const tables = tableRows.rows.map((r) => r.table_name) as string[]; + + for (const table of tables) { + await pool.query( + `DROP TABLE IF EXISTS "${schema}"."${table}" CASCADE` + ); + } + await pool.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`); + console.log(`Dropped ${tables.length} tables from schema "${schema}".`); + } + + await pool.end(); + }; + } + + return async () => { + await shutdownProxy(); + await cleanupDatabase?.(); + }; } diff --git a/packages/core/src/_test/setup.ts b/packages/core/src/_test/setup.ts index 08c4d0eca..6af8cddf1 100644 --- a/packages/core/src/_test/setup.ts +++ b/packages/core/src/_test/setup.ts @@ -69,31 +69,31 @@ beforeEach((context) => { */ export async function setupEventStore( context: TestContext, - options = { skipMigrateUp: false } + options = { migrateUp: true } ) { if (process.env.DATABASE_URL) { const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const databaseSchema = `vitest_pool_${process.pid}_${poolId}`; context.eventStore = new PostgresEventStore({ pool, databaseSchema }); - if (!options.skipMigrateUp) await context.eventStore.migrateUp(); + if (options.migrateUp) await context.eventStore.migrateUp(); return async () => { try { await pool.query(`DROP SCHEMA IF EXISTS "${databaseSchema}" CASCADE`); + await context.eventStore.kill(); } catch (e) { - // This query fails in end-to-end tests where the pool has + // This fails in end-to-end tests where the pool has // already been shut down during the Ponder instance kill() method. // It's fine to ignore the error. } - await context.eventStore.kill(); }; } else { const rawSqliteDb = new SqliteDatabase(":memory:"); const db = patchSqliteDatabase({ db: rawSqliteDb }); context.eventStore = new SqliteEventStore({ db }); - if (!options.skipMigrateUp) await context.eventStore.migrateUp(); + if (options.migrateUp) await context.eventStore.migrateUp(); return async () => { await context.eventStore.kill(); @@ -121,7 +121,13 @@ export async function setupUserStore(context: TestContext) { } return async () => { - await context.userStore.teardown(); + try { + await context.userStore.kill(); + } catch (e) { + // This fails in end-to-end tests where the pool has + // already been shut down during the Ponder instance kill() method. + // It's fine to ignore the error. + } }; } diff --git a/packages/core/src/event-store/postgres/migrations.test.ts b/packages/core/src/event-store/postgres/migrations.test.ts index 708d1b8c7..b5dd9947b 100644 --- a/packages/core/src/event-store/postgres/migrations.test.ts +++ b/packages/core/src/event-store/postgres/migrations.test.ts @@ -15,7 +15,7 @@ import { rpcToPostgresTransaction, } from "./format"; -beforeEach((context) => setupEventStore(context, { skipMigrateUp: true })); +beforeEach((context) => setupEventStore(context, { migrateUp: false })); const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { await db @@ -60,20 +60,27 @@ const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { .execute(); }; -test("2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", async (context) => { - const { eventStore } = context; +test( + "2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", + async (context) => { + const { eventStore } = context; - if (eventStore.kind !== "postgres") return; + if (eventStore.kind !== "postgres") return; - const { error } = await eventStore.migrator.migrateTo( - "2023_07_24_0_drop_finalized" - ); - expect(error).toBeFalsy(); + const { error } = await eventStore.migrator.migrateTo( + "2023_07_24_0_drop_finalized" + ); - await seed_2023_07_24_0_drop_finalized(eventStore.db); + expect(error).toBeFalsy(); - const { error: latestError } = await eventStore.migrator.migrateTo( - "2023_09_19_0_new_sync_design" - ); - expect(latestError).toBeFalsy(); -}, 15_000); + await seed_2023_07_24_0_drop_finalized(eventStore.db); + + const { error: latestError } = await eventStore.migrator.migrateTo( + "2023_09_19_0_new_sync_design" + ); + expect(latestError).toBeFalsy(); + }, + // This test is flaky. It seems like a Postgres isolation issue with our + // test setup. Annoying! + { timeout: 15_000, retry: 3 } +); diff --git a/packages/core/src/event-store/sqlite/migrations.test.ts b/packages/core/src/event-store/sqlite/migrations.test.ts index 4a2cc939f..1b9d29b3e 100644 --- a/packages/core/src/event-store/sqlite/migrations.test.ts +++ b/packages/core/src/event-store/sqlite/migrations.test.ts @@ -15,7 +15,7 @@ import { rpcToSqliteTransaction, } from "./format"; -beforeEach((context) => setupEventStore(context, { skipMigrateUp: true })); +beforeEach((context) => setupEventStore(context, { migrateUp: false })); const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { await db @@ -60,24 +60,20 @@ const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { .execute(); }; -test( - "2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", - async (context) => { - const { eventStore } = context; +test("2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", async (context) => { + const { eventStore } = context; - if (eventStore.kind !== "sqlite") return; + if (eventStore.kind !== "sqlite") return; - const { error } = await eventStore.migrator.migrateTo( - "2023_07_24_0_drop_finalized" - ); - expect(error).toBeFalsy(); + const { error } = await eventStore.migrator.migrateTo( + "2023_07_24_0_drop_finalized" + ); + expect(error).toBeFalsy(); - await seed_2023_07_24_0_drop_finalized(eventStore.db); + await seed_2023_07_24_0_drop_finalized(eventStore.db); - const { error: latestError } = await eventStore.migrator.migrateTo( - "2023_09_19_0_new_sync_design" - ); - expect(latestError).toBeFalsy(); - }, - {} -); + const { error: latestError } = await eventStore.migrator.migrateTo( + "2023_09_19_0_new_sync_design" + ); + expect(latestError).toBeFalsy(); +}); diff --git a/packages/core/src/server/service.test.ts b/packages/core/src/server/service.test.ts index 569c1ec03..4d4ebf641 100644 --- a/packages/core/src/server/service.test.ts +++ b/packages/core/src/server/service.test.ts @@ -1393,7 +1393,6 @@ test("serves singular entity versioned at specified timestamp", async (context) expect(testEntity.string).toBe("updated"); await service.kill(); - await userStore.teardown(); }); test("serves plural entities versioned at specified timestamp", async (context) => { @@ -1452,7 +1451,6 @@ test("serves plural entities versioned at specified timestamp", async (context) ]); await service.kill(); - await userStore.teardown(); }); test("derived field respects skip argument", async (context) => { @@ -1485,7 +1483,6 @@ test("derived field respects skip argument", async (context) => { }); await service.kill(); - await userStore.teardown(); }); test("responds with appropriate status code pre and post historical sync", async (context) => { @@ -1530,7 +1527,6 @@ test("responds with appropriate status code pre and post historical sync", async }); await service.kill(); - await userStore.teardown(); }); // This is a known limitation for now, which is that the timestamp version of entities @@ -1592,5 +1588,4 @@ test.skip("serves derived entities versioned at provided timestamp", async (cont }); await service.kill(); - await userStore.teardown(); }); diff --git a/packages/core/src/user-store/postgres/store.ts b/packages/core/src/user-store/postgres/store.ts index 859dc8c6c..0b3da3e7f 100644 --- a/packages/core/src/user-store/postgres/store.ts +++ b/packages/core/src/user-store/postgres/store.ts @@ -169,22 +169,21 @@ export class PostgresUserStore implements UserStore { }); }; - /** - * Tears down the store by dropping all tables for the current schema. - */ - teardown = async () => { - if (!this.schema) return; + async kill() { + const entities = this.schema?.entities ?? []; + if (entities.length > 0) { + await this.db.transaction().execute(async (tx) => { + await Promise.all( + entities.map(async (model) => { + const tableName = `${model.name}_${this.versionId}`; + await tx.schema.dropTable(tableName).execute(); + }) + ); + }); + } - // Drop tables from existing schema. - await this.db.transaction().execute(async (tx) => { - await Promise.all( - this.schema!.entities.map((model) => { - const tableName = `${model.name}_${this.versionId}`; - tx.schema.dropTable(tableName); - }) - ); - }); - }; + await this.db.destroy(); + } findUnique = async ({ modelName, diff --git a/packages/core/src/user-store/sqlite/store.ts b/packages/core/src/user-store/sqlite/store.ts index 4092d5644..193749d2c 100644 --- a/packages/core/src/user-store/sqlite/store.ts +++ b/packages/core/src/user-store/sqlite/store.ts @@ -150,22 +150,21 @@ export class SqliteUserStore implements UserStore { }); }; - /** - * Tears down the store by dropping all tables for the current schema. - */ - teardown = async () => { - if (!this.schema) return; + async kill() { + const entities = this.schema?.entities ?? []; + if (entities.length > 0) { + await this.db.transaction().execute(async (tx) => { + await Promise.all( + entities.map(async (model) => { + const tableName = `${model.name}_${this.versionId}`; + await tx.schema.dropTable(tableName).execute(); + }) + ); + }); + } - // Drop tables from existing schema. - await this.db.transaction().execute(async (tx) => { - await Promise.all( - this.schema!.entities.map((model) => { - const tableName = `${model.name}_${this.versionId}`; - tx.schema.dropTable(tableName); - }) - ); - }); - }; + await this.db.destroy(); + } findUnique = async ({ modelName, diff --git a/packages/core/src/user-store/store.test.ts b/packages/core/src/user-store/store.test.ts index aff263080..c3828df45 100644 --- a/packages/core/src/user-store/store.test.ts +++ b/packages/core/src/user-store/store.test.ts @@ -35,8 +35,6 @@ test("reload() binds the schema", async (context) => { await userStore.reload({ schema }); expect(userStore.schema).toBe(schema); - - await userStore.teardown(); }); test("create() inserts a record that is effective after timestamp", async (context) => { @@ -56,8 +54,6 @@ test("create() inserts a record that is effective after timestamp", async (conte id: "id1", }); expect(instance).toMatchObject({ id: "id1", name: "Skip", age: 12 }); - - await userStore.teardown(); }); test("create() inserts a record that is effective at timestamp", async (context) => { @@ -77,8 +73,6 @@ test("create() inserts a record that is effective at timestamp", async (context) id: "id1", }); expect(instance).toMatchObject({ id: "id1", name: "Skip", age: 12 }); - - await userStore.teardown(); }); test("create() inserts a record that is not effective before timestamp", async (context) => { @@ -98,8 +92,6 @@ test("create() inserts a record that is not effective before timestamp", async ( id: "id1", }); expect(instance).toBeNull(); - - await userStore.teardown(); }); test("create() throws on unique constraint violation", async (context) => { @@ -121,8 +113,6 @@ test("create() throws on unique constraint violation", async (context) => { data: { name: "Skip", age: 13 }, }) ).rejects.toThrow(); - - await userStore.teardown(); }); test("create() respects optional fields", async (context) => { @@ -143,8 +133,6 @@ test("create() respects optional fields", async (context) => { }); expect(instance).toMatchObject({ id: "id1", name: "Skip", age: null }); - - await userStore.teardown(); }); test("create() accepts enums", async (context) => { @@ -165,8 +153,6 @@ test("create() accepts enums", async (context) => { }); expect(instance).toMatchObject({ id: "id1", name: "Skip", kind: "CAT" }); - - await userStore.teardown(); }); test("create() throws on invalid enum value", async (context) => { @@ -181,8 +167,6 @@ test("create() throws on invalid enum value", async (context) => { data: { name: "Skip", kind: "NOTACAT" }, }) ).rejects.toThrow(); - - await userStore.teardown(); }); test("create() accepts BigInt fields as bigint and returns as bigint", async (context) => { @@ -203,8 +187,6 @@ test("create() accepts BigInt fields as bigint and returns as bigint", async (co }); expect(instance).toMatchObject({ id: "id1", name: "Skip", bigAge: 100n }); - - await userStore.teardown(); }); test("update() updates a record", async (context) => { @@ -236,8 +218,6 @@ test("update() updates a record", async (context) => { id: "id1", }); expect(updatedInstance).toMatchObject({ id: "id1", name: "Peanut Butter" }); - - await userStore.teardown(); }); test("update() updates a record using an update function", async (context) => { @@ -274,8 +254,6 @@ test("update() updates a record using an update function", async (context) => { id: "id1", name: "Skip and Skipper", }); - - await userStore.teardown(); }); test("update() updates a record and maintains older version", async (context) => { @@ -306,8 +284,6 @@ test("update() updates a record and maintains older version", async (context) => name: "Skip", bigAge: 100n, }); - - await userStore.teardown(); }); test("update() throws if trying to update an instance in the past", async (context) => { @@ -329,8 +305,6 @@ test("update() throws if trying to update an instance in the past", async (conte data: { name: "Peanut Butter" }, }) ).rejects.toThrow(); - - await userStore.teardown(); }); test("update() updates a record in-place within the same timestamp", async (context) => { @@ -356,8 +330,6 @@ test("update() updates a record in-place within the same timestamp", async (cont id: "id1", }); expect(updatedInstance).toMatchObject({ id: "id1", name: "Peanut Butter" }); - - await userStore.teardown(); }); test("upsert() inserts a new record", async (context) => { @@ -373,8 +345,6 @@ test("upsert() inserts a new record", async (context) => { const instance = await userStore.findUnique({ modelName: "Pet", id: "id1" }); expect(instance).toMatchObject({ id: "id1", name: "Skip", age: 12 }); - - await userStore.teardown(); }); test("upsert() updates a record", async (context) => { @@ -403,8 +373,6 @@ test("upsert() updates a record", async (context) => { id: "id1", }); expect(updatedInstance).toMatchObject({ id: "id1", name: "Jelly", age: 12 }); - - await userStore.teardown(); }); test("upsert() updates a record using an update function", async (context) => { @@ -435,8 +403,6 @@ test("upsert() updates a record using an update function", async (context) => { id: "id1", }); expect(updatedInstance).toMatchObject({ id: "id1", name: "Skip", age: 7 }); - - await userStore.teardown(); }); test("upsert() throws if trying to update an instance in the past", async (context) => { @@ -459,8 +425,6 @@ test("upsert() throws if trying to update an instance in the past", async (conte update: { name: "Peanut Butter" }, }) ).rejects.toThrow(); - - await userStore.teardown(); }); test("upsert() updates a record in-place within the same timestamp", async (context) => { @@ -487,8 +451,6 @@ test("upsert() updates a record in-place within the same timestamp", async (cont id: "id1", }); expect(updatedInstance).toMatchObject({ id: "id1", name: "Peanut Butter" }); - - await userStore.teardown(); }); test("delete() removes a record", async (context) => { @@ -511,8 +473,6 @@ test("delete() removes a record", async (context) => { id: "id1", }); expect(deletedInstance).toBe(null); - - await userStore.teardown(); }); test("delete() retains older version of record", async (context) => { @@ -534,8 +494,6 @@ test("delete() retains older version of record", async (context) => { id: "id1", }); expect(deletedInstance).toMatchObject({ id: "id1", name: "Skip", age: 12 }); - - await userStore.teardown(); }); test("delete() removes a record entirely if only present for one timestamp", async (context) => { @@ -559,8 +517,6 @@ test("delete() removes a record entirely if only present for one timestamp", asy id: "id1", }); expect(deletedInstance).toBe(null); - - await userStore.teardown(); }); test("delete() removes a record entirely if only present for one timestamp after update()", async (context) => { @@ -600,8 +556,6 @@ test("delete() removes a record entirely if only present for one timestamp after id: "id1", }); expect(deletedInstance).toBe(null); - - await userStore.teardown(); }); test("delete() deletes versions effective in the delete timestamp", async (context) => { @@ -630,8 +584,6 @@ test("delete() deletes versions effective in the delete timestamp", async (conte id: "id1", }); expect(instancePriorToDelete!.name).toBe("Skip"); - - await userStore.teardown(); }); test("findMany() returns current versions of all records", async (context) => { @@ -670,8 +622,6 @@ test("findMany() returns current versions of all records", async (context) => { "Foo", "Bar", ]); - - await userStore.teardown(); }); test("findMany() sorts on bigint field", async (context) => { @@ -708,8 +658,6 @@ test("findMany() sorts on bigint field", async (context) => { orderBy: { bigAge: "asc" }, }); expect(instances.map((i) => i.bigAge)).toMatchObject([null, 10n, 105n, 190n]); - - await userStore.teardown(); }); test("findMany() filters on bigint gt", async (context) => { @@ -747,8 +695,6 @@ test("findMany() filters on bigint gt", async (context) => { }); expect(instances.map((i) => i.bigAge)).toMatchObject([105n, 190n]); - - await userStore.teardown(); }); test("findMany() sorts and filters together", async (context) => { @@ -787,8 +733,6 @@ test("findMany() sorts and filters together", async (context) => { }); expect(instances.map((i) => i.name)).toMatchObject(["Bar", "Zarbar"]); - - await userStore.teardown(); }); test("findMany() errors on invalid filter condition", async (context) => { @@ -801,8 +745,6 @@ test("findMany() errors on invalid filter condition", async (context) => { where: { name: { invalidWhereCondition: "ar" } }, }) ).rejects.toThrow("Invalid filter condition name: invalidWhereCondition"); - - await userStore.teardown(); }); test("findMany() errors on orderBy object with multiple keys", async (context) => { @@ -815,8 +757,6 @@ test("findMany() errors on orderBy object with multiple keys", async (context) = orderBy: { name: "asc", bigAge: "desc" }, }) ).rejects.toThrow("Invalid sort condition: Must have exactly one property"); - - await userStore.teardown(); }); test("createMany() inserts multiple entities", async (context) => { @@ -836,8 +776,6 @@ test("createMany() inserts multiple entities", async (context) => { const instances = await userStore.findMany({ modelName: "Pet" }); expect(instances.length).toBe(3); - - await userStore.teardown(); }); test("createMany() inserts a large number of entities", async (context) => { @@ -859,8 +797,6 @@ test("createMany() inserts a large number of entities", async (context) => { const instances = await userStore.findMany({ modelName: "Pet" }); expect(instances.length).toBe(ENTITY_COUNT); - - await userStore.teardown(); }); test("updateMany() updates multiple entities", async (context) => { @@ -889,8 +825,6 @@ test("updateMany() updates multiple entities", async (context) => { const instances = await userStore.findMany({ modelName: "Pet" }); expect(instances.map((i) => i.bigAge)).toMatchObject([10n, 300n, 300n]); - - await userStore.teardown(); }); test("revert() deletes versions newer than the safe timestamp", async (context) => { @@ -937,8 +871,6 @@ test("revert() deletes versions newer than the safe timestamp", async (context) const persons = await userStore.findMany({ modelName: "Person" }); expect(persons.length).toBe(1); expect(persons[0].name).toBe("Bobby"); - - await userStore.teardown(); }); test("revert() updates versions that only existed during the safe timestamp to latest", async (context) => { @@ -962,6 +894,4 @@ test("revert() updates versions that only existed during the safe timestamp to l const pets = await userStore.findMany({ modelName: "Pet" }); expect(pets.length).toBe(1); expect(pets[0].name).toBe("Skip"); - - await userStore.teardown(); }); diff --git a/packages/core/src/user-store/store.ts b/packages/core/src/user-store/store.ts index 85eaac974..c83361e1a 100644 --- a/packages/core/src/user-store/store.ts +++ b/packages/core/src/user-store/store.ts @@ -78,7 +78,7 @@ export interface UserStore { versionId?: string; reload(options?: { schema?: Schema }): Promise; - teardown(): Promise; + kill(): Promise; revert(options: { safeTimestamp: number }): Promise; From d89abd534828ad7bc29bfd4042d4887381124e1d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:59:00 -0400 Subject: [PATCH 20/44] chore: version packages (#397) Co-authored-by: github-actions[bot] --- .changeset/large-buckets-flash.md | 7 ------- .changeset/loud-days-perform.md | 5 ----- packages/core/CHANGELOG.md | 10 ++++++++++ packages/core/package.json | 2 +- packages/create-ponder/CHANGELOG.md | 2 ++ packages/create-ponder/package.json | 2 +- packages/eslint-config-ponder/CHANGELOG.md | 2 ++ packages/eslint-config-ponder/package.json | 2 +- 8 files changed, 17 insertions(+), 15 deletions(-) delete mode 100644 .changeset/large-buckets-flash.md delete mode 100644 .changeset/loud-days-perform.md diff --git a/.changeset/large-buckets-flash.md b/.changeset/large-buckets-flash.md deleted file mode 100644 index a580e6549..000000000 --- a/.changeset/large-buckets-flash.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@ponder/core": patch ---- - -BREAKING: This release includes a major update to Ponder's sync engine. Upgrading to this version will delete all cached sync progress and you will need to re-sync your app from scratch. If you're running a large Ponder app in production, please test this version on a branch + separate environment before upgrading on main. - -Added support for factory contracts. Please see the [documentation](https://ponder.sh/docs/contracts#factory-contracts) for a complete guide & API reference. diff --git a/.changeset/loud-days-perform.md b/.changeset/loud-days-perform.md deleted file mode 100644 index 6ea1d31db..000000000 --- a/.changeset/loud-days-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ponder/core": patch ---- - -Fixed a bug where stale tables were left in the database after the service was stopped. diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 124698823..4e2a049a1 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,15 @@ # @ponder/core +## 0.0.94 + +### Patch Changes + +- [#361](https://github.com/0xOlias/ponder/pull/361) [`54bbd92`](https://github.com/0xOlias/ponder/commit/54bbd92ddfde8a17c45c244f1e0e6cf0000e4e9b) Thanks [@0xOlias](https://github.com/0xOlias)! - BREAKING: This release includes a major update to Ponder's sync engine. Upgrading to this version will delete all cached sync progress and you will need to re-sync your app from scratch. If you're running a large Ponder app in production, please test this version on a branch + separate environment before upgrading on main. + + Added support for factory contracts. Please see the [documentation](https://ponder.sh/docs/contracts#factory-contracts) for a complete guide & API reference. + +- [#405](https://github.com/0xOlias/ponder/pull/405) [`fb3a2a8`](https://github.com/0xOlias/ponder/commit/fb3a2a85225f8f1a03f90b6a66865c351530e6de) Thanks [@0xOlias](https://github.com/0xOlias)! - Fixed a bug where stale tables were left in the database after the service was stopped. + ## 0.0.93 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index d33ca8aee..f1decdd07 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ponder/core", - "version": "0.0.93", + "version": "0.0.94", "description": "An open-source framework for crypto application backends", "license": "MIT", "repository": { diff --git a/packages/create-ponder/CHANGELOG.md b/packages/create-ponder/CHANGELOG.md index 78afc66bc..5afadd20c 100644 --- a/packages/create-ponder/CHANGELOG.md +++ b/packages/create-ponder/CHANGELOG.md @@ -1,5 +1,7 @@ # create-ponder +## 0.0.94 + ## 0.0.93 ## 0.0.92 diff --git a/packages/create-ponder/package.json b/packages/create-ponder/package.json index 8d28e2b32..89752c394 100644 --- a/packages/create-ponder/package.json +++ b/packages/create-ponder/package.json @@ -1,6 +1,6 @@ { "name": "create-ponder", - "version": "0.0.93", + "version": "0.0.94", "description": "A CLI tool to create Ponder apps", "license": "MIT", "repository": { diff --git a/packages/eslint-config-ponder/CHANGELOG.md b/packages/eslint-config-ponder/CHANGELOG.md index 268352f7d..5f9e83733 100644 --- a/packages/eslint-config-ponder/CHANGELOG.md +++ b/packages/eslint-config-ponder/CHANGELOG.md @@ -1,5 +1,7 @@ # eslint-config-ponder +## 0.0.94 + ## 0.0.93 ## 0.0.92 diff --git a/packages/eslint-config-ponder/package.json b/packages/eslint-config-ponder/package.json index d2bd5a93f..99bdd3e37 100644 --- a/packages/eslint-config-ponder/package.json +++ b/packages/eslint-config-ponder/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-ponder", - "version": "0.0.93", + "version": "0.0.94", "description": "ESLint config for Ponder apps", "license": "MIT", "main": "./index.js", From 2f74dd3a56816def4279078f9fd54ac7d0bc0486 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Fri, 3 Nov 2023 18:44:14 -0400 Subject: [PATCH 21/44] event type inference working --- packages/core/src/config/config.test-d.ts | 18 ++++++++++++++++++ packages/core/src/config/config.test.ts | 2 +- packages/core/src/config/config.ts | 23 ++++++++++++++--------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index b9b91536d..f70d34cc0 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -4,6 +4,7 @@ import { ContractFilter, FilterElement, FilterEvents, + Kevin, SafeEventNames, } from "./config"; @@ -121,3 +122,20 @@ test("infer event names from abi", () => { assertType([] as readonly ("Approve" | "Transfer")[] | undefined); }); + +test("kevin", () => { + const a = [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiSimple, + event: ["Approve"], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ] as const; + type t = Kevin[0]["event"]; + // ^? +}); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 3a655b73b..f4b3bad8b 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -33,7 +33,7 @@ test("createConfig() has strict events inferred from abi", () => { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], abi: abiSimple, - event: ["Transfer", "Approve"], + event: ["Approve"], address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 4b1edd08d..f6549c07a 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -129,7 +129,19 @@ type Network = { type Contract< TNetworkNames extends string | unknown = string | unknown, TAbi extends Abi | unknown = Abi | unknown -> = ContractRequired & ContractFilter; +> = ContractRequired & ContractFilter; + +export type Kevin< + TContracts extends readonly Omit[], + TNetworkNames extends string | unknown = string | unknown +> = TContracts extends readonly [ + infer First, + ...infer Rest extends readonly Contract[] +] + ? First extends { abi: infer _abi } + ? readonly [Contract, ...Kevin] + : Kevin + : []; type Option = { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ @@ -158,7 +170,7 @@ export const createConfig = < database?: Database; networks: readonly Network[]; contracts: { - [key in keyof TConfig["contracts"]]: Contract< + [key in keyof TConfig["contracts"] & number]: Contract< TConfig["networks"][number]["name"], TConfig["contracts"][key]["abi"] >; @@ -172,10 +184,3 @@ export const createConfig = < | (() => TConfig) | (() => Promise) ) => config; - -// contracts: { -// [key in keyof TConfig["contracts"] & number]: Contract< -// TConfig["networks"][number]["name"], -// TConfig["contracts"][key]["abi"] -// >; -// }; From d809832fad80f26caa87f5c9c7f5a2b933978931 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 12:34:35 -0400 Subject: [PATCH 22/44] cleanup --- packages/core/src/config/config.test-d.ts | 18 ------------------ packages/core/src/config/config.test.ts | 9 ++++++--- packages/core/src/config/config.ts | 21 ++++----------------- packages/core/src/config/sources.ts | 14 ++++++++------ 4 files changed, 18 insertions(+), 44 deletions(-) diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index f70d34cc0..b9b91536d 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -4,7 +4,6 @@ import { ContractFilter, FilterElement, FilterEvents, - Kevin, SafeEventNames, } from "./config"; @@ -122,20 +121,3 @@ test("infer event names from abi", () => { assertType([] as readonly ("Approve" | "Transfer")[] | undefined); }); - -test("kevin", () => { - const a = [ - { - name: "BaseRegistrarImplementation", - network: [{ name: "mainnet" }], - abi: abiSimple, - event: ["Approve"], - address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - startBlock: 16370000, - endBlock: 16370020, - maxBlockRange: 10, - }, - ] as const; - type t = Kevin[0]["event"]; - // ^? -}); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index f4b3bad8b..c12f9b26a 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -2,7 +2,7 @@ import { http } from "viem"; import { test } from "vitest"; import { createConfig } from "./config"; -import { abiSimple } from "./config.test-d"; +import { abiWithSameEvent } from "./config.test-d"; test("createConfig enforces matching network names", () => { createConfig({ @@ -32,8 +32,11 @@ test("createConfig() has strict events inferred from abi", () => { { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], - abi: abiSimple, - event: ["Approve"], + abi: abiWithSameEvent, + event: [ + "Transfer", + "Approve(address indexed, bytes32 indexed, uint256)", + ], address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index f6549c07a..6b0b6fab3 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -82,10 +82,9 @@ export type ContractFilter = ( /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ maxBlockRange?: number; - event?: readonly SafeEventNames< - FilterEvents, - FilterEvents - >[number][]; + event?: Abi extends TAbi + ? string[] + : readonly SafeEventNames, FilterEvents>[number][]; }; type Database = @@ -131,18 +130,6 @@ type Contract< TAbi extends Abi | unknown = Abi | unknown > = ContractRequired & ContractFilter; -export type Kevin< - TContracts extends readonly Omit[], - TNetworkNames extends string | unknown = string | unknown -> = TContracts extends readonly [ - infer First, - ...infer Rest extends readonly Contract[] -] - ? First extends { abi: infer _abi } - ? readonly [Contract, ...Kevin] - : Kevin - : []; - type Option = { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ maxHealthcheckDuration?: number; @@ -150,7 +137,7 @@ type Option = { export type ResolvedConfig< TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi = Abi + TAbi extends Abi | unknown = Abi | unknown > = { /** Database to use for storing blockchain & entity data. Default: `"postgres"` if `DATABASE_URL` env var is present, otherwise `"sqlite"`. */ database?: Database; diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 7e59e39f6..b27476bd0 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -137,17 +137,19 @@ const buildTopics = ( .map((event) => encodeEventTopics({ abi: [event], - eventName: event.name, + eventName: event, }) ) .flat(), ]; } else { + // TODO:KYLE handle this once events get more complex + return []; // Single event with args - return encodeEventTopics({ - abi: [events.signature], - eventName: events.signature.name, - args: events.args, - }); + // return encodeEventTopics({ + // abi: [events.signature], + // eventName: events.signature.name, + // args: events.args, + // }); } }; From d53f66156b7d9175142fc33a78464be72f7eee63 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 12:39:33 -0400 Subject: [PATCH 23/44] change config event shape --- packages/core/src/config/config.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 6b0b6fab3..c9803ba3b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -82,9 +82,19 @@ export type ContractFilter = ( /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ maxBlockRange?: number; - event?: Abi extends TAbi - ? string[] - : readonly SafeEventNames, FilterEvents>[number][]; + filter?: Abi extends TAbi + ? string[] | { event: string } + : + | readonly SafeEventNames< + FilterEvents, + FilterEvents + >[number][] + | { + event: SafeEventNames< + FilterEvents, + FilterEvents + >[number]; + }; }; type Database = From 25b686aa8b0ce09ec0f6c04649b89d4a8ca847ba Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 14:24:35 -0400 Subject: [PATCH 24/44] inferring event args not working --- packages/core/src/config/config.test-d.ts | 34 +++++++----- packages/core/src/config/config.test.ts | 27 +++++++++- packages/core/src/config/config.ts | 66 ++++++++++++++++++----- packages/core/src/config/sources.test.ts | 7 +++ packages/core/src/config/sources.ts | 6 ++- 5 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 packages/core/src/config/sources.test.ts diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index b9b91536d..777529355 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -1,9 +1,9 @@ import { assertType, test } from "vitest"; import { - ContractFilter, - FilterElement, FilterEvents, + RecoverAbiEvent, + ResolvedConfig, SafeEventNames, } from "./config"; @@ -18,14 +18,17 @@ export const abiSimple = [ { indexed: true, type: "address", + name: "from", }, { indexed: true, type: "address", + name: "to", }, { indexed: false, type: "uint256", + name: "amount", }, ], name: "Approve", @@ -89,12 +92,6 @@ test("filter events", () => { ] as const); }); -test("filter elements", () => { - type a = FilterElement<"a", readonly ["a", "b", "c"]>; - // ^? - assertType(["b", "c"] as const); -}); - test("safe event names", () => { type a = SafeEventNames< // ^? @@ -109,15 +106,28 @@ test("safe event names", () => { FilterEvents >; assertType([ - "Approve(address indexed, address indexed, uint256)", + "Approve(address indexed from, address indexed to, uint256 amount)", "Transfer", "Approve(address indexed, bytes32 indexed, uint256)", ]); }); -test("infer event names from abi", () => { - type a = ContractFilter["event"]; +test("ResolvedConfig default values", () => { + type a = NonNullable[number]["filter"]; // ^? + assertType({} as string[] | { event: string } | undefined); +}); + +test("RecoverAbiEvent", () => { + type a = RecoverAbiEvent< + // ^? + FilterEvents, + SafeEventNames< + FilterEvents, + FilterEvents + >, + "Approve" + >; - assertType([] as readonly ("Approve" | "Transfer")[] | undefined); + assertType(abiSimple[1]); }); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index c12f9b26a..4c326d8c9 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -2,7 +2,7 @@ import { http } from "viem"; import { test } from "vitest"; import { createConfig } from "./config"; -import { abiWithSameEvent } from "./config.test-d"; +import { abiSimple, abiWithSameEvent } from "./config.test-d"; test("createConfig enforces matching network names", () => { createConfig({ @@ -33,7 +33,7 @@ test("createConfig() has strict events inferred from abi", () => { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], abi: abiWithSameEvent, - event: [ + filter: [ "Transfer", "Approve(address indexed, bytes32 indexed, uint256)", ], @@ -45,3 +45,26 @@ test("createConfig() has strict events inferred from abi", () => { ], }); }); + +test("createConfig() has strict arg types for event", () => { + createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiSimple, + filter: { + event: "Approve", + args: {}, + }, + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index c9803ba3b..8a6059373 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1,5 +1,5 @@ import type { Abi, AbiEvent, FormatAbiItem } from "abitype"; -import type { Transport } from "viem"; +import type { GetEventArgs, Transport } from "viem"; /** * Keep only AbiEvents from an Abi @@ -16,7 +16,7 @@ export type FilterEvents = T extends readonly [ /** * Remove TElement from TArr */ -export type FilterElement< +type FilterElement< TElement, TArr extends readonly unknown[] | unknown > = TArr extends readonly [infer First, ...infer Rest] @@ -44,9 +44,28 @@ export type SafeEventNames< readonly [First["name"], ...SafeEventNames] : []; +export type RecoverAbiEvent< + TAbi extends readonly AbiEvent[] | unknown, + TSafeNames extends readonly string[], + TSafeName extends string +> = TAbi extends readonly [ + infer FirstAbi, + ...infer RestAbi extends readonly AbiEvent[] +] + ? TSafeNames extends readonly [ + infer FirstName, + ...infer RestName extends readonly string[] + ] + ? FirstName extends TSafeName + ? FirstAbi + : RecoverAbiEvent + : [] + : []; + type ContractRequired< TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi | unknown = Abi | unknown + TAbi extends Abi | unknown = Abi | unknown, + TEventName extends string = string > = { /** Contract name. Must be unique across `contracts` and `filters`. */ name: string; @@ -54,11 +73,13 @@ type ContractRequired< * Network that this contract is deployed to. Must match a network name in `networks`. * Any filter information overrides the values in the higher level "contracts" property. Factories cannot override an address and vice versa. */ - network: readonly ({ name: TNetworkNames } & Partial>)[]; + network: readonly ({ name: TNetworkNames } & Partial< + ContractFilter + >)[]; abi: Abi; }; -export type ContractFilter = ( +type ContractFilter = ( | { /** Contract address. */ address?: `0x${string}`; @@ -94,6 +115,22 @@ export type ContractFilter = ( FilterEvents, FilterEvents >[number]; + args: GetEventArgs< + Abi, + string, + { + EnableUnion: true; + IndexedOnly: true; + Required: false; + }, + RecoverAbiEvent< + TAbi, + SafeEventNames, FilterEvents>, + TEventName + > extends infer _abiEvent extends AbiEvent + ? _abiEvent + : AbiEvent + >; }; }; @@ -137,24 +174,22 @@ type Network = { type Contract< TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi | unknown = Abi | unknown -> = ContractRequired & ContractFilter; + TAbi extends Abi | unknown = Abi | unknown, + TEventName extends string = string +> = ContractRequired & ContractFilter; type Option = { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ maxHealthcheckDuration?: number; }; -export type ResolvedConfig< - TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi | unknown = Abi | unknown -> = { +export type ResolvedConfig = { /** Database to use for storing blockchain & entity data. Default: `"postgres"` if `DATABASE_URL` env var is present, otherwise `"sqlite"`. */ database?: Database; /** List of blockchain networks. */ networks: readonly Network[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: readonly Contract[]; + contracts?: readonly Contract[]; /** Configuration for Ponder internals. */ options?: Option; }; @@ -169,7 +204,12 @@ export const createConfig = < contracts: { [key in keyof TConfig["contracts"] & number]: Contract< TConfig["networks"][number]["name"], - TConfig["contracts"][key]["abi"] + TConfig["contracts"][key]["abi"], + TConfig["contracts"][key]["filter"] extends { + event: infer _event extends string; + } + ? _event + : string >; }; options?: Option; diff --git a/packages/core/src/config/sources.test.ts b/packages/core/src/config/sources.test.ts new file mode 100644 index 000000000..63b4cb693 --- /dev/null +++ b/packages/core/src/config/sources.test.ts @@ -0,0 +1,7 @@ +import { test } from "vitest"; + +test.todo("buildSources() builds topics for multiple events"); + +test.todo("buildSources() builds topics for event with args"); + +test.todo("buildSources() overrides default values with network values"); diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index b27476bd0..318564963 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -76,7 +76,7 @@ export const buildSources = ({ (n) => n.name === networkContract.name )!; - const resolvedEvents = networkContract.event ?? contract.event; + const resolvedEvents = networkContract.filter ?? contract.filter; const topics = resolvedEvents ? buildTopics(resolvedEvents) @@ -128,7 +128,9 @@ export const buildSources = ({ }; const buildTopics = ( - events: NonNullable[number]["event"]> + events: NonNullable< + NonNullable[number]["filter"] + > ): Topics => { if (Array.isArray(events)) { // List of event signatures From 435f567cb947cbcdc03a3c04d7c00fc03e5fc5ea Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 14:33:19 -0400 Subject: [PATCH 25/44] fixed event arg type inference --- packages/core/src/config/config.test.ts | 6 ++-- packages/core/src/config/config.ts | 40 ++++++++++++++----------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 4c326d8c9..ae5d7c1e7 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -35,7 +35,7 @@ test("createConfig() has strict events inferred from abi", () => { abi: abiWithSameEvent, filter: [ "Transfer", - "Approve(address indexed, bytes32 indexed, uint256)", + "Approve(address indexed from, address indexed to, uint256 amount)", ], address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, @@ -58,7 +58,9 @@ test("createConfig() has strict arg types for event", () => { abi: abiSimple, filter: { event: "Approve", - args: {}, + args: { + to: ["0x"], + }, }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 8a6059373..2be6c0643 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -4,7 +4,7 @@ import type { GetEventArgs, Transport } from "viem"; /** * Keep only AbiEvents from an Abi */ -export type FilterEvents = T extends readonly [ +export type FilterEvents = T extends readonly [ infer First, ...infer Rest extends Abi ] @@ -18,7 +18,7 @@ export type FilterEvents = T extends readonly [ */ type FilterElement< TElement, - TArr extends readonly unknown[] | unknown + TArr extends readonly unknown[] > = TArr extends readonly [infer First, ...infer Rest] ? TElement extends First ? FilterElement @@ -29,8 +29,8 @@ type FilterElement< * Return an array of safe event names that handle multiple events with the same name */ export type SafeEventNames< - TAbi extends readonly AbiEvent[] | unknown, - TArr extends readonly AbiEvent[] | unknown + TAbi extends readonly AbiEvent[], + TArr extends readonly AbiEvent[] > = TAbi extends readonly [ infer First extends AbiEvent, ...infer Rest extends readonly AbiEvent[] @@ -45,7 +45,7 @@ export type SafeEventNames< : []; export type RecoverAbiEvent< - TAbi extends readonly AbiEvent[] | unknown, + TAbi extends readonly AbiEvent[], TSafeNames extends readonly string[], TSafeName extends string > = TAbi extends readonly [ @@ -63,9 +63,9 @@ export type RecoverAbiEvent< : []; type ContractRequired< - TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi | unknown = Abi | unknown, - TEventName extends string = string + TNetworkNames extends string, + TAbi extends readonly AbiEvent[], + TEventName extends string > = { /** Contract name. Must be unique across `contracts` and `filters`. */ name: string; @@ -79,7 +79,10 @@ type ContractRequired< abi: Abi; }; -type ContractFilter = ( +type ContractFilter< + TAbi extends readonly AbiEvent[], + TEventName extends string +> = ( | { /** Contract address. */ address?: `0x${string}`; @@ -103,8 +106,8 @@ type ContractFilter = ( /** Maximum block range to use when calling `eth_getLogs`. Default: `10_000`. */ maxBlockRange?: number; - filter?: Abi extends TAbi - ? string[] | { event: string } + filter?: readonly AbiEvent[] extends TAbi + ? string[] | { event: string; args?: unknown } : | readonly SafeEventNames< FilterEvents, @@ -115,7 +118,7 @@ type ContractFilter = ( FilterEvents, FilterEvents >[number]; - args: GetEventArgs< + args?: GetEventArgs< Abi, string, { @@ -173,10 +176,11 @@ type Network = { }; type Contract< - TNetworkNames extends string | unknown = string | unknown, - TAbi extends Abi | unknown = Abi | unknown, - TEventName extends string = string -> = ContractRequired & ContractFilter; + TNetworkNames extends string, + TAbi extends readonly AbiEvent[], + TEventName extends string +> = ContractRequired & + ContractFilter; type Option = { /** Maximum number of seconds to wait for event processing to be complete before responding as healthy. If event processing exceeds this duration, the API may serve incomplete data. Default: `240` (4 minutes). */ @@ -189,7 +193,7 @@ export type ResolvedConfig = { /** List of blockchain networks. */ networks: readonly Network[]; /** List of contracts to sync & index events from. Contracts defined here will be present in `context.contracts`. */ - contracts?: readonly Contract[]; + contracts?: readonly Contract[]; /** Configuration for Ponder internals. */ options?: Option; }; @@ -204,7 +208,7 @@ export const createConfig = < contracts: { [key in keyof TConfig["contracts"] & number]: Contract< TConfig["networks"][number]["name"], - TConfig["contracts"][key]["abi"], + FilterEvents, TConfig["contracts"][key]["filter"] extends { event: infer _event extends string; } From a0d12b9f2fa2369cbd985c4dfec10800bc7ff42c Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 14:40:18 -0400 Subject: [PATCH 26/44] test override types --- packages/core/src/config/config.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index ae5d7c1e7..843967fa3 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -54,12 +54,18 @@ test("createConfig() has strict arg types for event", () => { contracts: [ { name: "BaseRegistrarImplementation", - network: [{ name: "mainnet" }], + network: [ + { + name: "mainnet", + address: "0x", + filter: { event: "Approve", args: { from: "0x" } }, + }, + ], abi: abiSimple, filter: { event: "Approve", args: { - to: ["0x"], + to: ["0x1", "0x2"], }, }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", From a7948aa270e4d4f9b43ef4fa0821b9412886a576 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 15:39:15 -0400 Subject: [PATCH 27/44] fix topic generation and test --- packages/core/src/Ponder.ts | 2 +- packages/core/src/config/config.ts | 2 +- packages/core/src/config/sources.test.ts | 148 ++++++++++++++++++++++- packages/core/src/config/sources.ts | 56 ++++++--- 4 files changed, 183 insertions(+), 25 deletions(-) diff --git a/packages/core/src/Ponder.ts b/packages/core/src/Ponder.ts index cd4977b4e..0a9d9d9c8 100644 --- a/packages/core/src/Ponder.ts +++ b/packages/core/src/Ponder.ts @@ -105,7 +105,7 @@ export class Ponder { buildNetwork({ network, common }) ); - const sources = buildSources({ options, config }); + const sources = buildSources({ config }); this.sources = sources; const networksToSync = config.networks diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 2be6c0643..6b3a14dc9 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -107,7 +107,7 @@ type ContractFilter< maxBlockRange?: number; filter?: readonly AbiEvent[] extends TAbi - ? string[] | { event: string; args?: unknown } + ? string[] | { event: string; args?: GetEventArgs } : | readonly SafeEventNames< FilterEvents, diff --git a/packages/core/src/config/sources.test.ts b/packages/core/src/config/sources.test.ts index 63b4cb693..a032c30c1 100644 --- a/packages/core/src/config/sources.test.ts +++ b/packages/core/src/config/sources.test.ts @@ -1,7 +1,147 @@ -import { test } from "vitest"; +import { http } from "viem"; +import { expect, test } from "vitest"; -test.todo("buildSources() builds topics for multiple events"); +import { createConfig, ResolvedConfig } from "./config"; +import { abiSimple, abiWithSameEvent } from "./config.test-d"; +import { buildSources } from "./sources"; -test.todo("buildSources() builds topics for event with args"); +test("buildSources() builds topics for multiple events", () => { + const sources = buildSources({ + config: createConfig({ + networks: [ + { + name: "mainnet", + chainId: 1, + transport: http("http://127.0.0.1:8545"), + }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiSimple, + filter: ["Transfer", "Approve"], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }) as unknown as ResolvedConfig, + }); -test.todo("buildSources() overrides default values with network values"); + expect(sources[0].criteria.topics).toMatchObject([ + [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x6e11fb1b7f119e3f2fa29896ef5fdf8b8a2d0d4df6fe90ba8668e7d8b2ffa25e", + ], + ]); +}); + +test("buildSources() for duplicate event", () => { + const sources = buildSources({ + config: createConfig({ + networks: [ + { + name: "mainnet", + chainId: 1, + transport: http("http://127.0.0.1:8545"), + }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiWithSameEvent, + filter: [ + "Approve(address indexed from, address indexed to, uint256 amount)", + "Approve(address indexed, bytes32 indexed, uint256)", + ], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }) as unknown as ResolvedConfig, + }); + + expect(sources[0].criteria.topics).toMatchObject([ + [ + "0x6e11fb1b7f119e3f2fa29896ef5fdf8b8a2d0d4df6fe90ba8668e7d8b2ffa25e", + "0xdbb5081f3bcbc60be144528482d176e8141b95ebe19a2ab38100455dc726eaa6", + ], + ]); +}); + +test("buildSources() builds topics for event with args", () => { + const sources = buildSources({ + config: createConfig({ + networks: [ + { + name: "mainnet", + chainId: 1, + transport: http("http://127.0.0.1:8545"), + }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiSimple, + filter: { + event: "Approve", + args: { + to: "0xF39d15cB3910d5e33fb1a2E42D4a2da153Ba076B", + }, + }, + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }) as unknown as ResolvedConfig, + }); + + expect(sources[0].criteria.topics).toMatchObject([ + "0x6e11fb1b7f119e3f2fa29896ef5fdf8b8a2d0d4df6fe90ba8668e7d8b2ffa25e", + null, + "0x000000000000000000000000f39d15cb3910d5e33fb1a2e42d4a2da153ba076b", + ]); +}); + +test("buildSources() overrides default values with network values", () => { + const sources = buildSources({ + config: createConfig({ + networks: [ + { + name: "mainnet", + chainId: 1, + transport: http("http://127.0.0.1:8545"), + }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [ + { + name: "mainnet", + address: "0xF39d15cB3910d5e33fb1a2E42D4a2da153Ba076B", + }, + ], + abi: abiSimple, + filter: ["Transfer"], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }) as unknown as ResolvedConfig, + }); + + expect(sources[0].criteria.address).toBe( + "0xf39d15cb3910d5e33fb1a2e42d4a2da153ba076b" + ); +}); diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 318564963..fd3787e86 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -1,16 +1,16 @@ -import { Abi, Address, encodeEventTopics, Hex } from "viem"; +import { AbiEvent, parseAbiItem } from "abitype"; +import { Abi, Address, encodeEventTopics, getAbiItem, Hex } from "viem"; import { toLowerCase } from "@/utils/lowercase"; import { AbiEvents, getEvents } from "./abi"; import { ResolvedConfig } from "./config"; import { buildFactoryCriteria } from "./factories"; -import { Options } from "./options"; /** * There are up to 4 topics in an EVM event * - * Technically, only the first element could be an array + * @todo Change this to a more strict type */ export type Topics = (Hex | Hex[] | null)[]; @@ -59,7 +59,6 @@ export const buildSources = ({ config, }: { config: ResolvedConfig; - options: Options; }): Source[] => { const contracts = config.contracts ?? []; @@ -76,10 +75,10 @@ export const buildSources = ({ (n) => n.name === networkContract.name )!; - const resolvedEvents = networkContract.filter ?? contract.filter; + const resolvedFilter = networkContract.filter ?? contract.filter; - const topics = resolvedEvents - ? buildTopics(resolvedEvents) + const topics = resolvedFilter + ? buildTopics(contract.abi, resolvedFilter) : undefined; const sharedSource = { @@ -99,23 +98,31 @@ export const buildSources = ({ if ("factory" in contract) { // factory + const resolvedFactory = + ("factory" in networkContract && networkContract.factory) || + contract.factory; + return { ...sharedSource, type: "factory", criteria: { - ...buildFactoryCriteria(contract.factory), + ...buildFactoryCriteria(resolvedFactory), topics, }, } as const satisfies Factory; } else { // log filter + const resolvedAddress = + ("address" in networkContract && networkContract.address) || + contract.address; + return { ...sharedSource, type: "logFilter", criteria: { - address: contract.address - ? toLowerCase(contract.address) + address: resolvedAddress + ? toLowerCase(resolvedAddress) : undefined, topics, }, @@ -128,6 +135,7 @@ export const buildSources = ({ }; const buildTopics = ( + abi: Abi, events: NonNullable< NonNullable[number]["filter"] > @@ -138,20 +146,30 @@ const buildTopics = ( events .map((event) => encodeEventTopics({ - abi: [event], - eventName: event, + abi: [findAbiEvent(abi, event)], }) ) .flat(), ]; } else { - // TODO:KYLE handle this once events get more complex - return []; // Single event with args - // return encodeEventTopics({ - // abi: [events.signature], - // eventName: events.signature.name, - // args: events.args, - // }); + return encodeEventTopics({ + abi: [findAbiEvent(abi, events.event)], + args: events.args, + }); + } +}; + +/** + * Finds the abi event for the event string + * + * @param eventName Event name or event signature if there are collisions + */ +const findAbiEvent = (abi: Abi, eventName: string): AbiEvent => { + if (eventName.includes("(")) { + // Collision + return parseAbiItem(`event ${eventName}`) as AbiEvent; + } else { + return getAbiItem({ abi, name: eventName }) as AbiEvent; } }; From 80b1813fc29d6d1cc277f3435983417687589123 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 15:41:20 -0400 Subject: [PATCH 28/44] docker example --- .../with-docker/abis/AdventureGold.abi.ts | 349 +++++++++++++++++ examples/with-docker/abis/AdventureGold.json | 357 ------------------ examples/with-docker/ponder.config.ts | 12 +- 3 files changed, 356 insertions(+), 362 deletions(-) create mode 100644 examples/with-docker/abis/AdventureGold.abi.ts delete mode 100644 examples/with-docker/abis/AdventureGold.json diff --git a/examples/with-docker/abis/AdventureGold.abi.ts b/examples/with-docker/abis/AdventureGold.abi.ts new file mode 100644 index 000000000..4d21f2474 --- /dev/null +++ b/examples/with-docker/abis/AdventureGold.abi.ts @@ -0,0 +1,349 @@ +export const AdventureGoldAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [], + name: "adventureGoldPerTokenId", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "claimAllForOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "claimById", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "ownerIndexStart", + type: "uint256", + }, + { internalType: "uint256", name: "ownerIndexEnd", type: "uint256" }, + ], + name: "claimRangeForOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "amountDisplayValue", + type: "uint256", + }, + ], + name: "daoMint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "adventureGoldDisplayValue", + type: "uint256", + }, + ], + name: "daoSetAdventureGoldPerTokenId", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "lootContractAddress_", + type: "address", + }, + ], + name: "daoSetLootContractAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "season_", type: "uint256" }], + name: "daoSetSeason", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "season_", type: "uint256" }, + { + internalType: "uint256", + name: "adventureGoldDisplayValue", + type: "uint256", + }, + ], + name: "daoSetSeasonAndAdventureGoldPerTokenID", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "tokenIdStart_", type: "uint256" }, + { internalType: "uint256", name: "tokenIdEnd_", type: "uint256" }, + ], + name: "daoSetTokenIdRange", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { + internalType: "uint256", + name: "subtractedValue", + type: "uint256", + }, + ], + name: "decreaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "addedValue", type: "uint256" }, + ], + name: "increaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "lootContract", + outputs: [ + { + internalType: "contract IERC721Enumerable", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "lootContractAddress", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "season", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "seasonClaimedByTokenId", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tokenIdEnd", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "tokenIdStart", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/examples/with-docker/abis/AdventureGold.json b/examples/with-docker/abis/AdventureGold.json deleted file mode 100644 index 95231479d..000000000 --- a/examples/with-docker/abis/AdventureGold.json +++ /dev/null @@ -1,357 +0,0 @@ -[ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "adventureGoldPerTokenId", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "account", "type": "address" } - ], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "claimAllForOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "claimById", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ownerIndexStart", - "type": "uint256" - }, - { "internalType": "uint256", "name": "ownerIndexEnd", "type": "uint256" } - ], - "name": "claimRangeForOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amountDisplayValue", - "type": "uint256" - } - ], - "name": "daoMint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "adventureGoldDisplayValue", - "type": "uint256" - } - ], - "name": "daoSetAdventureGoldPerTokenId", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "lootContractAddress_", - "type": "address" - } - ], - "name": "daoSetLootContractAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "season_", "type": "uint256" } - ], - "name": "daoSetSeason", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "season_", "type": "uint256" }, - { - "internalType": "uint256", - "name": "adventureGoldDisplayValue", - "type": "uint256" - } - ], - "name": "daoSetSeasonAndAdventureGoldPerTokenID", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "tokenIdStart_", "type": "uint256" }, - { "internalType": "uint256", "name": "tokenIdEnd_", "type": "uint256" } - ], - "name": "daoSetTokenIdRange", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "spender", "type": "address" }, - { "internalType": "uint256", "name": "addedValue", "type": "uint256" } - ], - "name": "increaseAllowance", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "lootContract", - "outputs": [ - { - "internalType": "contract IERC721Enumerable", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lootContractAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "season", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "name": "seasonClaimedByTokenId", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tokenIdEnd", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tokenIdStart", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "address", "name": "recipient", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/examples/with-docker/ponder.config.ts b/examples/with-docker/ponder.config.ts index 21726f81e..9d54f319a 100644 --- a/examples/with-docker/ponder.config.ts +++ b/examples/with-docker/ponder.config.ts @@ -1,11 +1,13 @@ -import type { Config } from "@ponder/core"; +import { createConfig } from "@ponder/core"; import { http } from "viem"; +import { AdventureGoldAbi } from "./abis/AdventureGold.abi"; + if (!process.env.PONDER_RPC_URL_1) { throw new Error("Missing PONDER_RPC_URL_1 environment variable"); } -export const config: Config = { +export const config = createConfig({ networks: [ { name: "mainnet", @@ -16,11 +18,11 @@ export const config: Config = { contracts: [ { name: "AdventureGold", - network: "mainnet", - abi: "./abis/AdventureGold.json", + network: [{ name: "mainnet" }], + abi: AdventureGoldAbi, address: "0x32353A6C91143bfd6C7d363B546e62a9A2489A20", startBlock: 13142655, endBlock: 13150000, }, ], -}; +}); From a26b578d66591673756365bc4132e740855a2578 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sat, 4 Nov 2023 17:54:06 -0400 Subject: [PATCH 29/44] change shape of multi event data --- packages/core/src/config/config.test-d.ts | 2 +- packages/core/src/config/config.test.ts | 12 +++++++----- packages/core/src/config/config.ts | 15 ++++++++++----- packages/core/src/config/sources.test.ts | 16 +++++++++------- packages/core/src/config/sources.ts | 20 +++++++++++++++----- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index 777529355..46c973fc4 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -115,7 +115,7 @@ test("safe event names", () => { test("ResolvedConfig default values", () => { type a = NonNullable[number]["filter"]; // ^? - assertType({} as string[] | { event: string } | undefined); + assertType({} as { event: string[] } | { event: string } | undefined); }); test("RecoverAbiEvent", () => { diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 843967fa3..48bdedc52 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -33,10 +33,12 @@ test("createConfig() has strict events inferred from abi", () => { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], abi: abiWithSameEvent, - filter: [ - "Transfer", - "Approve(address indexed from, address indexed to, uint256 amount)", - ], + filter: { + event: [ + "Transfer", + "Approve(address indexed from, address indexed to, uint256 amount)", + ], + }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, @@ -58,7 +60,7 @@ test("createConfig() has strict arg types for event", () => { { name: "mainnet", address: "0x", - filter: { event: "Approve", args: { from: "0x" } }, + filter: { event: "Approve", args: { from: "0x", to: "0x" } }, }, ], abi: abiSimple, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 6b3a14dc9..e7b873679 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -107,12 +107,17 @@ type ContractFilter< maxBlockRange?: number; filter?: readonly AbiEvent[] extends TAbi - ? string[] | { event: string; args?: GetEventArgs } + ? + | { event: readonly string[]; args?: never } + | { event: string; args?: GetEventArgs } : - | readonly SafeEventNames< - FilterEvents, - FilterEvents - >[number][] + | { + event: readonly SafeEventNames< + FilterEvents, + FilterEvents + >[number][]; + args?: never; + } | { event: SafeEventNames< FilterEvents, diff --git a/packages/core/src/config/sources.test.ts b/packages/core/src/config/sources.test.ts index a032c30c1..2de42c252 100644 --- a/packages/core/src/config/sources.test.ts +++ b/packages/core/src/config/sources.test.ts @@ -20,7 +20,7 @@ test("buildSources() builds topics for multiple events", () => { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], abi: abiSimple, - filter: ["Transfer", "Approve"], + filter: { event: ["Transfer", "Approve"] }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, @@ -53,10 +53,12 @@ test("buildSources() for duplicate event", () => { name: "BaseRegistrarImplementation", network: [{ name: "mainnet" }], abi: abiWithSameEvent, - filter: [ - "Approve(address indexed from, address indexed to, uint256 amount)", - "Approve(address indexed, bytes32 indexed, uint256)", - ], + filter: { + event: [ + "Approve(address indexed from, address indexed to, uint256 amount)", + "Approve(address indexed, bytes32 indexed, uint256)", + ], + }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, @@ -74,7 +76,7 @@ test("buildSources() for duplicate event", () => { ]); }); -test("buildSources() builds topics for event with args", () => { +test.only("buildSources() builds topics for event with args", () => { const sources = buildSources({ config: createConfig({ networks: [ @@ -131,7 +133,7 @@ test("buildSources() overrides default values with network values", () => { }, ], abi: abiSimple, - filter: ["Transfer"], + filter: { event: ["Transfer"] }, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 16370000, endBlock: 16370020, diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index fd3787e86..7d20c0c8a 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -7,6 +7,16 @@ import { AbiEvents, getEvents } from "./abi"; import { ResolvedConfig } from "./config"; import { buildFactoryCriteria } from "./factories"; +/** + * Fix issue with Array.isArray not checking readonly arrays + * {@link https://github.com/microsoft/TypeScript/issues/17002} + */ +declare global { + interface ArrayConstructor { + isArray(arg: ReadonlyArray | any): arg is ReadonlyArray; + } +} + /** * There are up to 4 topics in an EVM event * @@ -136,14 +146,14 @@ export const buildSources = ({ const buildTopics = ( abi: Abi, - events: NonNullable< + filter: NonNullable< NonNullable[number]["filter"] > ): Topics => { - if (Array.isArray(events)) { + if (Array.isArray(filter.event)) { // List of event signatures return [ - events + filter.event .map((event) => encodeEventTopics({ abi: [findAbiEvent(abi, event)], @@ -154,8 +164,8 @@ const buildTopics = ( } else { // Single event with args return encodeEventTopics({ - abi: [findAbiEvent(abi, events.event)], - args: events.args, + abi: [findAbiEvent(abi, filter.event)], + args: filter.args, }); } }; From 840f1240a844d6d7756be30ec757a763a251fffb Mon Sep 17 00:00:00 2001 From: typedarray <90073088+0xOlias@users.noreply.github.com> Date: Sun, 5 Nov 2023 02:08:13 -0500 Subject: [PATCH 30/44] fix ui bug (#409) * fix ui bug * fix alchemy response size limit bug * changesets * rm log --- .changeset/kind-buttons-tickle.md | 5 +++++ .changeset/unlucky-days-search.md | 5 +++++ packages/core/src/event-aggregator/service.ts | 2 +- packages/core/src/historical-sync/service.ts | 21 ++++++++++++++++--- packages/core/src/ui/IndexingBar.tsx | 14 ++++++------- packages/core/src/ui/app.tsx | 4 ++-- packages/core/src/ui/service.ts | 4 ++-- 7 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 .changeset/kind-buttons-tickle.md create mode 100644 .changeset/unlucky-days-search.md diff --git a/.changeset/kind-buttons-tickle.md b/.changeset/kind-buttons-tickle.md new file mode 100644 index 000000000..487b9d034 --- /dev/null +++ b/.changeset/kind-buttons-tickle.md @@ -0,0 +1,5 @@ +--- +"@ponder/core": patch +--- + +Fixed a bug where the indexing progress bar had incorrect values. diff --git a/.changeset/unlucky-days-search.md b/.changeset/unlucky-days-search.md new file mode 100644 index 000000000..0d54112bc --- /dev/null +++ b/.changeset/unlucky-days-search.md @@ -0,0 +1,5 @@ +--- +"@ponder/core": patch +--- + +Fixed a bug where Alchemy "Response size is larger than 150MB limit" errors wer enot handled properly. diff --git a/packages/core/src/event-aggregator/service.ts b/packages/core/src/event-aggregator/service.ts index 97f0a9117..ed7212b3a 100644 --- a/packages/core/src/event-aggregator/service.ts +++ b/packages/core/src/event-aggregator/service.ts @@ -190,7 +190,7 @@ export class EventAggregatorService extends Emittery { // TODO: emit a warning here that a log was not decoded. this.common.logger.error({ service: "app", - msg: `Unable to decode log (skipping it): ${event.log}`, + msg: `Unable to decode log, skipping it. id: ${event.log.id}, data: ${event.log.data}, topics: ${event.log.topics}`, error: err as Error, }); } diff --git a/packages/core/src/historical-sync/service.ts b/packages/core/src/historical-sync/service.ts index caa350cd8..be59a914d 100644 --- a/packages/core/src/historical-sync/service.ts +++ b/packages/core/src/historical-sync/service.ts @@ -9,6 +9,7 @@ import { HttpRequestError, InvalidParamsRpcError, LimitExceededRpcError, + RpcError, toHex, } from "viem"; @@ -870,7 +871,7 @@ export class HistoricalSyncService extends Emittery { ) => { const logs: RpcLog[] = []; - let error: Error | null = null; + let error: RpcError | null = null; const stopClock = startClock(); try { @@ -880,7 +881,7 @@ export class HistoricalSyncService extends Emittery { }); logs.push(...logs_); } catch (err) { - error = err as Error; + error = err as RpcError; } finally { this.common.metrics.ponder_historical_rpc_request_duration.observe( { method: "eth_getLogs", network: this.network.name }, @@ -890,7 +891,7 @@ export class HistoricalSyncService extends Emittery { if (!error) return logs; - const retryRanges: [Hex, Hex][] = []; + const retryRanges: ([Hex, Hex] | readonly [Hex, Hex])[] = []; if ( // Alchemy response size error. error instanceof InvalidParamsRpcError && @@ -902,6 +903,20 @@ export class HistoricalSyncService extends Emittery { retryRanges.push([toHex(safeStart), toHex(safeEnd)]); retryRanges.push([toHex(safeEnd + 1), options.toBlock]); + } else if ( + // Another Alchemy response size error. + error.details.includes("Response size is larger than 150MB limit") + ) { + // No hint available, split into 10 equal ranges. + const from = hexToNumber(options.fromBlock); + const to = hexToNumber(options.toBlock); + const chunks = getChunks({ + intervals: [[from, to]], + maxChunkSize: Math.round((to - from) / 10), + }); + retryRanges.push( + ...chunks.map(([f, t]) => [toHex(f), toHex(t)] as const) + ); } else if ( // Infura block range limit error. error instanceof LimitExceededRpcError && diff --git a/packages/core/src/ui/IndexingBar.tsx b/packages/core/src/ui/IndexingBar.tsx index 608e4f869..02846bf1f 100644 --- a/packages/core/src/ui/IndexingBar.tsx +++ b/packages/core/src/ui/IndexingBar.tsx @@ -8,16 +8,16 @@ import { ProgressBar } from "./ProgressBar"; export const IndexingBar = ({ ui }: { ui: UiState }) => { const completionRate = - ui.processedEventCount / Math.max(ui.totalMatchedEventCount, 1); + ui.processedEventCount / Math.max(ui.handledEventCount, 1); const completionDecimal = Math.round(completionRate * 1000) / 10; const completionText = Number.isInteger(completionDecimal) && completionDecimal < 100 ? `${completionDecimal}.0%` : `${completionDecimal}%`; - const isStarted = ui.totalEventCount > 0; + const isStarted = ui.handledEventCount > 0; const isHistoricalSyncComplete = ui.isHistoricalSyncComplete; - const isUpToDate = ui.processedEventCount === ui.totalMatchedEventCount; + const isUpToDate = ui.processedEventCount === ui.handledEventCount; const titleText = () => { if (!isStarted) return (not started); @@ -39,15 +39,15 @@ export const IndexingBar = ({ ui }: { ui: UiState }) => { {" "} | {ui.processedEventCount}/ {"?".repeat(ui.processedEventCount.toString().length)} events ( - {ui.totalEventCount} total) + {ui.totalMatchedEventCount} total)
); } return ( {" "} - | {ui.processedEventCount}/{ui.totalMatchedEventCount} events ( - {ui.totalEventCount} total) + | {ui.processedEventCount}/{ui.handledEventCount} events ( + {ui.totalMatchedEventCount} total) ); }; @@ -61,7 +61,7 @@ export const IndexingBar = ({ ui }: { ui: UiState }) => { {" "} diff --git a/packages/core/src/ui/app.tsx b/packages/core/src/ui/app.tsx index f3b43ec34..20f654c0d 100644 --- a/packages/core/src/ui/app.tsx +++ b/packages/core/src/ui/app.tsx @@ -18,7 +18,7 @@ export type UiState = { indexingError: boolean; processedEventCount: number; - totalEventCount: number; + handledEventCount: number; totalMatchedEventCount: number; eventsProcessedToTimestamp: number; @@ -41,7 +41,7 @@ export const buildUiState = ({ indexingError: false, processedEventCount: 0, - totalEventCount: 0, + handledEventCount: 0, totalMatchedEventCount: 0, eventsProcessedToTimestamp: 0, diff --git a/packages/core/src/ui/service.ts b/packages/core/src/ui/service.ts index 301c64de0..94b86884f 100644 --- a/packages/core/src/ui/service.ts +++ b/packages/core/src/ui/service.ts @@ -90,7 +90,7 @@ export class UiService { const matchedEventCount = ( await this.common.metrics.ponder_indexing_matched_events.get() ).values.reduce((a, v) => a + v.value, 0); - const totalEventCount = ( + const handledEventCount = ( await this.common.metrics.ponder_indexing_handled_events.get() ).values.reduce((a, v) => a + v.value, 0); const processedEventCount = ( @@ -101,7 +101,7 @@ export class UiService { await this.common.metrics.ponder_indexing_latest_processed_timestamp.get() ).values[0].value ?? 0; this.ui.totalMatchedEventCount = matchedEventCount; - this.ui.totalEventCount = totalEventCount; + this.ui.handledEventCount = handledEventCount; this.ui.processedEventCount = processedEventCount; this.ui.eventsProcessedToTimestamp = latestProcessedTimestamp; From 20467f446632e7760e12be596b92a077c38a7265 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Nov 2023 02:09:34 -0500 Subject: [PATCH 31/44] chore: version packages (#410) Co-authored-by: github-actions[bot] --- .changeset/kind-buttons-tickle.md | 5 ----- .changeset/unlucky-days-search.md | 5 ----- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/create-ponder/CHANGELOG.md | 2 ++ packages/create-ponder/package.json | 2 +- packages/eslint-config-ponder/CHANGELOG.md | 2 ++ packages/eslint-config-ponder/package.json | 2 +- 8 files changed, 15 insertions(+), 13 deletions(-) delete mode 100644 .changeset/kind-buttons-tickle.md delete mode 100644 .changeset/unlucky-days-search.md diff --git a/.changeset/kind-buttons-tickle.md b/.changeset/kind-buttons-tickle.md deleted file mode 100644 index 487b9d034..000000000 --- a/.changeset/kind-buttons-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ponder/core": patch ---- - -Fixed a bug where the indexing progress bar had incorrect values. diff --git a/.changeset/unlucky-days-search.md b/.changeset/unlucky-days-search.md deleted file mode 100644 index 0d54112bc..000000000 --- a/.changeset/unlucky-days-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ponder/core": patch ---- - -Fixed a bug where Alchemy "Response size is larger than 150MB limit" errors wer enot handled properly. diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 4e2a049a1..a5e98e09a 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,13 @@ # @ponder/core +## 0.0.95 + +### Patch Changes + +- [#409](https://github.com/0xOlias/ponder/pull/409) [`840f124`](https://github.com/0xOlias/ponder/commit/840f1240a844d6d7756be30ec757a763a251fffb) Thanks [@0xOlias](https://github.com/0xOlias)! - Fixed a bug where the indexing progress bar had incorrect values. + +- [#409](https://github.com/0xOlias/ponder/pull/409) [`840f124`](https://github.com/0xOlias/ponder/commit/840f1240a844d6d7756be30ec757a763a251fffb) Thanks [@0xOlias](https://github.com/0xOlias)! - Fixed a bug where Alchemy "Response size is larger than 150MB limit" errors wer enot handled properly. + ## 0.0.94 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index f1decdd07..01df8ba89 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ponder/core", - "version": "0.0.94", + "version": "0.0.95", "description": "An open-source framework for crypto application backends", "license": "MIT", "repository": { diff --git a/packages/create-ponder/CHANGELOG.md b/packages/create-ponder/CHANGELOG.md index 5afadd20c..104022863 100644 --- a/packages/create-ponder/CHANGELOG.md +++ b/packages/create-ponder/CHANGELOG.md @@ -1,5 +1,7 @@ # create-ponder +## 0.0.95 + ## 0.0.94 ## 0.0.93 diff --git a/packages/create-ponder/package.json b/packages/create-ponder/package.json index 89752c394..9b6f45639 100644 --- a/packages/create-ponder/package.json +++ b/packages/create-ponder/package.json @@ -1,6 +1,6 @@ { "name": "create-ponder", - "version": "0.0.94", + "version": "0.0.95", "description": "A CLI tool to create Ponder apps", "license": "MIT", "repository": { diff --git a/packages/eslint-config-ponder/CHANGELOG.md b/packages/eslint-config-ponder/CHANGELOG.md index 5f9e83733..59cde824a 100644 --- a/packages/eslint-config-ponder/CHANGELOG.md +++ b/packages/eslint-config-ponder/CHANGELOG.md @@ -1,5 +1,7 @@ # eslint-config-ponder +## 0.0.95 + ## 0.0.94 ## 0.0.93 diff --git a/packages/eslint-config-ponder/package.json b/packages/eslint-config-ponder/package.json index 99bdd3e37..ad00ad744 100644 --- a/packages/eslint-config-ponder/package.json +++ b/packages/eslint-config-ponder/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-ponder", - "version": "0.0.94", + "version": "0.0.95", "description": "ESLint config for Ponder apps", "license": "MIT", "main": "./index.js", From e5c74082d4e37544f0a85e6ce9a72f92c281d812 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sun, 5 Nov 2023 20:08:48 -0500 Subject: [PATCH 32/44] read contract without caching --- .../core/src/indexing/readContract.test.ts | 22 +++++ packages/core/src/indexing/readContract.ts | 81 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 packages/core/src/indexing/readContract.test.ts create mode 100644 packages/core/src/indexing/readContract.ts diff --git a/packages/core/src/indexing/readContract.test.ts b/packages/core/src/indexing/readContract.test.ts new file mode 100644 index 000000000..55e9eb3ff --- /dev/null +++ b/packages/core/src/indexing/readContract.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from "vitest"; + +import { usdcContractConfig } from "@/_test/constants"; +import { publicClient } from "@/_test/utils"; + +import { buildReadContract } from "./readContract"; + +test("read contract", async () => { + const readContract = buildReadContract({ + getCurrentBlockNumber: () => 16375000n, + }); + + const totalSupply = await readContract(publicClient, { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }); + + expect(totalSupply).toBe(40921687992499550n); +}); + +test.todo("read contract with cache hit"); diff --git a/packages/core/src/indexing/readContract.ts b/packages/core/src/indexing/readContract.ts new file mode 100644 index 000000000..2de6b60e7 --- /dev/null +++ b/packages/core/src/indexing/readContract.ts @@ -0,0 +1,81 @@ +import { + Abi, + BaseError, + CallParameters, + Chain, + Client, + decodeFunctionResult, + DecodeFunctionResultParameters, + encodeFunctionData, + EncodeFunctionDataParameters, + getContractError, + ReadContractParameters, + ReadContractReturnType, + Transport, +} from "viem"; +import { call } from "viem/actions"; + +/** + * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/readContract.html readContract} function, + * but caches the results in the event store. + */ +export const buildReadContract = + ({ + getCurrentBlockNumber, + }: { + // eventStore: EventStore; + getCurrentBlockNumber: () => bigint; + }) => + async < + TChain extends Chain | undefined, + TAbi extends Abi | readonly unknown[], + TFunctionName extends string + >( + client: Client, + { + abi, + address, + args, + functionName, + ...callRequest + }: Omit< + ReadContractParameters, + "blockTag" | "blockNumber" + > + ): Promise> => { + const calldata = encodeFunctionData({ + abi, + args, + functionName, + } as unknown as EncodeFunctionDataParameters); + + // If cache hit + // If no cache hit + try { + const { data } = await call(client, { + to: address, + data: calldata, + blockNumber: getCurrentBlockNumber(), + ...callRequest, + } as unknown as CallParameters); + + return decodeFunctionResult({ + abi, + args, + functionName, + data: data || "0x", + } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< + TAbi, + TFunctionName + >; + // TODO: add `data` to cache + } catch (err) { + throw getContractError(err as BaseError, { + abi: abi as Abi, + address, + args, + docsPath: "/docs/contract/readContract", + functionName, + }); + } + }; From 8dcc60a2548fe3544b165c6771ebbc6876882c93 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sun, 5 Nov 2023 20:20:03 -0500 Subject: [PATCH 33/44] caching for readContract --- .../core/src/indexing/readContract.test.ts | 35 +++++- packages/core/src/indexing/readContract.ts | 100 +++++++++++++----- 2 files changed, 103 insertions(+), 32 deletions(-) diff --git a/packages/core/src/indexing/readContract.test.ts b/packages/core/src/indexing/readContract.test.ts index 55e9eb3ff..cd45c1fad 100644 --- a/packages/core/src/indexing/readContract.test.ts +++ b/packages/core/src/indexing/readContract.test.ts @@ -1,13 +1,19 @@ -import { expect, test } from "vitest"; +import { beforeEach, expect, test } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; +import { setupEventStore } from "@/_test/setup"; import { publicClient } from "@/_test/utils"; import { buildReadContract } from "./readContract"; -test("read contract", async () => { +beforeEach((context) => setupEventStore(context)); + +const usdcTotalSupply16375000 = 40921687992499550n; + +test("read contract", async ({ eventStore }) => { const readContract = buildReadContract({ getCurrentBlockNumber: () => 16375000n, + eventStore, }); const totalSupply = await readContract(publicClient, { @@ -16,7 +22,28 @@ test("read contract", async () => { address: usdcContractConfig.address, }); - expect(totalSupply).toBe(40921687992499550n); + expect(totalSupply).toBe(usdcTotalSupply16375000); }); -test.todo("read contract with cache hit"); +test("read contract with cache hit", async ({ eventStore }) => { + const readContract = buildReadContract({ + getCurrentBlockNumber: () => 16375000n, + eventStore, + }); + + let totalSupply = await readContract(publicClient, { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }); + + expect(totalSupply).toBe(usdcTotalSupply16375000); + + totalSupply = await readContract(publicClient, { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }); + + expect(totalSupply).toBe(usdcTotalSupply16375000); +}); diff --git a/packages/core/src/indexing/readContract.ts b/packages/core/src/indexing/readContract.ts index 2de6b60e7..53a5fd7a5 100644 --- a/packages/core/src/indexing/readContract.ts +++ b/packages/core/src/indexing/readContract.ts @@ -15,15 +15,20 @@ import { } from "viem"; import { call } from "viem/actions"; +import { EventStore } from "@/event-store/store"; + /** * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/readContract.html readContract} function, * but caches the results in the event store. + * + * @todo How to determine chainID */ export const buildReadContract = ({ + eventStore, getCurrentBlockNumber, }: { - // eventStore: EventStore; + eventStore: EventStore; getCurrentBlockNumber: () => bigint; }) => async < @@ -48,34 +53,73 @@ export const buildReadContract = args, functionName, } as unknown as EncodeFunctionDataParameters); + const blockNumber = getCurrentBlockNumber(); + const chainId = client.chain!.id; + + // Check cache + const cachedContractReadResult = await eventStore.getContractReadResult({ + address, + blockNumber, + chainId, + data: calldata, + }); + + if (cachedContractReadResult) { + // Cache hit + try { + return decodeFunctionResult({ + abi, + args, + functionName, + data: cachedContractReadResult.result, + } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< + TAbi, + TFunctionName + >; + } catch (err) { + throw getContractError(err as BaseError, { + abi: abi as Abi, + address, + args, + docsPath: "/docs/contract/readContract", + functionName, + }); + } + } else { + // No cache hit + try { + const { data: rawResult } = await call(client, { + to: address, + data: calldata, + blockNumber, + ...callRequest, + } as unknown as CallParameters); - // If cache hit - // If no cache hit - try { - const { data } = await call(client, { - to: address, - data: calldata, - blockNumber: getCurrentBlockNumber(), - ...callRequest, - } as unknown as CallParameters); + await eventStore.insertContractReadResult({ + address, + blockNumber, + chainId, + data: calldata, + result: rawResult || "0x", + }); - return decodeFunctionResult({ - abi, - args, - functionName, - data: data || "0x", - } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< - TAbi, - TFunctionName - >; - // TODO: add `data` to cache - } catch (err) { - throw getContractError(err as BaseError, { - abi: abi as Abi, - address, - args, - docsPath: "/docs/contract/readContract", - functionName, - }); + return decodeFunctionResult({ + abi, + args, + functionName, + data: rawResult || "0x", + } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< + TAbi, + TFunctionName + >; + } catch (err) { + throw getContractError(err as BaseError, { + abi: abi as Abi, + address, + args, + docsPath: "/docs/contract/readContract", + functionName, + }); + } } }; From cc0056feb84c58e8bd75fbf393df8ea7953b0679 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sun, 5 Nov 2023 20:22:53 -0500 Subject: [PATCH 34/44] remove more notions of contracts --- packages/core/src/indexing/contract.ts | 151 --------------------- packages/core/src/indexing/service.test.ts | 3 - packages/core/src/indexing/service.ts | 12 -- packages/core/src/types/contract.ts | 19 --- 4 files changed, 185 deletions(-) delete mode 100644 packages/core/src/indexing/contract.ts delete mode 100644 packages/core/src/types/contract.ts diff --git a/packages/core/src/indexing/contract.ts b/packages/core/src/indexing/contract.ts deleted file mode 100644 index a76bf1600..000000000 --- a/packages/core/src/indexing/contract.ts +++ /dev/null @@ -1,151 +0,0 @@ -// import { -// type Abi, -// type BaseError, -// type CallParameters, -// type GetContractReturnType, -// type Hex, -// type PublicClient, -// type ReadContractParameters, -// decodeFunctionResult, -// encodeFunctionData, -// getContract, -// getContractError, -// } from "viem"; - -// // import type { Contract } from "@/config/contracts"; -// import type { EventStore } from "@/event-store/store"; - -// export function buildReadOnlyContracts({ -// contracts, -// eventStore, -// getCurrentBlockNumber, -// }: { -// contracts: Contract[]; -// eventStore: EventStore; -// getCurrentBlockNumber: () => bigint; -// }): Record> { -// return contracts.reduce< -// Record> -// >((acc, { name, abi, address, network }) => { -// const { chainId, client: publicClient } = network; - -// const readOnlyContract = getContract({ abi, address, publicClient }); - -// readOnlyContract.read = new Proxy( -// {}, -// { -// get(_, functionName: string) { -// return async ( -// ...parameters: [ -// args?: readonly unknown[], -// options?: Omit< -// ReadContractParameters, -// "abi" | "address" | "functionName" | "args" -// > -// ] -// ) => { -// const { args, options } = getFunctionParameters(parameters); - -// // If the user specified a block tag, serve the request as normal (no caching). -// if (options?.blockTag) { -// return publicClient.readContract({ -// abi, -// address, -// functionName, -// args, -// ...options, -// } as ReadContractParameters); -// } - -// // If the user specified a block number, use it, otherwise use the -// // block number of the current event being processed. -// const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(); - -// const calldata = encodeFunctionData({ abi, args, functionName }); - -// const decodeRawResult = (rawResult: Hex) => { -// try { -// return decodeFunctionResult({ -// abi, -// args, -// functionName, -// data: rawResult, -// }); -// } catch (err) { -// throw getContractError(err as BaseError, { -// abi, -// address, -// args, -// docsPath: "/docs/contract/readContract", -// functionName, -// }); -// } -// }; - -// // Check if this request can be served from the cache. -// const cachedContractReadResult = -// await eventStore.getContractReadResult({ -// address, -// blockNumber, -// chainId, -// data: calldata, -// }); - -// if (cachedContractReadResult) { -// return decodeRawResult(cachedContractReadResult.result); -// } - -// // Cache miss. Make the RPC request, then add to the cache. -// let rawResult: Hex; -// try { -// const { data } = await publicClient.call({ -// data: calldata, -// to: address, -// ...{ -// ...options, -// blockNumber, -// }, -// } as unknown as CallParameters); - -// rawResult = data || "0x"; -// } catch (err) { -// throw getContractError(err as BaseError, { -// abi, -// address, -// args, -// docsPath: "/docs/contract/readContract", -// functionName, -// }); -// } - -// await eventStore.insertContractReadResult({ -// address, -// blockNumber, -// chainId, -// data: calldata, -// result: rawResult, -// }); - -// return decodeRawResult(rawResult); -// }; -// }, -// } -// ); - -// acc[name] = readOnlyContract; - -// return acc; -// }, {}); -// } - -// function getFunctionParameters( -// values: [args?: readonly unknown[], options?: object] -// ) { -// const hasArgs = values.length && Array.isArray(values[0]); -// const args = hasArgs ? values[0]! : []; -// const options = ((hasArgs ? values[1] : values[0]) ?? {}) as Omit< -// ReadContractParameters, -// "abi" | "address" | "functionName" | "args" -// >; -// return { args, options }; -// } diff --git a/packages/core/src/indexing/service.test.ts b/packages/core/src/indexing/service.test.ts index cf1c4ae5a..7d0f28aca 100644 --- a/packages/core/src/indexing/service.test.ts +++ b/packages/core/src/indexing/service.test.ts @@ -37,8 +37,6 @@ const sources: Source[] = [ }, ]; -// const contracts = [{ name: "USDC", ...usdcContractConfig, network }]; - const schema = buildSchema( buildGraphqlSchema(`${schemaHeader} type TransferEvent @entity { @@ -183,7 +181,6 @@ test("processEvents() calls indexing functions with correct arguments", async (c name: "Transfer", }, context: { - contracts: {}, entities: { TransferEvent: expect.anything() }, }, }) diff --git a/packages/core/src/indexing/service.ts b/packages/core/src/indexing/service.ts index 5149c9aca..960536228 100644 --- a/packages/core/src/indexing/service.ts +++ b/packages/core/src/indexing/service.ts @@ -12,7 +12,6 @@ import type { import type { EventStore } from "@/event-store/store"; import type { Common } from "@/Ponder"; import type { Schema } from "@/schema/types"; -import type { ReadOnlyContract } from "@/types/contract"; import type { Model } from "@/types/model"; import type { ModelInstance, UserStore } from "@/user-store/store"; import { formatShortDate } from "@/utils/date"; @@ -38,8 +37,6 @@ export class IndexingService extends Emittery { private eventAggregatorService: EventAggregatorService; private sources: Source[]; - private readOnlyContracts: Record = {}; - private schema?: Schema; private models: Record> = {}; @@ -75,14 +72,6 @@ export class IndexingService extends Emittery { this.eventAggregatorService = eventAggregatorService; this.sources = sources; - // The read-only contract objects only depend on config, so they can - // be built in the constructor (they can't be hot-reloaded). - // this.readOnlyContracts = buildReadOnlyContracts({ - // contracts, - // getCurrentBlockNumber: () => this.currentEventBlockNumber, - // eventStore, - // }); - this.eventProcessingMutex = new Mutex(); } @@ -363,7 +352,6 @@ export class IndexingService extends Emittery { indexingFunctions: IndexingFunctions; }) => { const context = { - contracts: this.readOnlyContracts, entities: this.models, }; diff --git a/packages/core/src/types/contract.ts b/packages/core/src/types/contract.ts deleted file mode 100644 index 38c077204..000000000 --- a/packages/core/src/types/contract.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Abi, Address, ExtractAbiFunctionNames } from "abitype"; -import type { GetContractReturnType, PublicClient } from "viem"; - -export type ReadOnlyContract< - TAbi extends Abi = Abi, - _ReadFunctionNames extends string = TAbi extends Abi - ? Abi extends TAbi - ? string - : ExtractAbiFunctionNames - : string -> = GetContractReturnType< - TAbi, - PublicClient, - unknown, - Address, - never, - _ReadFunctionNames, - never ->; From e25713fb733efb84f18276a0243768ff6c33b4cd Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Sun, 5 Nov 2023 20:27:34 -0500 Subject: [PATCH 35/44] remove comments --- packages/core/src/build/functions.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/build/functions.ts b/packages/core/src/build/functions.ts index a2818efdd..db4817a15 100644 --- a/packages/core/src/build/functions.ts +++ b/packages/core/src/build/functions.ts @@ -222,9 +222,6 @@ export const hydrateIndexingFunctions = ({ Object.entries(rawIndexingFunctions.eventSources).forEach( ([eventSourceName, eventSourceFunctions]) => { - // const logFilter = logFilters.find((l) => l.name === eventSourceName); - // const factory = factories.find((f) => f.name === eventSourceName); - const source = sources.find((source) => source.name === eventSourceName); if (!source) { From 7f1b70d4cb54b5e5f55f274e195ecd4a1c4c0388 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 12:54:34 -0500 Subject: [PATCH 36/44] eth_call sqlite request caching --- .../core/src/event-store/sqlite/format.ts | 9 +- .../core/src/event-store/sqlite/migrations.ts | 13 +++ packages/core/src/event-store/sqlite/store.ts | 40 +++---- packages/core/src/event-store/store.test.ts | 8 +- packages/core/src/event-store/store.ts | 19 ++- packages/core/src/indexing/multicall.test.ts | 70 +++++++++++ packages/core/src/indexing/multicall.ts | 34 ++++++ .../core/src/indexing/readContract.test.ts | 26 +++-- packages/core/src/indexing/readContract.ts | 109 ++---------------- packages/core/src/indexing/transport.test.ts | 5 + packages/core/src/indexing/transport.ts | 50 ++++++++ 11 files changed, 231 insertions(+), 152 deletions(-) create mode 100644 packages/core/src/indexing/multicall.test.ts create mode 100644 packages/core/src/indexing/multicall.ts create mode 100644 packages/core/src/indexing/transport.test.ts create mode 100644 packages/core/src/indexing/transport.ts diff --git a/packages/core/src/event-store/sqlite/format.ts b/packages/core/src/event-store/sqlite/format.ts index 6de1f0dc4..a0d3667d0 100644 --- a/packages/core/src/event-store/sqlite/format.ts +++ b/packages/core/src/event-store/sqlite/format.ts @@ -160,12 +160,11 @@ export function rpcToSqliteLog(log: RpcLog): Omit { }; } -type ContractReadResultsTable = { - address: Address; +type RpcRequestResults = { blockNumber: BigIntText; chainId: number; - data: Hex; - result: Hex; + result: string; + request: string; }; type LogFiltersTable = { @@ -208,7 +207,7 @@ export type EventStoreTables = { blocks: BlocksTable; transactions: TransactionsTable; logs: LogsTable; - contractReadResults: ContractReadResultsTable; + rpcRequestResults: RpcRequestResults; logFilters: LogFiltersTable; logFilterIntervals: LogFilterIntervalsTable; diff --git a/packages/core/src/event-store/sqlite/migrations.ts b/packages/core/src/event-store/sqlite/migrations.ts index 3ef4fc61b..c094e3b97 100644 --- a/packages/core/src/event-store/sqlite/migrations.ts +++ b/packages/core/src/event-store/sqlite/migrations.ts @@ -349,6 +349,19 @@ const migrations: Record = { .execute(); }, }, + ["2023_11_06_0_new_rpc_cache_design"]: { + async up(db: Kysely) { + await db.schema.dropTable("contractReadResults").execute(); + + await db.schema + .createTable("rpcRequestResults") + .addColumn("request", "text", (col) => col.notNull().primaryKey()) + .addColumn("blockNumber", "varchar(79)", (col) => col.notNull()) + .addColumn("chainId", "integer", (col) => col.notNull()) + .addColumn("result", "text", (col) => col.notNull()) + .execute(); + }, + }, }; class StaticMigrationProvider implements MigrationProvider { diff --git a/packages/core/src/event-store/sqlite/store.ts b/packages/core/src/event-store/sqlite/store.ts index e07e6aef3..871a1b15e 100644 --- a/packages/core/src/event-store/sqlite/store.ts +++ b/packages/core/src/event-store/sqlite/store.ts @@ -7,7 +7,7 @@ import { sql, SqliteDialect, } from "kysely"; -import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; +import type { Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; import type { Block } from "@/types/block"; @@ -549,7 +549,7 @@ export class SqliteEventStore implements EventStore { .where("blockNumber", ">", fromBlock) .execute(); await tx - .deleteFrom("contractReadResults") + .deleteFrom("rpcRequestResults") .where("chainId", "=", chainId) .where("blockNumber", ">", fromBlock) .execute(); @@ -703,56 +703,50 @@ export class SqliteEventStore implements EventStore { /** CONTRACT READS */ - insertContractReadResult = async ({ - address, + insertRpcRequestResult = async ({ blockNumber, chainId, - data, + request, result, }: { - address: Address; blockNumber: bigint; chainId: number; - data: Hex; - result: Hex; + request: string; + result: string; }) => { await this.db - .insertInto("contractReadResults") + .insertInto("rpcRequestResults") .values({ - address, + request, blockNumber: encodeAsText(blockNumber), chainId, - data, result, }) .onConflict((oc) => oc.doUpdateSet({ result })) .execute(); }; - getContractReadResult = async ({ - address, + getRpcRequestResult = async ({ blockNumber, chainId, - data, + request, }: { - address: Address; blockNumber: bigint; chainId: number; - data: Hex; + request: string; }) => { - const contractReadResult = await this.db - .selectFrom("contractReadResults") + const rpcRequestResult = await this.db + .selectFrom("rpcRequestResults") .selectAll() - .where("address", "=", address) .where("blockNumber", "=", encodeAsText(blockNumber)) .where("chainId", "=", chainId) - .where("data", "=", data) + .where("request", "=", request) .executeTakeFirst(); - return contractReadResult + return rpcRequestResult ? { - ...contractReadResult, - blockNumber: decodeToBigInt(contractReadResult.blockNumber), + ...rpcRequestResult, + blockNumber: decodeToBigInt(rpcRequestResult.blockNumber), } : null; }; diff --git a/packages/core/src/event-store/store.test.ts b/packages/core/src/event-store/store.test.ts index 0d6aa518a..5f52f6222 100644 --- a/packages/core/src/event-store/store.test.ts +++ b/packages/core/src/event-store/store.test.ts @@ -896,7 +896,7 @@ test("deleteRealtimeData updates interval data", async (context) => { ).toMatchObject([[15495110, 15495110]]); }); -test("insertContractReadResult inserts a contract call", async (context) => { +test.skip("insertContractReadResult inserts a contract call", async (context) => { const { eventStore } = context; await eventStore.insertContractReadResult({ @@ -921,7 +921,7 @@ test("insertContractReadResult inserts a contract call", async (context) => { }); }); -test("insertContractReadResult upserts on conflict", async (context) => { +test.skip("insertContractReadResult upserts on conflict", async (context) => { const { eventStore } = context; await eventStore.insertContractReadResult({ @@ -963,7 +963,7 @@ test("insertContractReadResult upserts on conflict", async (context) => { }); }); -test("getContractReadResult returns data", async (context) => { +test.skip("getContractReadResult returns data", async (context) => { const { eventStore } = context; await eventStore.insertContractReadResult({ @@ -990,7 +990,7 @@ test("getContractReadResult returns data", async (context) => { }); }); -test("getContractReadResult returns null if not found", async (context) => { +test.skip("getContractReadResult returns null if not found", async (context) => { const { eventStore } = context; await eventStore.insertContractReadResult({ diff --git a/packages/core/src/event-store/store.ts b/packages/core/src/event-store/store.ts index 2c01c2882..63cc4f8e1 100644 --- a/packages/core/src/event-store/store.ts +++ b/packages/core/src/event-store/store.ts @@ -126,27 +126,24 @@ export interface EventStore { fromBlock: bigint; }): Promise; - /** CONTRACT READ METHODS */ + /** RPC REQUEST METHODS */ - insertContractReadResult(options: { - address: Address; + insertRpcRequestResult(options: { + request: string; blockNumber: bigint; chainId: number; - data: Hex; - result: Hex; + result: string; }): Promise; - getContractReadResult(options: { - address: Address; + getRpcRequestResult(options: { + request: string; blockNumber: bigint; chainId: number; - data: Hex; }): Promise<{ - address: Address; + request: string; blockNumber: bigint; chainId: number; - data: Hex; - result: Hex; + result: string; } | null>; /** EVENTS METHOD */ diff --git a/packages/core/src/indexing/multicall.test.ts b/packages/core/src/indexing/multicall.test.ts new file mode 100644 index 000000000..71b553f61 --- /dev/null +++ b/packages/core/src/indexing/multicall.test.ts @@ -0,0 +1,70 @@ +import { createClient, http } from "viem"; +import { beforeEach, expect, test } from "vitest"; + +import { usdcContractConfig } from "@/_test/constants"; +import { setupEventStore } from "@/_test/setup"; +import { anvil } from "@/_test/utils"; + +import { buildMulticall } from "./multicall"; +import { buildReadContract } from "./readContract"; +import { ponderTransport } from "./transport"; + +beforeEach((context) => setupEventStore(context)); + +const usdcTotalSupply16375000 = 40921687992499550n; + +test("multicall() no cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const multicall = buildReadContract({ + getCurrentBlockNumber: () => 16375000n, + }); + + const totalSupply = await multicall(client, { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }); + + expect(totalSupply).toBe(usdcTotalSupply16375000); +}); + +test("multicall() with cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const multicall = buildMulticall({ + getCurrentBlockNumber: () => 16375000n, + }); + + let totalSupply = await multicall(client, { + allowFailure: false, + contracts: [ + { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }, + ], + }); + + expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); + + totalSupply = await multicall(client, { + allowFailure: false, + contracts: [ + { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }, + ], + }); + + expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); +}); diff --git a/packages/core/src/indexing/multicall.ts b/packages/core/src/indexing/multicall.ts new file mode 100644 index 000000000..39783b951 --- /dev/null +++ b/packages/core/src/indexing/multicall.ts @@ -0,0 +1,34 @@ +import { + Chain, + Client, + ContractFunctionConfig, + MulticallParameters, + MulticallReturnType, + Transport, +} from "viem"; +import { multicall } from "viem/actions"; + +/** + * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/multicall.html multicall} function, + * but caches the results in the event store. + * + * @todo How to determine chainID + */ +export const buildMulticall = + ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => + async < + TChain extends Chain | undefined, + TContracts extends ContractFunctionConfig[], + TAllowFailure extends boolean = true + >( + client: Client, + args: Omit< + MulticallParameters, + "blockTag" | "blockNumber" + > + ): Promise> => { + return multicall(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }); + }; diff --git a/packages/core/src/indexing/readContract.test.ts b/packages/core/src/indexing/readContract.test.ts index cd45c1fad..e7dde4413 100644 --- a/packages/core/src/indexing/readContract.test.ts +++ b/packages/core/src/indexing/readContract.test.ts @@ -1,22 +1,28 @@ +import { createClient, http } from "viem"; import { beforeEach, expect, test } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; -import { publicClient } from "@/_test/utils"; +import { anvil } from "@/_test/utils"; import { buildReadContract } from "./readContract"; +import { ponderTransport } from "./transport"; beforeEach((context) => setupEventStore(context)); const usdcTotalSupply16375000 = 40921687992499550n; -test("read contract", async ({ eventStore }) => { +test("readContract() no cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + const readContract = buildReadContract({ getCurrentBlockNumber: () => 16375000n, - eventStore, }); - const totalSupply = await readContract(publicClient, { + const totalSupply = await readContract(client, { abi: usdcContractConfig.abi, functionName: "totalSupply", address: usdcContractConfig.address, @@ -25,13 +31,17 @@ test("read contract", async ({ eventStore }) => { expect(totalSupply).toBe(usdcTotalSupply16375000); }); -test("read contract with cache hit", async ({ eventStore }) => { +test("readContract() with cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + const readContract = buildReadContract({ getCurrentBlockNumber: () => 16375000n, - eventStore, }); - let totalSupply = await readContract(publicClient, { + let totalSupply = await readContract(client, { abi: usdcContractConfig.abi, functionName: "totalSupply", address: usdcContractConfig.address, @@ -39,7 +49,7 @@ test("read contract with cache hit", async ({ eventStore }) => { expect(totalSupply).toBe(usdcTotalSupply16375000); - totalSupply = await readContract(publicClient, { + totalSupply = await readContract(client, { abi: usdcContractConfig.abi, functionName: "totalSupply", address: usdcContractConfig.address, diff --git a/packages/core/src/indexing/readContract.ts b/packages/core/src/indexing/readContract.ts index 53a5fd7a5..f3ea268b9 100644 --- a/packages/core/src/indexing/readContract.ts +++ b/packages/core/src/indexing/readContract.ts @@ -1,125 +1,32 @@ import { Abi, - BaseError, - CallParameters, Chain, Client, - decodeFunctionResult, - DecodeFunctionResultParameters, - encodeFunctionData, - EncodeFunctionDataParameters, - getContractError, ReadContractParameters, ReadContractReturnType, Transport, } from "viem"; -import { call } from "viem/actions"; - -import { EventStore } from "@/event-store/store"; +import { readContract } from "viem/actions"; /** * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/readContract.html readContract} function, - * but caches the results in the event store. - * - * @todo How to determine chainID + * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. */ export const buildReadContract = - ({ - eventStore, - getCurrentBlockNumber, - }: { - eventStore: EventStore; - getCurrentBlockNumber: () => bigint; - }) => + ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => async < TChain extends Chain | undefined, TAbi extends Abi | readonly unknown[], TFunctionName extends string >( client: Client, - { - abi, - address, - args, - functionName, - ...callRequest - }: Omit< + args: Omit< ReadContractParameters, "blockTag" | "blockNumber" > ): Promise> => { - const calldata = encodeFunctionData({ - abi, - args, - functionName, - } as unknown as EncodeFunctionDataParameters); - const blockNumber = getCurrentBlockNumber(); - const chainId = client.chain!.id; - - // Check cache - const cachedContractReadResult = await eventStore.getContractReadResult({ - address, - blockNumber, - chainId, - data: calldata, - }); - - if (cachedContractReadResult) { - // Cache hit - try { - return decodeFunctionResult({ - abi, - args, - functionName, - data: cachedContractReadResult.result, - } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< - TAbi, - TFunctionName - >; - } catch (err) { - throw getContractError(err as BaseError, { - abi: abi as Abi, - address, - args, - docsPath: "/docs/contract/readContract", - functionName, - }); - } - } else { - // No cache hit - try { - const { data: rawResult } = await call(client, { - to: address, - data: calldata, - blockNumber, - ...callRequest, - } as unknown as CallParameters); - - await eventStore.insertContractReadResult({ - address, - blockNumber, - chainId, - data: calldata, - result: rawResult || "0x", - }); - - return decodeFunctionResult({ - abi, - args, - functionName, - data: rawResult || "0x", - } as unknown as DecodeFunctionResultParameters) as ReadContractReturnType< - TAbi, - TFunctionName - >; - } catch (err) { - throw getContractError(err as BaseError, { - abi: abi as Abi, - address, - args, - docsPath: "/docs/contract/readContract", - functionName, - }); - } - } + return readContract(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + } as unknown as ReadContractParameters); }; diff --git a/packages/core/src/indexing/transport.test.ts b/packages/core/src/indexing/transport.test.ts new file mode 100644 index 000000000..3a6adc800 --- /dev/null +++ b/packages/core/src/indexing/transport.test.ts @@ -0,0 +1,5 @@ +import { test } from "vitest"; + +test.todo("request no cache", async () => {}); + +test.todo("request with cache", async () => {}); diff --git a/packages/core/src/indexing/transport.ts b/packages/core/src/indexing/transport.ts new file mode 100644 index 000000000..2cdb76275 --- /dev/null +++ b/packages/core/src/indexing/transport.ts @@ -0,0 +1,50 @@ +import { custom, Hex, Transport } from "viem"; + +import { EventStore } from "@/event-store/store"; + +export const ponderTransport = ({ + transport, + eventStore, +}: { + transport: Transport; + eventStore: EventStore; +}): Transport => { + return ({ chain }) => { + const underlyingTransport = transport({ chain }); + + const c = custom({ + async request({ method, params }) { + const body = { method, params }; + if (method === "eth_call") { + const [{ data, to }, blockNumber] = params as [ + { data: Hex; to: Hex }, + Hex + ]; + + const request = `${method as string}_${to}_${data}`; + + const cachedResult = await eventStore.getRpcRequestResult({ + blockNumber: BigInt(blockNumber), + chainId: chain!.id, + request, + }); + + if (cachedResult?.result) return cachedResult.result; + else { + const response = await underlyingTransport.request(body); + await eventStore.insertRpcRequestResult({ + blockNumber: BigInt(blockNumber), + chainId: chain!.id, + request, + result: response as string, + }); + return response; + } + } else { + return await underlyingTransport.request(body); + } + }, + }); + return c({ chain }); + }; +}; From 6e610c323fe54b6e5b38728c3f52d0d938d3d582 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 13:10:05 -0500 Subject: [PATCH 37/44] getbalance override --- packages/core/src/indexing/getBalance.test.ts | 50 +++++++++++++++++++ packages/core/src/indexing/getBalance.ts | 24 +++++++++ packages/core/src/indexing/multicall.test.ts | 14 ++++-- packages/core/src/indexing/multicall.ts | 4 +- packages/core/src/indexing/transport.ts | 20 ++++++-- 5 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/indexing/getBalance.test.ts create mode 100644 packages/core/src/indexing/getBalance.ts diff --git a/packages/core/src/indexing/getBalance.test.ts b/packages/core/src/indexing/getBalance.test.ts new file mode 100644 index 000000000..f6bb10741 --- /dev/null +++ b/packages/core/src/indexing/getBalance.test.ts @@ -0,0 +1,50 @@ +import { createClient, http } from "viem"; +import { beforeEach, expect, test } from "vitest"; + +import { setupEventStore } from "@/_test/setup"; +import { anvil } from "@/_test/utils"; + +import { buildGetBalance } from "./getBalance"; +import { ponderTransport } from "./transport"; + +beforeEach((context) => setupEventStore(context)); + +test("getBalance() no cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const getBalance = buildGetBalance({ + getCurrentBlockNumber: () => 16375000n, + }); + + const balance = await getBalance(client, { + address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + }); + + expect(balance).toBe(398806329552690329n); +}); + +test("getBalance() with cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const getBalance = buildGetBalance({ + getCurrentBlockNumber: () => 16375000n, + }); + + let balance = await getBalance(client, { + address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + }); + + expect(balance).toBe(398806329552690329n); + + balance = await getBalance(client, { + address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + }); + + expect(balance).toBe(398806329552690329n); +}); diff --git a/packages/core/src/indexing/getBalance.ts b/packages/core/src/indexing/getBalance.ts new file mode 100644 index 000000000..c184fe552 --- /dev/null +++ b/packages/core/src/indexing/getBalance.ts @@ -0,0 +1,24 @@ +import { + Chain, + Client, + GetBalanceParameters, + GetBalanceReturnType, + Transport, +} from "viem"; +import { getBalance } from "viem/actions"; + +/** + * Build a function with the same api as viem's {@link https://viem.sh/docs/actions/public/getBalance.html getBalance} function, + * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. + */ +export const buildGetBalance = + ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => + async ( + client: Client, + args: Omit + ): Promise => { + return getBalance(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }); + }; diff --git a/packages/core/src/indexing/multicall.test.ts b/packages/core/src/indexing/multicall.test.ts index 71b553f61..7b9aa83e1 100644 --- a/packages/core/src/indexing/multicall.test.ts +++ b/packages/core/src/indexing/multicall.test.ts @@ -6,7 +6,6 @@ import { setupEventStore } from "@/_test/setup"; import { anvil } from "@/_test/utils"; import { buildMulticall } from "./multicall"; -import { buildReadContract } from "./readContract"; import { ponderTransport } from "./transport"; beforeEach((context) => setupEventStore(context)); @@ -19,14 +18,19 @@ test("multicall() no cache", async ({ eventStore }) => { transport: ponderTransport({ transport: http(), eventStore }), }); - const multicall = buildReadContract({ + const multicall = buildMulticall({ getCurrentBlockNumber: () => 16375000n, }); const totalSupply = await multicall(client, { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, + allowFailure: false, + contracts: [ + { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }, + ], }); expect(totalSupply).toBe(usdcTotalSupply16375000); diff --git a/packages/core/src/indexing/multicall.ts b/packages/core/src/indexing/multicall.ts index 39783b951..83c445641 100644 --- a/packages/core/src/indexing/multicall.ts +++ b/packages/core/src/indexing/multicall.ts @@ -10,9 +10,7 @@ import { multicall } from "viem/actions"; /** * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/multicall.html multicall} function, - * but caches the results in the event store. - * - * @todo How to determine chainID + * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. */ export const buildMulticall = ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => diff --git a/packages/core/src/indexing/transport.ts b/packages/core/src/indexing/transport.ts index 2cdb76275..9648a589d 100644 --- a/packages/core/src/indexing/transport.ts +++ b/packages/core/src/indexing/transport.ts @@ -1,4 +1,4 @@ -import { custom, Hex, Transport } from "viem"; +import { Address, custom, Hex, Transport } from "viem"; import { EventStore } from "@/event-store/store"; @@ -15,16 +15,28 @@ export const ponderTransport = ({ const c = custom({ async request({ method, params }) { const body = { method, params }; + console.log(body); + + let request: string | null = null; + let blockNumber: bigint | null = null; if (method === "eth_call") { - const [{ data, to }, blockNumber] = params as [ + const [{ data, to }, _blockNumber] = params as [ { data: Hex; to: Hex }, Hex ]; - const request = `${method as string}_${to}_${data}`; + request = `${method as string}_${to}_${data}`; + blockNumber = BigInt(_blockNumber); + } else if (method === "eth_getBalance") { + const [address, _blockNumber] = params as [Address, Hex]; + + request = `${method as string}_${address}`; + blockNumber = BigInt(_blockNumber); + } + if (request !== null && blockNumber !== null) { const cachedResult = await eventStore.getRpcRequestResult({ - blockNumber: BigInt(blockNumber), + blockNumber, chainId: chain!.id, request, }); From 60b42a14b364b5e137404e2fa3ace26f990cff76 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 15:27:32 -0500 Subject: [PATCH 38/44] fix tests --- packages/core/src/event-store/store.test.ts | 87 +++++++++---------- packages/core/src/indexing/getBalance.test.ts | 6 +- .../core/src/indexing/getBytecode.test.ts | 62 +++++++++++++ packages/core/src/indexing/getBytecode.ts | 24 +++++ packages/core/src/indexing/multicall.test.ts | 7 +- .../core/src/indexing/readContract.test.ts | 5 +- packages/core/src/indexing/transport.test.ts | 5 -- packages/core/src/indexing/transport.ts | 9 +- 8 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 packages/core/src/indexing/getBytecode.test.ts create mode 100644 packages/core/src/indexing/getBytecode.ts delete mode 100644 packages/core/src/indexing/transport.test.ts diff --git a/packages/core/src/event-store/store.test.ts b/packages/core/src/event-store/store.test.ts index 5f52f6222..624802936 100644 --- a/packages/core/src/event-store/store.test.ts +++ b/packages/core/src/event-store/store.test.ts @@ -30,7 +30,7 @@ test("setup creates tables", async (context) => { expect(tableNames).toContain("factories"); expect(tableNames).toContain("factoryLogFilterIntervals"); - expect(tableNames).toContain("contractReadResults"); + expect(tableNames).toContain("rpcRequestResults"); }); test("insertLogFilterInterval inserts block, transactions, and logs", async (context) => { @@ -896,119 +896,110 @@ test("deleteRealtimeData updates interval data", async (context) => { ).toMatchObject([[15495110, 15495110]]); }); -test.skip("insertContractReadResult inserts a contract call", async (context) => { +test("insertRpcRequestResult inserts a request result", async (context) => { const { eventStore } = context; - await eventStore.insertContractReadResult({ - address: usdcContractConfig.address, + await eventStore.insertRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789", }); - const contractReadResults = await eventStore.db - .selectFrom("contractReadResults") + const rpcRequestResults = await eventStore.db + .selectFrom("rpcRequestResults") .selectAll() .execute(); - expect(contractReadResults).toHaveLength(1); - expect(contractReadResults[0]).toMatchObject({ - address: usdcContractConfig.address, + expect(rpcRequestResults).toHaveLength(1); + expect(rpcRequestResults[0]).toMatchObject({ chainId: 1, - data: "0x123", + request: "0x123", result: "0x789", }); }); -test.skip("insertContractReadResult upserts on conflict", async (context) => { +test("insertRpcRequestResult upserts on conflict", async (context) => { const { eventStore } = context; - await eventStore.insertContractReadResult({ - address: usdcContractConfig.address, + await eventStore.insertRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789", }); - const contractReadResults = await eventStore.db - .selectFrom("contractReadResults") - .select(["address", "result"]) + const rpcRequestResult = await eventStore.db + .selectFrom("rpcRequestResults") + .selectAll() .execute(); - expect(contractReadResults).toHaveLength(1); - expect(contractReadResults[0]).toMatchObject({ - address: usdcContractConfig.address, + expect(rpcRequestResult).toHaveLength(1); + expect(rpcRequestResult[0]).toMatchObject({ + request: "0x123", result: "0x789", }); - await eventStore.insertContractReadResult({ - address: usdcContractConfig.address, + await eventStore.insertRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789123", }); - const contractReadResultsUpdated = await eventStore.db - .selectFrom("contractReadResults") - .select(["address", "result"]) + const rpcRequestResultsUpdated = await eventStore.db + .selectFrom("rpcRequestResults") + .selectAll() .execute(); - expect(contractReadResultsUpdated).toHaveLength(1); - expect(contractReadResultsUpdated[0]).toMatchObject({ - address: usdcContractConfig.address, + expect(rpcRequestResultsUpdated).toHaveLength(1); + expect(rpcRequestResultsUpdated[0]).toMatchObject({ + request: "0x123", result: "0x789123", }); }); -test.skip("getContractReadResult returns data", async (context) => { +test("getRpcRequestResult returns data", async (context) => { const { eventStore } = context; - await eventStore.insertContractReadResult({ - address: usdcContractConfig.address, + await eventStore.insertRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789", }); - const contractReadResult = await eventStore.getContractReadResult({ - address: usdcContractConfig.address, + const rpcRequestResult = await eventStore.getRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, }); - expect(contractReadResult).toMatchObject({ - address: usdcContractConfig.address, + expect(rpcRequestResult).toMatchObject({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789", }); }); -test.skip("getContractReadResult returns null if not found", async (context) => { +test("getRpcRequestResult returns null if not found", async (context) => { const { eventStore } = context; - await eventStore.insertContractReadResult({ - address: usdcContractConfig.address, + await eventStore.insertRpcRequestResult({ chainId: 1, - data: "0x123", + request: "0x123", blockNumber: 100n, result: "0x789", }); - const contractReadResult = await eventStore.getContractReadResult({ - address: usdcContractConfig.address, + const rpcRequestResult = await eventStore.getRpcRequestResult({ + request: "0x125", chainId: 1, - data: "0x125", blockNumber: 100n, }); - expect(contractReadResult).toBe(null); + expect(rpcRequestResult).toBe(null); }); test("getLogEvents returns log events", async (context) => { diff --git a/packages/core/src/indexing/getBalance.test.ts b/packages/core/src/indexing/getBalance.test.ts index f6bb10741..3d70910ae 100644 --- a/packages/core/src/indexing/getBalance.test.ts +++ b/packages/core/src/indexing/getBalance.test.ts @@ -1,5 +1,5 @@ import { createClient, http } from "viem"; -import { beforeEach, expect, test } from "vitest"; +import { beforeEach, expect, test, vi } from "vitest"; import { setupEventStore } from "@/_test/setup"; import { anvil } from "@/_test/utils"; @@ -32,6 +32,8 @@ test("getBalance() with cache", async ({ eventStore }) => { transport: ponderTransport({ transport: http(), eventStore }), }); + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + const getBalance = buildGetBalance({ getCurrentBlockNumber: () => 16375000n, }); @@ -47,4 +49,6 @@ test("getBalance() with cache", async ({ eventStore }) => { }); expect(balance).toBe(398806329552690329n); + + expect(callSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/core/src/indexing/getBytecode.test.ts b/packages/core/src/indexing/getBytecode.test.ts new file mode 100644 index 000000000..667ffdfbb --- /dev/null +++ b/packages/core/src/indexing/getBytecode.test.ts @@ -0,0 +1,62 @@ +import { createClient, http, keccak256 } from "viem"; +import { beforeEach, expect, test, vi } from "vitest"; + +import { usdcContractConfig } from "@/_test/constants"; +import { setupEventStore } from "@/_test/setup"; +import { anvil } from "@/_test/utils"; + +import { buildGetBytecode } from "./getBytecode"; +import { ponderTransport } from "./transport"; + +beforeEach((context) => setupEventStore(context)); + +test("getBytecode() no cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const getBytecode = buildGetBytecode({ + getCurrentBlockNumber: () => 16375000n, + }); + + const bytecode = await getBytecode(client, { + address: usdcContractConfig.address, + }); + + expect(bytecode).toBeTruthy(); + expect(keccak256(bytecode!)).toBe( + "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + ); +}); + +test("getBytecode() with cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const getBytecode = buildGetBytecode({ + getCurrentBlockNumber: () => 16375000n, + }); + + let bytecode = await getBytecode(client, { + address: usdcContractConfig.address, + }); + + expect(bytecode).toBeTruthy(); + expect(keccak256(bytecode!)).toBe( + "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + ); + bytecode = await getBytecode(client, { + address: usdcContractConfig.address, + }); + + expect(bytecode).toBeTruthy(); + expect(keccak256(bytecode!)).toBe( + "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + ); + expect(callSpy).toHaveBeenCalledTimes(1); +}); diff --git a/packages/core/src/indexing/getBytecode.ts b/packages/core/src/indexing/getBytecode.ts new file mode 100644 index 000000000..370b844c2 --- /dev/null +++ b/packages/core/src/indexing/getBytecode.ts @@ -0,0 +1,24 @@ +import { + Chain, + Client, + GetBytecodeParameters, + GetBytecodeReturnType, + Transport, +} from "viem"; +import { getBytecode } from "viem/actions"; + +/** + * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/getBytecode.html getBytecode} function, + * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. + */ +export const buildGetBytecode = + ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => + async ( + client: Client, + args: Omit + ): Promise => { + return getBytecode(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }); + }; diff --git a/packages/core/src/indexing/multicall.test.ts b/packages/core/src/indexing/multicall.test.ts index 7b9aa83e1..e67220071 100644 --- a/packages/core/src/indexing/multicall.test.ts +++ b/packages/core/src/indexing/multicall.test.ts @@ -1,5 +1,5 @@ import { createClient, http } from "viem"; -import { beforeEach, expect, test } from "vitest"; +import { beforeEach, expect, test, vi } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; @@ -33,7 +33,7 @@ test("multicall() no cache", async ({ eventStore }) => { ], }); - expect(totalSupply).toBe(usdcTotalSupply16375000); + expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); }); test("multicall() with cache", async ({ eventStore }) => { @@ -42,6 +42,8 @@ test("multicall() with cache", async ({ eventStore }) => { transport: ponderTransport({ transport: http(), eventStore }), }); + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + const multicall = buildMulticall({ getCurrentBlockNumber: () => 16375000n, }); @@ -71,4 +73,5 @@ test("multicall() with cache", async ({ eventStore }) => { }); expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); + expect(callSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/core/src/indexing/readContract.test.ts b/packages/core/src/indexing/readContract.test.ts index e7dde4413..cc8fcdd79 100644 --- a/packages/core/src/indexing/readContract.test.ts +++ b/packages/core/src/indexing/readContract.test.ts @@ -1,5 +1,5 @@ import { createClient, http } from "viem"; -import { beforeEach, expect, test } from "vitest"; +import { beforeEach, expect, test, vi } from "vitest"; import { usdcContractConfig } from "@/_test/constants"; import { setupEventStore } from "@/_test/setup"; @@ -37,6 +37,8 @@ test("readContract() with cache", async ({ eventStore }) => { transport: ponderTransport({ transport: http(), eventStore }), }); + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + const readContract = buildReadContract({ getCurrentBlockNumber: () => 16375000n, }); @@ -56,4 +58,5 @@ test("readContract() with cache", async ({ eventStore }) => { }); expect(totalSupply).toBe(usdcTotalSupply16375000); + expect(callSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/core/src/indexing/transport.test.ts b/packages/core/src/indexing/transport.test.ts deleted file mode 100644 index 3a6adc800..000000000 --- a/packages/core/src/indexing/transport.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { test } from "vitest"; - -test.todo("request no cache", async () => {}); - -test.todo("request with cache", async () => {}); diff --git a/packages/core/src/indexing/transport.ts b/packages/core/src/indexing/transport.ts index 9648a589d..3c124a194 100644 --- a/packages/core/src/indexing/transport.ts +++ b/packages/core/src/indexing/transport.ts @@ -1,6 +1,7 @@ import { Address, custom, Hex, Transport } from "viem"; import { EventStore } from "@/event-store/store"; +import { toLowerCase } from "@/utils/lowercase"; export const ponderTransport = ({ transport, @@ -15,7 +16,6 @@ export const ponderTransport = ({ const c = custom({ async request({ method, params }) { const body = { method, params }; - console.log(body); let request: string | null = null; let blockNumber: bigint | null = null; @@ -30,7 +30,12 @@ export const ponderTransport = ({ } else if (method === "eth_getBalance") { const [address, _blockNumber] = params as [Address, Hex]; - request = `${method as string}_${address}`; + request = `${method as string}_${toLowerCase(address)}`; + blockNumber = BigInt(_blockNumber); + } else if (method === "eth_getCode") { + const [address, _blockNumber] = params as [Address, Hex]; + + request = `${method as string}_${toLowerCase(address)}`; blockNumber = BigInt(_blockNumber); } From 1b22da3a415d1c5224482b2ed2ddc1c57c86747a Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 17:04:56 -0500 Subject: [PATCH 39/44] update event store for postgrest and migrations --- .../core/src/event-store/postgres/format.ts | 9 ++- .../event-store/postgres/migrations.test.ts | 21 ++----- .../src/event-store/postgres/migrations.ts | 25 ++++++++ .../core/src/event-store/postgres/store.ts | 33 +++++----- .../core/src/event-store/sqlite/format.ts | 4 +- .../src/event-store/sqlite/migrations.test.ts | 21 ++----- .../core/src/event-store/sqlite/migrations.ts | 14 ++++- packages/core/src/index.ts | 1 - .../core/src/indexing/getStorageAt.test.ts | 62 +++++++++++++++++++ packages/core/src/indexing/getStorageAt.ts | 24 +++++++ packages/core/src/indexing/transport.ts | 11 +++- 11 files changed, 164 insertions(+), 61 deletions(-) create mode 100644 packages/core/src/indexing/getStorageAt.test.ts create mode 100644 packages/core/src/indexing/getStorageAt.ts diff --git a/packages/core/src/event-store/postgres/format.ts b/packages/core/src/event-store/postgres/format.ts index 69d29caff..bdd12a591 100644 --- a/packages/core/src/event-store/postgres/format.ts +++ b/packages/core/src/event-store/postgres/format.ts @@ -155,12 +155,11 @@ export function rpcToPostgresLog(log: RpcLog): Omit { }; } -type ContractReadResultsTable = { - address: Address; +type RpcRequestResultsTable = { blockNumber: bigint; chainId: number; - data: Hex; - result: Hex; + request: string; + result: string; }; type LogFiltersTable = { @@ -203,7 +202,7 @@ export type EventStoreTables = { blocks: BlocksTable; transactions: TransactionsTable; logs: LogsTable; - contractReadResults: ContractReadResultsTable; + rpcRequestResults: RpcRequestResultsTable; logFilters: LogFiltersTable; logFilterIntervals: LogFilterIntervalsTable; diff --git a/packages/core/src/event-store/postgres/migrations.test.ts b/packages/core/src/event-store/postgres/migrations.test.ts index 708d1b8c7..5f2f988db 100644 --- a/packages/core/src/event-store/postgres/migrations.test.ts +++ b/packages/core/src/event-store/postgres/migrations.test.ts @@ -17,7 +17,7 @@ import { beforeEach((context) => setupEventStore(context, { skipMigrateUp: true })); -const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { +const seed_2023_09_19_0_new_sync_design = async (db: Kysely) => { await db .insertInto("blocks") .values({ ...rpcToPostgresBlock(blockOne), chainId: 1 }) @@ -47,33 +47,22 @@ const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { .insertInto("contractReadResults") .values(contractReadResultOne) .execute(); - - await db - .insertInto("logFilterCachedRanges") - .values({ - filterKey: - '1-0x93d4c048f83bd7e37d49ea4c83a07267ec4203da-["0x1",null,"0x3"]', - startBlock: 16000010, - endBlock: 16000090, - endBlockTimestamp: 16000010, - }) - .execute(); }; -test("2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", async (context) => { +test("2023_09_19_0_new_sync_design -> 2023_11_06_0_new_rpc_cache_design succeeds", async (context) => { const { eventStore } = context; if (eventStore.kind !== "postgres") return; const { error } = await eventStore.migrator.migrateTo( - "2023_07_24_0_drop_finalized" + "2023_09_19_0_new_sync_design" ); expect(error).toBeFalsy(); - await seed_2023_07_24_0_drop_finalized(eventStore.db); + await seed_2023_09_19_0_new_sync_design(eventStore.db); const { error: latestError } = await eventStore.migrator.migrateTo( - "2023_09_19_0_new_sync_design" + "2023_11_06_0_new_rpc_cache_design" ); expect(latestError).toBeFalsy(); }, 15_000); diff --git a/packages/core/src/event-store/postgres/migrations.ts b/packages/core/src/event-store/postgres/migrations.ts index 2b556ac3d..2f349c9e5 100644 --- a/packages/core/src/event-store/postgres/migrations.ts +++ b/packages/core/src/event-store/postgres/migrations.ts @@ -349,6 +349,31 @@ const migrations: Record = { .execute(); }, }, + ["2023_11_06_0_new_rpc_cache_design"]: { + async up(db: Kysely) { + await db.schema.dropTable("contractReadResults").execute(); + + /** + * request format + * eth_call: eth_call_{to}_{data} + * eth_getBalance: eth_getBalance_{address} + * eth_getCode: eth_getCode_{address} + * eth_getStorageAt: eth_getStorageAt_{address}_{slot} + */ + await db.schema + .createTable("rpcRequestResults") + .addColumn("request", "text", (col) => col.notNull()) + .addColumn("blockNumber", "numeric(78, 0)", (col) => col.notNull()) + .addColumn("chainId", "integer", (col) => col.notNull()) + .addColumn("result", "text", (col) => col.notNull()) + .addPrimaryKeyConstraint("rpcRequestResultPrimaryKey", [ + "request", + "chainId", + "blockNumber", + ]) + .execute(); + }, + }, }; class StaticMigrationProvider implements MigrationProvider { diff --git a/packages/core/src/event-store/postgres/store.ts b/packages/core/src/event-store/postgres/store.ts index 8a85b0156..6964e2b0f 100644 --- a/packages/core/src/event-store/postgres/store.ts +++ b/packages/core/src/event-store/postgres/store.ts @@ -8,7 +8,7 @@ import { sql, } from "kysely"; import type { Pool } from "pg"; -import type { Address, Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; +import type { Hex, RpcBlock, RpcLog, RpcTransaction } from "viem"; import type { FactoryCriteria, LogFilterCriteria } from "@/config/sources"; import type { Block } from "@/types/block"; @@ -579,7 +579,7 @@ export class PostgresEventStore implements EventStore { .where("blockNumber", ">", fromBlock) .execute(); await tx - .deleteFrom("contractReadResults") + .deleteFrom("rpcRequestResults") .where("chainId", "=", chainId) .where("blockNumber", ">", fromBlock) .execute(); @@ -723,46 +723,41 @@ export class PostgresEventStore implements EventStore { ); }; - insertContractReadResult = async ({ - address, + insertRpcRequestResult = async ({ + request, blockNumber, chainId, - data, result, }: { - address: Address; + request: string; blockNumber: bigint; chainId: number; - data: Hex; - result: Hex; + result: string; }) => { await this.db - .insertInto("contractReadResults") - .values({ address, blockNumber, chainId, data, result }) + .insertInto("rpcRequestResults") + .values({ request, blockNumber, chainId, result }) .onConflict((oc) => - oc.constraint("contractReadResultPrimaryKey").doUpdateSet({ result }) + oc.constraint("rpcRequestResultPrimaryKey").doUpdateSet({ result }) ) .execute(); }; - getContractReadResult = async ({ - address, + getRpcRequestResult = async ({ + request, blockNumber, chainId, - data, }: { - address: Address; + request: string; blockNumber: bigint; chainId: number; - data: Hex; }) => { const contractReadResult = await this.db - .selectFrom("contractReadResults") + .selectFrom("rpcRequestResults") .selectAll() - .where("address", "=", address) + .where("request", "=", request) .where("blockNumber", "=", blockNumber) .where("chainId", "=", chainId) - .where("data", "=", data) .executeTakeFirst(); return contractReadResult ?? null; diff --git a/packages/core/src/event-store/sqlite/format.ts b/packages/core/src/event-store/sqlite/format.ts index a0d3667d0..1e47b0dd3 100644 --- a/packages/core/src/event-store/sqlite/format.ts +++ b/packages/core/src/event-store/sqlite/format.ts @@ -160,7 +160,7 @@ export function rpcToSqliteLog(log: RpcLog): Omit { }; } -type RpcRequestResults = { +type RpcRequestResultsTable = { blockNumber: BigIntText; chainId: number; result: string; @@ -207,7 +207,7 @@ export type EventStoreTables = { blocks: BlocksTable; transactions: TransactionsTable; logs: LogsTable; - rpcRequestResults: RpcRequestResults; + rpcRequestResults: RpcRequestResultsTable; logFilters: LogFiltersTable; logFilterIntervals: LogFilterIntervalsTable; diff --git a/packages/core/src/event-store/sqlite/migrations.test.ts b/packages/core/src/event-store/sqlite/migrations.test.ts index 4a2cc939f..4b3b8cdaf 100644 --- a/packages/core/src/event-store/sqlite/migrations.test.ts +++ b/packages/core/src/event-store/sqlite/migrations.test.ts @@ -17,7 +17,7 @@ import { beforeEach((context) => setupEventStore(context, { skipMigrateUp: true })); -const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { +const seed_2023_09_19_0_new_sync_design = async (db: Kysely) => { await db .insertInto("blocks") .values({ ...rpcToSqliteBlock(blockOne), chainId: 1 }) @@ -47,35 +47,24 @@ const seed_2023_07_24_0_drop_finalized = async (db: Kysely) => { .insertInto("contractReadResults") .values(contractReadResultOne) .execute(); - - await db - .insertInto("logFilterCachedRanges") - .values({ - filterKey: - '1-0x93d4c048f83bd7e37d49ea4c83a07267ec4203da-["0x1",null,"0x3"]', - startBlock: 16000010, - endBlock: 16000090, - endBlockTimestamp: 16000010, - }) - .execute(); }; test( - "2023_07_24_0_drop_finalized -> 2023_09_19_0_new_sync_design succeeds", + "2023_09_19_0_new_sync_design -> 2023_11_06_0_new_rpc_cache_design succeeds", async (context) => { const { eventStore } = context; if (eventStore.kind !== "sqlite") return; const { error } = await eventStore.migrator.migrateTo( - "2023_07_24_0_drop_finalized" + "2023_09_19_0_new_sync_design" ); expect(error).toBeFalsy(); - await seed_2023_07_24_0_drop_finalized(eventStore.db); + await seed_2023_09_19_0_new_sync_design(eventStore.db); const { error: latestError } = await eventStore.migrator.migrateTo( - "2023_09_19_0_new_sync_design" + "2023_11_06_0_new_rpc_cache_design" ); expect(latestError).toBeFalsy(); }, diff --git a/packages/core/src/event-store/sqlite/migrations.ts b/packages/core/src/event-store/sqlite/migrations.ts index c094e3b97..8709940b3 100644 --- a/packages/core/src/event-store/sqlite/migrations.ts +++ b/packages/core/src/event-store/sqlite/migrations.ts @@ -353,12 +353,24 @@ const migrations: Record = { async up(db: Kysely) { await db.schema.dropTable("contractReadResults").execute(); + /** + * request format + * eth_call: eth_call_{to}_{data} + * eth_getBalance: eth_getBalance_{address} + * eth_getCode: eth_getCode_{address} + * eth_getStorageAt: eth_getStorageAt_{address}_{slot} + */ await db.schema .createTable("rpcRequestResults") - .addColumn("request", "text", (col) => col.notNull().primaryKey()) + .addColumn("request", "text", (col) => col.notNull()) .addColumn("blockNumber", "varchar(79)", (col) => col.notNull()) .addColumn("chainId", "integer", (col) => col.notNull()) .addColumn("result", "text", (col) => col.notNull()) + .addPrimaryKeyConstraint("rpcRequestResultPrimaryKey", [ + "request", + "chainId", + "blockNumber", + ]) .execute(); }, }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 09dc15b00..5b99670a8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,6 @@ export { PonderApp } from "@/build/functions"; export { createConfig } from "@/config/config"; export type { Block } from "@/types/block"; -export type { ReadOnlyContract } from "@/types/contract"; export type { Log } from "@/types/log"; export type { Model } from "@/types/model"; export type { Transaction } from "@/types/transaction"; diff --git a/packages/core/src/indexing/getStorageAt.test.ts b/packages/core/src/indexing/getStorageAt.test.ts new file mode 100644 index 000000000..4c170de2b --- /dev/null +++ b/packages/core/src/indexing/getStorageAt.test.ts @@ -0,0 +1,62 @@ +import { createClient, http, toHex } from "viem"; +import { beforeEach, expect, test, vi } from "vitest"; + +import { uniswapV3PoolFactoryConfig } from "@/_test/constants"; +import { setupEventStore } from "@/_test/setup"; +import { anvil } from "@/_test/utils"; + +import { buildGetStorageAt } from "./getStorageAt"; +import { ponderTransport } from "./transport"; + +beforeEach((context) => setupEventStore(context)); + +test("getStorageAt() no cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const getStorageAt = buildGetStorageAt({ + getCurrentBlockNumber: () => 16375000n, + }); + + const storage = await getStorageAt(client, { + address: uniswapV3PoolFactoryConfig.criteria.address, + slot: toHex(3), + }); + + expect(storage).toBe( + "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" + ); +}); + +test("getStorageAt() with cache", async ({ eventStore }) => { + const client = createClient({ + chain: anvil, + transport: ponderTransport({ transport: http(), eventStore }), + }); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const getStorageAt = buildGetStorageAt({ + getCurrentBlockNumber: () => 16375000n, + }); + + let storage = await getStorageAt(client, { + address: uniswapV3PoolFactoryConfig.criteria.address, + slot: toHex(3), + }); + + expect(storage).toBe( + "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" + ); + storage = await getStorageAt(client, { + address: uniswapV3PoolFactoryConfig.criteria.address, + slot: toHex(3), + }); + + expect(storage).toBe( + "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" + ); + expect(callSpy).toHaveBeenCalledTimes(1); +}); diff --git a/packages/core/src/indexing/getStorageAt.ts b/packages/core/src/indexing/getStorageAt.ts new file mode 100644 index 000000000..0c598ca57 --- /dev/null +++ b/packages/core/src/indexing/getStorageAt.ts @@ -0,0 +1,24 @@ +import { + Chain, + Client, + GetStorageAtParameters, + GetStorageAtReturnType, + Transport, +} from "viem"; +import { getStorageAt } from "viem/actions"; + +/** + * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/getStorageAt.html getStorageAt} function, + * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. + */ +export const buildGetStorageAt = + ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => + async ( + client: Client, + args: Omit + ): Promise => { + return getStorageAt(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }); + }; diff --git a/packages/core/src/indexing/transport.ts b/packages/core/src/indexing/transport.ts index 3c124a194..4941b3dba 100644 --- a/packages/core/src/indexing/transport.ts +++ b/packages/core/src/indexing/transport.ts @@ -25,7 +25,9 @@ export const ponderTransport = ({ Hex ]; - request = `${method as string}_${to}_${data}`; + request = `${method as string}_${toLowerCase(to)}_${toLowerCase( + data + )}`; blockNumber = BigInt(_blockNumber); } else if (method === "eth_getBalance") { const [address, _blockNumber] = params as [Address, Hex]; @@ -37,6 +39,13 @@ export const ponderTransport = ({ request = `${method as string}_${toLowerCase(address)}`; blockNumber = BigInt(_blockNumber); + } else if (method === "eth_getStorageAt") { + const [address, slot, _blockNumber] = params as [Address, Hex, Hex]; + + request = `${method as string}_${toLowerCase(address)}_${toLowerCase( + slot + )}`; + blockNumber = BigInt(_blockNumber); } if (request !== null && blockNumber !== null) { From 2160a120d166e5bd4da2c50f066bcf5fd1139d72 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 17:13:45 -0500 Subject: [PATCH 40/44] fix .only in test --- packages/core/src/config/sources.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/config/sources.test.ts b/packages/core/src/config/sources.test.ts index 2de42c252..161449038 100644 --- a/packages/core/src/config/sources.test.ts +++ b/packages/core/src/config/sources.test.ts @@ -76,7 +76,7 @@ test("buildSources() for duplicate event", () => { ]); }); -test.only("buildSources() builds topics for event with args", () => { +test("buildSources() builds topics for event with args", () => { const sources = buildSources({ config: createConfig({ networks: [ From a0ff18f4c1479e9c63d245b6359695281941c4cd Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Mon, 6 Nov 2023 17:54:52 -0500 Subject: [PATCH 41/44] remove passing function to config, update config tests --- packages/core/src/build/config.ts | 15 +--- packages/core/src/config/config.test-d.ts | 90 +++++++++++++++++++++++ packages/core/src/config/config.test.ts | 80 -------------------- packages/core/src/config/config.ts | 6 +- 4 files changed, 94 insertions(+), 97 deletions(-) delete mode 100644 packages/core/src/config/config.test.ts diff --git a/packages/core/src/build/config.ts b/packages/core/src/build/config.ts index 7ffd50c1c..626117f89 100644 --- a/packages/core/src/build/config.ts +++ b/packages/core/src/build/config.ts @@ -2,7 +2,6 @@ import { build, Plugin } from "esbuild"; import { existsSync, rmSync } from "node:fs"; import path from "node:path"; -import type { ResolvedConfig } from "@/config/config"; import { ensureDirExists } from "@/utils/exists"; /** @@ -69,10 +68,10 @@ export const buildConfig = async ({ configFile }: { configFile: string }) => { logLevel: "silent", }); - const { default: rawDefault, config: rawConfig } = require(buildFile); + const { default: rawDefault, config } = require(buildFile); rmSync(buildFile, { force: true }); - if (!rawConfig) { + if (!config) { if (rawDefault) { throw new Error( `Ponder config not found. ${path.basename( @@ -87,15 +86,7 @@ export const buildConfig = async ({ configFile }: { configFile: string }) => { ); } - let resolvedConfig: ResolvedConfig; - - if (typeof rawConfig === "function") { - resolvedConfig = await rawConfig(); - } else { - resolvedConfig = await rawConfig; - } - - return resolvedConfig; + return config; } catch (err) { rmSync(buildFile, { force: true }); throw err; diff --git a/packages/core/src/config/config.test-d.ts b/packages/core/src/config/config.test-d.ts index 46c973fc4..c2a5b11ad 100644 --- a/packages/core/src/config/config.test-d.ts +++ b/packages/core/src/config/config.test-d.ts @@ -1,6 +1,8 @@ +import { http } from "viem"; import { assertType, test } from "vitest"; import { + createConfig, FilterEvents, RecoverAbiEvent, ResolvedConfig, @@ -131,3 +133,91 @@ test("RecoverAbiEvent", () => { assertType(abiSimple[1]); }); + +test("createConfig() strict config names", () => { + const config = createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: [], + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); + + assertType(config.contracts[0].network); + assertType(config.networks); +}); + +test("createConfig() has strict events inferred from abi", () => { + const config = createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [{ name: "mainnet" }], + abi: abiWithSameEvent, + filter: { + event: [ + "Transfer", + "Approve(address indexed from, address indexed to, uint256 amount)", + ], + }, + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); + assertType< + readonly [ + "Transfer", + "Approve(address indexed from, address indexed to, uint256 amount)" + ] + >(config.contracts[0].filter.event); +}); + +test("createConfig() has strict arg types for event", () => { + const config = createConfig({ + networks: [ + { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, + ], + contracts: [ + { + name: "BaseRegistrarImplementation", + network: [ + { + name: "mainnet", + address: "0x", + filter: { event: "Approve", args: { from: "0x", to: "0x" } }, + }, + ], + abi: abiSimple, + filter: { + event: "Approve", + args: { + to: ["0x1", "0x2"], + }, + }, + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", + startBlock: 16370000, + endBlock: 16370020, + maxBlockRange: 10, + }, + ], + }); + + assertType< + { to?: `0x${string}` | `0x${string}`[] | null | undefined } | undefined + >(config.contracts[0].filter?.args); +}); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts deleted file mode 100644 index 48bdedc52..000000000 --- a/packages/core/src/config/config.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { http } from "viem"; -import { test } from "vitest"; - -import { createConfig } from "./config"; -import { abiSimple, abiWithSameEvent } from "./config.test-d"; - -test("createConfig enforces matching network names", () => { - createConfig({ - networks: [ - { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, - ], - contracts: [ - { - name: "BaseRegistrarImplementation", - network: [{ name: "mainnet" }], - abi: [], - address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - startBlock: 16370000, - endBlock: 16370020, - maxBlockRange: 10, - }, - ], - }); -}); - -test("createConfig() has strict events inferred from abi", () => { - createConfig({ - networks: [ - { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, - ], - contracts: [ - { - name: "BaseRegistrarImplementation", - network: [{ name: "mainnet" }], - abi: abiWithSameEvent, - filter: { - event: [ - "Transfer", - "Approve(address indexed from, address indexed to, uint256 amount)", - ], - }, - address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - startBlock: 16370000, - endBlock: 16370020, - maxBlockRange: 10, - }, - ], - }); -}); - -test("createConfig() has strict arg types for event", () => { - createConfig({ - networks: [ - { name: "mainnet", chainId: 1, transport: http("http://127.0.0.1:8545") }, - ], - contracts: [ - { - name: "BaseRegistrarImplementation", - network: [ - { - name: "mainnet", - address: "0x", - filter: { event: "Approve", args: { from: "0x", to: "0x" } }, - }, - ], - abi: abiSimple, - filter: { - event: "Approve", - args: { - to: ["0x1", "0x2"], - }, - }, - address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - startBlock: 16370000, - endBlock: 16370020, - maxBlockRange: 10, - }, - ], - }); -}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index e7b873679..4989d0558 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -224,9 +224,5 @@ export const createConfig = < options?: Option; } >( - config: - | TConfig - | Promise - | (() => TConfig) - | (() => Promise) + config: TConfig ) => config; From 069acd66fad227e4c4d5491305eb691c04a48634 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Tue, 7 Nov 2023 10:57:40 -0500 Subject: [PATCH 42/44] fixes for kevin --- packages/core/src/config/config.ts | 2 +- packages/core/src/config/sources.ts | 30 ++++++++++--------- .../src/event-store/postgres/migrations.ts | 3 +- .../core/src/event-store/sqlite/migrations.ts | 3 +- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 4989d0558..c73e5ba9c 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -85,7 +85,7 @@ type ContractFilter< > = ( | { /** Contract address. */ - address?: `0x${string}`; + address?: `0x${string}` | readonly `0x${string}`[]; } | { /** Factory contract configuration. */ diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 7d20c0c8a..81c22cd50 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -18,9 +18,7 @@ declare global { } /** - * There are up to 4 topics in an EVM event - * - * @todo Change this to a more strict type + * There are up to 4 topics in an EVM log, so given that this could be more strict. */ export type Topics = (Hex | Hex[] | null)[]; @@ -105,13 +103,19 @@ export const buildSources = ({ networkContract.maxBlockRange ?? contract.maxBlockRange, } as const; - if ("factory" in contract) { + // Check that factory and address are not both defined + const resolvedFactory = + ("factory" in networkContract && networkContract.factory) || + ("factory" in contract && contract.factory); + const resolvedAddress = + ("address" in networkContract && networkContract.address) || + ("address" in contract && contract.address); + if (resolvedFactory && resolvedAddress) + throw Error("Factory and address cannot both be defined"); + + if (resolvedFactory) { // factory - const resolvedFactory = - ("factory" in networkContract && networkContract.factory) || - contract.factory; - return { ...sharedSource, type: "factory", @@ -123,15 +127,13 @@ export const buildSources = ({ } else { // log filter - const resolvedAddress = - ("address" in networkContract && networkContract.address) || - contract.address; - return { ...sharedSource, type: "logFilter", criteria: { - address: resolvedAddress + address: Array.isArray(resolvedAddress) + ? resolvedAddress.map((r) => toLowerCase(r)) + : resolvedAddress ? toLowerCase(resolvedAddress) : undefined, topics, @@ -171,7 +173,7 @@ const buildTopics = ( }; /** - * Finds the abi event for the event string + * Finds the event ABI item for the safe event name. * * @param eventName Event name or event signature if there are collisions */ diff --git a/packages/core/src/event-store/postgres/migrations.ts b/packages/core/src/event-store/postgres/migrations.ts index 2f349c9e5..5d357d6f9 100644 --- a/packages/core/src/event-store/postgres/migrations.ts +++ b/packages/core/src/event-store/postgres/migrations.ts @@ -354,7 +354,8 @@ const migrations: Record = { await db.schema.dropTable("contractReadResults").execute(); /** - * request format + * Formatting for "request" field values: + * * eth_call: eth_call_{to}_{data} * eth_getBalance: eth_getBalance_{address} * eth_getCode: eth_getCode_{address} diff --git a/packages/core/src/event-store/sqlite/migrations.ts b/packages/core/src/event-store/sqlite/migrations.ts index 8709940b3..a2faa51d5 100644 --- a/packages/core/src/event-store/sqlite/migrations.ts +++ b/packages/core/src/event-store/sqlite/migrations.ts @@ -354,7 +354,8 @@ const migrations: Record = { await db.schema.dropTable("contractReadResults").execute(); /** - * request format + * Formatting for "request" field values: + * * eth_call: eth_call_{to}_{data} * eth_getBalance: eth_getBalance_{address} * eth_getCode: eth_getCode_{address} From 20a4b2e11022f6c843cd31e05aa2ed40dc896f12 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Tue, 7 Nov 2023 11:51:44 -0500 Subject: [PATCH 43/44] refactor actions using decorator and add low level transport tests --- packages/core/src/indexing/getBalance.test.ts | 54 ------ packages/core/src/indexing/getBalance.ts | 24 --- .../core/src/indexing/getBytecode.test.ts | 62 ------- packages/core/src/indexing/getBytecode.ts | 24 --- .../core/src/indexing/getStorageAt.test.ts | 62 ------- packages/core/src/indexing/getStorageAt.ts | 24 --- packages/core/src/indexing/multicall.test.ts | 77 -------- packages/core/src/indexing/multicall.ts | 32 ---- .../core/src/indexing/ponderActions.test.ts | 69 ++++++++ packages/core/src/indexing/ponderActions.ts | 78 ++++++++ .../core/src/indexing/readContract.test.ts | 62 ------- packages/core/src/indexing/readContract.ts | 32 ---- packages/core/src/indexing/transport.test.ts | 166 ++++++++++++++++++ 13 files changed, 313 insertions(+), 453 deletions(-) delete mode 100644 packages/core/src/indexing/getBalance.test.ts delete mode 100644 packages/core/src/indexing/getBalance.ts delete mode 100644 packages/core/src/indexing/getBytecode.test.ts delete mode 100644 packages/core/src/indexing/getBytecode.ts delete mode 100644 packages/core/src/indexing/getStorageAt.test.ts delete mode 100644 packages/core/src/indexing/getStorageAt.ts delete mode 100644 packages/core/src/indexing/multicall.test.ts delete mode 100644 packages/core/src/indexing/multicall.ts create mode 100644 packages/core/src/indexing/ponderActions.test.ts create mode 100644 packages/core/src/indexing/ponderActions.ts delete mode 100644 packages/core/src/indexing/readContract.test.ts delete mode 100644 packages/core/src/indexing/readContract.ts create mode 100644 packages/core/src/indexing/transport.test.ts diff --git a/packages/core/src/indexing/getBalance.test.ts b/packages/core/src/indexing/getBalance.test.ts deleted file mode 100644 index 3d70910ae..000000000 --- a/packages/core/src/indexing/getBalance.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createClient, http } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { setupEventStore } from "@/_test/setup"; -import { anvil } from "@/_test/utils"; - -import { buildGetBalance } from "./getBalance"; -import { ponderTransport } from "./transport"; - -beforeEach((context) => setupEventStore(context)); - -test("getBalance() no cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const getBalance = buildGetBalance({ - getCurrentBlockNumber: () => 16375000n, - }); - - const balance = await getBalance(client, { - address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", - }); - - expect(balance).toBe(398806329552690329n); -}); - -test("getBalance() with cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); - - const getBalance = buildGetBalance({ - getCurrentBlockNumber: () => 16375000n, - }); - - let balance = await getBalance(client, { - address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", - }); - - expect(balance).toBe(398806329552690329n); - - balance = await getBalance(client, { - address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", - }); - - expect(balance).toBe(398806329552690329n); - - expect(callSpy).toHaveBeenCalledTimes(1); -}); diff --git a/packages/core/src/indexing/getBalance.ts b/packages/core/src/indexing/getBalance.ts deleted file mode 100644 index c184fe552..000000000 --- a/packages/core/src/indexing/getBalance.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - Chain, - Client, - GetBalanceParameters, - GetBalanceReturnType, - Transport, -} from "viem"; -import { getBalance } from "viem/actions"; - -/** - * Build a function with the same api as viem's {@link https://viem.sh/docs/actions/public/getBalance.html getBalance} function, - * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. - */ -export const buildGetBalance = - ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => - async ( - client: Client, - args: Omit - ): Promise => { - return getBalance(client, { - ...args, - blockNumber: getCurrentBlockNumber(), - }); - }; diff --git a/packages/core/src/indexing/getBytecode.test.ts b/packages/core/src/indexing/getBytecode.test.ts deleted file mode 100644 index 667ffdfbb..000000000 --- a/packages/core/src/indexing/getBytecode.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createClient, http, keccak256 } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { usdcContractConfig } from "@/_test/constants"; -import { setupEventStore } from "@/_test/setup"; -import { anvil } from "@/_test/utils"; - -import { buildGetBytecode } from "./getBytecode"; -import { ponderTransport } from "./transport"; - -beforeEach((context) => setupEventStore(context)); - -test("getBytecode() no cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const getBytecode = buildGetBytecode({ - getCurrentBlockNumber: () => 16375000n, - }); - - const bytecode = await getBytecode(client, { - address: usdcContractConfig.address, - }); - - expect(bytecode).toBeTruthy(); - expect(keccak256(bytecode!)).toBe( - "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" - ); -}); - -test("getBytecode() with cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); - - const getBytecode = buildGetBytecode({ - getCurrentBlockNumber: () => 16375000n, - }); - - let bytecode = await getBytecode(client, { - address: usdcContractConfig.address, - }); - - expect(bytecode).toBeTruthy(); - expect(keccak256(bytecode!)).toBe( - "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" - ); - bytecode = await getBytecode(client, { - address: usdcContractConfig.address, - }); - - expect(bytecode).toBeTruthy(); - expect(keccak256(bytecode!)).toBe( - "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" - ); - expect(callSpy).toHaveBeenCalledTimes(1); -}); diff --git a/packages/core/src/indexing/getBytecode.ts b/packages/core/src/indexing/getBytecode.ts deleted file mode 100644 index 370b844c2..000000000 --- a/packages/core/src/indexing/getBytecode.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - Chain, - Client, - GetBytecodeParameters, - GetBytecodeReturnType, - Transport, -} from "viem"; -import { getBytecode } from "viem/actions"; - -/** - * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/getBytecode.html getBytecode} function, - * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. - */ -export const buildGetBytecode = - ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => - async ( - client: Client, - args: Omit - ): Promise => { - return getBytecode(client, { - ...args, - blockNumber: getCurrentBlockNumber(), - }); - }; diff --git a/packages/core/src/indexing/getStorageAt.test.ts b/packages/core/src/indexing/getStorageAt.test.ts deleted file mode 100644 index 4c170de2b..000000000 --- a/packages/core/src/indexing/getStorageAt.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createClient, http, toHex } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { uniswapV3PoolFactoryConfig } from "@/_test/constants"; -import { setupEventStore } from "@/_test/setup"; -import { anvil } from "@/_test/utils"; - -import { buildGetStorageAt } from "./getStorageAt"; -import { ponderTransport } from "./transport"; - -beforeEach((context) => setupEventStore(context)); - -test("getStorageAt() no cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const getStorageAt = buildGetStorageAt({ - getCurrentBlockNumber: () => 16375000n, - }); - - const storage = await getStorageAt(client, { - address: uniswapV3PoolFactoryConfig.criteria.address, - slot: toHex(3), - }); - - expect(storage).toBe( - "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" - ); -}); - -test("getStorageAt() with cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); - - const getStorageAt = buildGetStorageAt({ - getCurrentBlockNumber: () => 16375000n, - }); - - let storage = await getStorageAt(client, { - address: uniswapV3PoolFactoryConfig.criteria.address, - slot: toHex(3), - }); - - expect(storage).toBe( - "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" - ); - storage = await getStorageAt(client, { - address: uniswapV3PoolFactoryConfig.criteria.address, - slot: toHex(3), - }); - - expect(storage).toBe( - "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" - ); - expect(callSpy).toHaveBeenCalledTimes(1); -}); diff --git a/packages/core/src/indexing/getStorageAt.ts b/packages/core/src/indexing/getStorageAt.ts deleted file mode 100644 index 0c598ca57..000000000 --- a/packages/core/src/indexing/getStorageAt.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - Chain, - Client, - GetStorageAtParameters, - GetStorageAtReturnType, - Transport, -} from "viem"; -import { getStorageAt } from "viem/actions"; - -/** - * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/getStorageAt.html getStorageAt} function, - * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. - */ -export const buildGetStorageAt = - ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => - async ( - client: Client, - args: Omit - ): Promise => { - return getStorageAt(client, { - ...args, - blockNumber: getCurrentBlockNumber(), - }); - }; diff --git a/packages/core/src/indexing/multicall.test.ts b/packages/core/src/indexing/multicall.test.ts deleted file mode 100644 index e67220071..000000000 --- a/packages/core/src/indexing/multicall.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { createClient, http } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { usdcContractConfig } from "@/_test/constants"; -import { setupEventStore } from "@/_test/setup"; -import { anvil } from "@/_test/utils"; - -import { buildMulticall } from "./multicall"; -import { ponderTransport } from "./transport"; - -beforeEach((context) => setupEventStore(context)); - -const usdcTotalSupply16375000 = 40921687992499550n; - -test("multicall() no cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const multicall = buildMulticall({ - getCurrentBlockNumber: () => 16375000n, - }); - - const totalSupply = await multicall(client, { - allowFailure: false, - contracts: [ - { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }, - ], - }); - - expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); -}); - -test("multicall() with cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); - - const multicall = buildMulticall({ - getCurrentBlockNumber: () => 16375000n, - }); - - let totalSupply = await multicall(client, { - allowFailure: false, - contracts: [ - { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }, - ], - }); - - expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); - - totalSupply = await multicall(client, { - allowFailure: false, - contracts: [ - { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }, - ], - }); - - expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); - expect(callSpy).toHaveBeenCalledTimes(1); -}); diff --git a/packages/core/src/indexing/multicall.ts b/packages/core/src/indexing/multicall.ts deleted file mode 100644 index 83c445641..000000000 --- a/packages/core/src/indexing/multicall.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Chain, - Client, - ContractFunctionConfig, - MulticallParameters, - MulticallReturnType, - Transport, -} from "viem"; -import { multicall } from "viem/actions"; - -/** - * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/multicall.html multicall} function, - * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. - */ -export const buildMulticall = - ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => - async < - TChain extends Chain | undefined, - TContracts extends ContractFunctionConfig[], - TAllowFailure extends boolean = true - >( - client: Client, - args: Omit< - MulticallParameters, - "blockTag" | "blockNumber" - > - ): Promise> => { - return multicall(client, { - ...args, - blockNumber: getCurrentBlockNumber(), - }); - }; diff --git a/packages/core/src/indexing/ponderActions.test.ts b/packages/core/src/indexing/ponderActions.test.ts new file mode 100644 index 000000000..6480b804e --- /dev/null +++ b/packages/core/src/indexing/ponderActions.test.ts @@ -0,0 +1,69 @@ +import { keccak256, toHex } from "viem/utils"; +import { expect, test } from "vitest"; + +import { + uniswapV3PoolFactoryConfig, + usdcContractConfig, +} from "@/_test/constants"; +import { publicClient } from "@/_test/utils"; + +import { ponderActions } from "./ponderActions"; + +const client = publicClient.extend(ponderActions(() => 16375000n)); + +const usdcTotalSupply16375000 = 40921687992499550n; + +test("getBalance()", async () => { + const balance = await client.getBalance({ + address: "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + }); + + expect(balance).toBe(398806329552690329n); +}); + +test("getBytecode()", async () => { + const bytecode = await client.getBytecode({ + address: usdcContractConfig.address, + }); + + expect(bytecode).toBeTruthy(); + expect(keccak256(bytecode!)).toBe( + "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + ); +}); + +test("getStorageAt()", async () => { + const storage = await client.getStorageAt({ + address: uniswapV3PoolFactoryConfig.criteria.address, + slot: toHex(3), + }); + + expect(storage).toBe( + "0x0000000000000000000000001a9c8182c09f50c8318d769245bea52c32be35bc" + ); +}); + +test("multicall()", async () => { + const totalSupply = await client.multicall({ + allowFailure: false, + contracts: [ + { + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }, + ], + }); + + expect(totalSupply).toMatchObject([usdcTotalSupply16375000]); +}); + +test("readContract()", async () => { + const totalSupply = await client.readContract({ + abi: usdcContractConfig.abi, + functionName: "totalSupply", + address: usdcContractConfig.address, + }); + + expect(totalSupply).toBe(usdcTotalSupply16375000); +}); diff --git a/packages/core/src/indexing/ponderActions.ts b/packages/core/src/indexing/ponderActions.ts new file mode 100644 index 000000000..722e9d14c --- /dev/null +++ b/packages/core/src/indexing/ponderActions.ts @@ -0,0 +1,78 @@ +import { + Abi, + Chain, + Client, + ContractFunctionConfig, + GetBalanceParameters, + GetBalanceReturnType, + GetBytecodeParameters, + GetBytecodeReturnType, + GetStorageAtParameters, + GetStorageAtReturnType, + MulticallParameters, + MulticallReturnType, + ReadContractParameters, + ReadContractReturnType, + Transport, +} from "viem"; +import { + getBalance as viemGetBalance, + getBytecode as viemGetBytecode, + getStorageAt as viemGetStorageAt, + multicall as viemMulticall, + readContract as viemReadContract, +} from "viem/actions"; + +export const ponderActions = + (getCurrentBlockNumber: () => bigint) => + ( + client: Client + ) => ({ + getBalance: ( + args: Omit + ): Promise => + viemGetBalance(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }), + getBytecode: ( + args: Omit + ): Promise => + viemGetBytecode(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }), + getStorageAt: ( + args: Omit + ): Promise => + viemGetStorageAt(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }), + multicall: < + TContracts extends ContractFunctionConfig[], + TAllowFailure extends boolean = true + >( + args: Omit< + MulticallParameters, + "blockTag" | "blockNumber" + > + ): Promise> => + viemMulticall(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + }), + readContract: < + TAbi extends Abi | readonly unknown[], + TFunctionName extends string + >( + args: Omit< + ReadContractParameters, + "blockTag" | "blockNumber" + > + ): Promise> => + viemReadContract(client, { + ...args, + blockNumber: getCurrentBlockNumber(), + } as ReadContractParameters), + }); diff --git a/packages/core/src/indexing/readContract.test.ts b/packages/core/src/indexing/readContract.test.ts deleted file mode 100644 index cc8fcdd79..000000000 --- a/packages/core/src/indexing/readContract.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createClient, http } from "viem"; -import { beforeEach, expect, test, vi } from "vitest"; - -import { usdcContractConfig } from "@/_test/constants"; -import { setupEventStore } from "@/_test/setup"; -import { anvil } from "@/_test/utils"; - -import { buildReadContract } from "./readContract"; -import { ponderTransport } from "./transport"; - -beforeEach((context) => setupEventStore(context)); - -const usdcTotalSupply16375000 = 40921687992499550n; - -test("readContract() no cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const readContract = buildReadContract({ - getCurrentBlockNumber: () => 16375000n, - }); - - const totalSupply = await readContract(client, { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }); - - expect(totalSupply).toBe(usdcTotalSupply16375000); -}); - -test("readContract() with cache", async ({ eventStore }) => { - const client = createClient({ - chain: anvil, - transport: ponderTransport({ transport: http(), eventStore }), - }); - - const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); - - const readContract = buildReadContract({ - getCurrentBlockNumber: () => 16375000n, - }); - - let totalSupply = await readContract(client, { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }); - - expect(totalSupply).toBe(usdcTotalSupply16375000); - - totalSupply = await readContract(client, { - abi: usdcContractConfig.abi, - functionName: "totalSupply", - address: usdcContractConfig.address, - }); - - expect(totalSupply).toBe(usdcTotalSupply16375000); - expect(callSpy).toHaveBeenCalledTimes(1); -}); diff --git a/packages/core/src/indexing/readContract.ts b/packages/core/src/indexing/readContract.ts deleted file mode 100644 index f3ea268b9..000000000 --- a/packages/core/src/indexing/readContract.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - Abi, - Chain, - Client, - ReadContractParameters, - ReadContractReturnType, - Transport, -} from "viem"; -import { readContract } from "viem/actions"; - -/** - * Build a function with the same api as viem's {@link https://viem.sh/docs/contract/readContract.html readContract} function, - * but removes `blockTag` and `blockNumber`, overriding `blockNumber`. - */ -export const buildReadContract = - ({ getCurrentBlockNumber }: { getCurrentBlockNumber: () => bigint }) => - async < - TChain extends Chain | undefined, - TAbi extends Abi | readonly unknown[], - TFunctionName extends string - >( - client: Client, - args: Omit< - ReadContractParameters, - "blockTag" | "blockNumber" - > - ): Promise> => { - return readContract(client, { - ...args, - blockNumber: getCurrentBlockNumber(), - } as unknown as ReadContractParameters); - }; diff --git a/packages/core/src/indexing/transport.test.ts b/packages/core/src/indexing/transport.test.ts new file mode 100644 index 000000000..a3ffeab3e --- /dev/null +++ b/packages/core/src/indexing/transport.test.ts @@ -0,0 +1,166 @@ +import { getFunctionSelector, http, toHex, Transport } from "viem"; +import { assertType, beforeEach, expect, test, vi } from "vitest"; + +import { usdcContractConfig } from "@/_test/constants"; +import { setupEventStore } from "@/_test/setup"; +import { anvil } from "@/_test/utils"; + +import { ponderTransport } from "./transport"; + +beforeEach((context) => setupEventStore(context)); + +test("default", ({ eventStore }) => { + const transport = ponderTransport({ + transport: http("https://mockapi.com/rpc"), + eventStore, + }); + + assertType(transport); + + expect(transport({})).toMatchInlineSnapshot(` + { + "config": { + "key": "custom", + "name": "Custom Provider", + "request": [Function], + "retryCount": 3, + "retryDelay": 150, + "timeout": undefined, + "type": "custom", + }, + "request": [Function], + "value": undefined, + } + `); +}); + +test("eth_call", async ({ eventStore }) => { + const transport = ponderTransport({ + transport: http(), + eventStore, + })({ + chain: anvil, + }); + + const response1 = await transport.request({ + method: "eth_call", + params: [ + { + data: getFunctionSelector("totalSupply()"), + to: usdcContractConfig.address, + }, + toHex(16375000n), + ], + }); + + expect(response1).toBeDefined(); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const response2 = await transport.request({ + method: "eth_call", + params: [ + { + data: getFunctionSelector("totalSupply()"), + to: usdcContractConfig.address, + }, + toHex(16375000n), + ], + }); + + expect(response1).toBe(response2); + + expect(callSpy).toHaveBeenCalledTimes(0); +}); + +test("eth_getBalance", async ({ eventStore }) => { + const transport = ponderTransport({ + transport: http(), + eventStore, + })({ + chain: anvil, + }); + + const response1 = await transport.request({ + method: "eth_getBalance", + params: [usdcContractConfig.address, toHex(16375000n)], + }); + + expect(response1).toBeDefined(); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const response2 = await transport.request({ + method: "eth_getBalance", + params: [usdcContractConfig.address, toHex(16375000n)], + }); + + expect(response1).toBe(response2); + + expect(callSpy).toHaveBeenCalledTimes(0); +}); + +test("eth_getStorageAt", async ({ eventStore }) => { + const transport = ponderTransport({ + transport: http(), + eventStore, + })({ + chain: anvil, + }); + + const response1 = await transport.request({ + method: "eth_getStorageAt", + params: [usdcContractConfig.address, toHex(3), toHex(16375000n)], + }); + + expect(response1).toBeDefined(); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const response2 = await transport.request({ + method: "eth_getStorageAt", + params: [usdcContractConfig.address, toHex(3), toHex(16375000n)], + }); + + expect(response1).toBe(response2); + + expect(callSpy).toHaveBeenCalledTimes(0); +}); + +test("eth_getCode", async ({ eventStore }) => { + const transport = ponderTransport({ + transport: http(), + eventStore, + })({ + chain: anvil, + }); + + const response1 = await transport.request({ + method: "eth_getCode", + params: [usdcContractConfig.address, toHex(16375000n)], + }); + + expect(response1).toBeDefined(); + + const callSpy = vi.spyOn(eventStore, "insertRpcRequestResult"); + + const response2 = await transport.request({ + method: "eth_getCode", + params: [usdcContractConfig.address, toHex(16375000n)], + }); + + expect(response1).toBe(response2); + + expect(callSpy).toHaveBeenCalledTimes(0); +}); + +test("fallback method", async ({ eventStore }) => { + const transport = ponderTransport({ + transport: http(), + eventStore, + })({ + chain: anvil, + }); + + expect(await transport.request({ method: "eth_blockNumber" })).toBeDefined(); +}); From bee9a375c657687a40532ff8ab5ca16e4ef4a836 Mon Sep 17 00:00:00 2001 From: Kyle Scott Date: Tue, 7 Nov 2023 12:21:42 -0500 Subject: [PATCH 44/44] use more readable function getEventSelector() --- packages/core/src/config/sources.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/core/src/config/sources.ts b/packages/core/src/config/sources.ts index 81c22cd50..4afad74e4 100644 --- a/packages/core/src/config/sources.ts +++ b/packages/core/src/config/sources.ts @@ -1,5 +1,12 @@ import { AbiEvent, parseAbiItem } from "abitype"; -import { Abi, Address, encodeEventTopics, getAbiItem, Hex } from "viem"; +import { + Abi, + Address, + encodeEventTopics, + getAbiItem, + getEventSelector, + Hex, +} from "viem"; import { toLowerCase } from "@/utils/lowercase"; @@ -155,13 +162,7 @@ const buildTopics = ( if (Array.isArray(filter.event)) { // List of event signatures return [ - filter.event - .map((event) => - encodeEventTopics({ - abi: [findAbiEvent(abi, event)], - }) - ) - .flat(), + filter.event.map((event) => getEventSelector(findAbiEvent(abi, event))), ]; } else { // Single event with args