diff --git a/.dockerignore b/.dockerignore index fc89450..ee9d8ec 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,4 @@ dist README.md .prettierrc volumes -data \ No newline at end of file +data/providers.json \ No newline at end of file diff --git a/.env.example b/.env.example index 2670d38..a91a53d 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,9 @@ NODE_ENV="dev or production" RPC_HOST=127.0.0.1:8545 CHAIN="anvil or optimism or optimism-sepolia" -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres \ No newline at end of file +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres +PORT=3000 + +# PROVIDER_PRIVATE_KEY_= +# BILLING_PRIVATE_KEY_= +# OPERATOR_PRIVATE_KEY_= \ No newline at end of file diff --git a/README.md b/README.md index 4396006..fed61af 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,47 @@ -# Base Provider Template +# Create a new Protocol -This repository contains the base Provider Template for the people wants to create their own Product Categories in Forest Protocols. +Forest Network consists of a multitude of Protocols that are incentivized to accelerate digital innovation and prove their worth to the users by building in-demand services. Every digital service can become a Protocol within Forest Network. The diversity of Protocols together with Network's inherent interoperability is what adds up to its strength. + +The Network is permissionless and everyone is allowed to create a new Protocol. + +This repository contains instructions and code templates for innovators who want to create their own Protocols, grow them and earn passive income. What is required of a potential Protocol Owner is to: + +1. [Fork and edit the repository](#1-fork-and-edit-the-repository), +2. [Registering in the Network](#2-registering-in-the-network), + 1. [Register as a Protocol Owner](#21-register-as-a-protocol-owner), + 2. [Register a New Protocol](#22-register-a-new-protocol), +3. [Prepare the README file for Users and Providers](#3-prepare-the-readme-file-for-users-and-providers). +4. [Grow Your Protocol by Onboarding Providers, Validators and Users](#4-grow-your-protocol). ## Quickstart +As a Protocol Owner you want to make life easy for Providers that will be adding offers to your Protocol and servicing clients. That's why you need to create a Provider Template that each Provider will be running to deliver to its clients. We have already implemented all of the Network level functionality. The only thing you need to do is to define the Protocol specific code. + ### 1. Fork and edit the repository -Fork this repository and clone it locally. Open the `src/product-category/base-provider.ts` file. The first step is to define the details each resource will have. At the beginning of the file, there is a type definition named `ExampleProductDetails`, which specifies the attributes stored for each resource in this Product Category. +Fork this repository and clone it locally. Open the `src/protocol/base-provider.ts` file. The first step is to define the details each resource will have. At the beginning of the file, there is a type definition named `ExampleResourceDetails`, which specifies the attributes stored in the daemon's database for each resource in this Protocol. -For instance, if your product is a database or API service, it will likely include connection strings, API keys, or endpoints. +Details of a resource are most likely the data that would be useful for the Users to see or the configuration that has to be used internally in order to handle the resource. They can be accessible by Users unless you prefix the detail name with `_`. For instance, these details might include connection strings for a Database resource or endpoints and API keys for an API service resource. -Rename the type to match your product and edit the fields accordingly. An example type definition for the SQLite Product Category is shown below: +Rename the type to match your service and edit the fields accordingly. An example type definition for the SQLite Protocol is shown below: ```typescript -export type SQLiteDatabaseDetails = ResourceDetails & { +export type SQLiteDatabaseResourceDetails = ResourceDetails & { // Fields should use PascalCase with underscores for spaces Size_MB: number; // Database file size in MB - // Fields starting with an underscore are for internal use only and won't be seen by the users. + // Fields starting with an underscore are for internal use only and won't be seen by the Users. _fileName: string; // SQLite database file name }; ``` -Once you have defined the details type, update the `BaseExampleProductProvider` abstract class to define the product's functionality. Providers within this Product Category must implement all functions in this class. Rename the class to reflect your product. For example: +Once you have defined the details type, update the `BaseExampleServiceProvider` abstract class to define this protocol's supported methods / functionality. This is a set of actions that Users can request your Providers to complete if they have an active Agreement for a service in your PT. All Providers within this Protocol must implement all functions you define in this class. Rename the class to reflect your service. For example: ```typescript -export abstract class BaseSQLiteDatabaseProvider extends AbstractProvider { +export abstract class BaseSQLiteDatabaseServiceProvider extends AbstractServiceProvider { /** - * Defines the product's functionality. All functions below - * must be implemented by providers in this Product Category. + * Defines the services's functionality. All functions below + * must be implemented by Providers in this Protocol. */ /** @@ -41,7 +54,7 @@ export abstract class BaseSQLiteDatabaseProvider extends AbstractProvider "**_Pipe_**" is a simple abstraction layer for HTTP-like request-response communication between participants. The current Pipe implementation is built on [XMTP](https://xmtp.org/) for fully decentralized communication within the Protocol. @@ -81,7 +94,7 @@ async init(providerTag: string) { */ const body = validateBodyOrParams(req.body, z.object({ id: z.number(), // Resource ID - pc: addressSchema, // Product Category address + pt: addressSchema, // Protocol address query: z.string(), // SQL query })); @@ -97,7 +110,7 @@ async init(providerTag: string) { */ const { resource } = await this.getResource( body.id, - body.pc as Address, + body.pt as Address, req.requester ); @@ -113,14 +126,14 @@ async init(providerTag: string) { } ``` -Once you are done with defining the abstract class, navigate to `src/product-category/provider.ts` and add a boilerplate implementation for your base class. For example: +Once you are done with defining the abstract class, navigate to `src/protocol/provider.ts` and add a boilerplate implementation for your base class. For example: ```typescript /** - * The main class that implements provider specific actions. + * The main class that implements Provider specific actions. * @responsible Provider */ -export class MainProviderImplementation extends BaseExampleProductProvider { +export class MainProviderImplementation extends BaseExampleServiceProvider { // Other abstract functions... async sqlQuery(resource: Resource, query: string): Promise { @@ -133,17 +146,19 @@ export class MainProviderImplementation extends BaseExampleProductProvider { } ``` -Rename the file `README_template.md` in the root of the repository to `README.md`, then fill out the sections mentioned with `{...}` in the whole file. +### 2. Registering in the Network + +#### 2.1 Register as a Protocol Owner -### 2. Registering in the Protocol +All Actors such as Protocol Owners, Providers and Validators need to register in the Network and pay the registration fee before they can start any type of interactions. -#### 2.1 Register as a Product Category Owner +**TESTNET NOTE**: if you need testnet tokens reach out to the Forest Network team on [Discord](https://discord.gg/8F8V8gEgua). 1. Create a JSON detail file in the following schema and save it somewhere: ```json { - "name": "", + "name": "", "description": "<[Optional] Description>", "homepage": "<[Optional] Homepage address>" } @@ -153,23 +168,142 @@ Rename the file `README_template.md` in the root of the repository to `README.md 3. Take that account's private key and save it to a file. 4. Put the JSON file and that private key file into the same folder. 5. Open up a terminal in that folder. - > If you are planning to use different accounts for billing and operating, you need to pass additional flags: `--billing
` and `--operator
`. If you don't need that, just skip those flags. + > If you are planning to use different accounts for billing and operating, you need to pass additional flags: `--billing
` and `--operator
`. This separation increases security of your configuration. Setting a billing address allows for having a separate address / identity for claiming your earnings and rewards while setting an operator allows you to delegate the operational work of running a daemon and servicing user requests to a third-party or a hotkey. If you don't need that, just skip those flags and the logic of the Protocol will use your main address as your billing and operator address. 6. Run the following command: ```sh - forest register pco \ - --details \ - --account + forest register pto \ + --details \ + --account ``` -7. Save your detail file somewhere. Later you'll place this file into `data/details` folder. +7. Save your detail file into `data/details` folder. + +#### 2.2 Register a New Protocol + +Each Protocol is a separate smart contract that is deployed by the Registry main protocol contract. To deploy a new Protocol: + +First, you need to create a file containing detailed information about this Protocol. You have two options to do this: + +##### **Option 1:** Human-Readable Format + +You can create a plain text, Markdown, or any other format with human-readable content, such as the example below: + +``` +# Blueprint to 3D Model (Sketch to 3D) + +## Goal + +This subnet aims to convert blueprints, hand-drawn sketches, or simple CAD drawings into detailed 3D models. The goal is to enable quick visualization of architectural, product, or mechanical designs. + +## Evaluation + +Responses will be evaluated based on: + +✅ Structural Accuracy: The 3D model should correctly represent the original sketch. +✅ Rendering Quality: The model should be free of graphical artifacts. +✅ Material & Texture Fidelity: If provided, material properties should be correctly applied. +........ +``` + +##### **Option 2:** Structured JSON + +Alternatively, you can create a JSON file following the type definitions below. With this approach, the details of this Protocol will be visible in the CLI and Marketplace. Additionally, all Offers registered by Providers in this Protocol must set all the parameters defined in the JSON file. + +> These are pseudo-type definitions to illustrate the JSON schema. + +```typescript +type ProtocolDetails = { + /* Descriptive name of the Protocol */ + name: string; -#### 2.2 Register a New Product Category + /* The tests will be doing by the Validators */ + tests: any[]; -Create a file with detailed information about this Product Category. The file can be in plain text, Markdown or any other format that you want. Save it at `data/details/[file name]` in your forked Provider Template repository. + /* Software/Type of the Protocol such as "Database", "VM" or "API Service" etc. */ + softwareStack?: string; + + /* Version of the Service that is going to be served in this Protocol */ + version?: string; + + /* The parameters that each Offer which registered in this Protocol has to include */ + offerParams: { + /* Visible name of the parameter */ + name: string; + + /* + * For numeric values, it is a string which specifies + * the unit of that number, otherwise possible + * values for the field. + */ + unit: string | string[]; + + /* Priority of the Offer param in the Marketplace filter list */ + priority?: number; + + /* Defines is this Offer param can be filterable in Marketplace */ + isFilterable?: boolean; + + /* Defines is this Offer param is a primary info or not */ + isPrimary?: boolean; + }[]; +}; +``` + +An example JSON file based on these type definitions: + +```json +{ + "name": "PostgreSQL", + "softwareStack": "Database", + "tests": [], + "offerParams": [ + { + "name": "CPU", + "unit": "Cores", + "priority": 100, + "isPrimary": true + }, + { + "name": "RAM", + "unit": "GB", + "priority": 90, + "isPrimary": true + }, + { + "name": "Disk Type", + "unit": ["SSD", "HDD", "M2"], + "priority": 80, + "isPrimary": true + }, + { + "name": "Disk Size", + "unit": "GB", + "priority": 70 + }, + { + "name": "Virtualization", + "unit": ["VM", "Container"], + "priority": 60 + }, + { + "name": "CPU Architecture", + "unit": ["x86", "ARM"] + }, + { + "name": "Isolation", + "unit": ["Shared", "Dedicated"], + "isFilterable": false, + "priority": 40 + } + ] +} +``` + +2. Save it at `data/details/[file name]` in your forked Provider Template repository. ```sh -forest product-category create \ +forest protocol create \ --details
\ - --account \ + --account \ --max-validator 10 \ --max-provider 10 \ --min-collateral 10 \ @@ -179,24 +313,32 @@ forest product-category create \ --term-update-delay 400 \ --provider-share 45 \ --validator-share 45 \ - --pco-share 10 + --pto-share 10 ``` + + #### Explanation of Command Flags -| Flag | Description | -| -------------------------- | ---------------------------------------------------------------- | -| `--max-validator` | Maximum number of Validators that can be registered. | -| `--max-provider` | Maximum number of Providers that can be registered. | -| `--min-collateral` | Minimum FOREST token collateral required for registration. | -| `--validator-register-fee` | Registration fee (FOREST token) for Validators. | -| `--provider-register-fee` | Registration fee (FOREST token) for Providers. | -| `--offer-register-fee` | Fee for Providers to register a new Offer. | -| `--term-update-delay` | Minimum block count before Providers can close agreements. | -| `--provider-share` | Percentage of emissions allocated to Providers. | -| `--validator-share` | Percentage of emissions allocated to Validators. | -| `--pco-share` | Percentage of emissions allocated to the Product Category Owner. | - -### Final Steps - -Congratulations! You have registered in the Protocol and created your Product Category. Now, publish your Provider Template and inform potential Providers and Validators on how to participate in your Product Category. +| Flag | Description | +| -------------------------- | ------------------------------------------------------------ | +| `--max-validator` | Maximum number of Validators that can be registered. | +| `--max-provider` | Maximum number of Providers that can be registered. | +| `--min-collateral` | Minimum FOREST token collateral required for a registration. | +| `--validator-register-fee` | Registration fee (FOREST token) for Validators. | +| `--provider-register-fee` | Registration fee (FOREST token) for Providers. | +| `--offer-register-fee` | Fee for Providers to register a new Offer. | +| `--term-update-delay` | Minimum block count before Providers can close agreements. | +| `--provider-share` | Percentage of emissions allocated to Providers. | +| `--validator-share` | Percentage of emissions allocated to Validators. | +| `--pto-share` | Percentage of emissions allocated to the Protocol Owner. | + +### 3. Prepare the README file for Users and Providers + +Now you need to create a human-readable specification of your Protocol. You have total freedom to shape this document in a way you think is best. However we provide two templates for inspiration (`README_template_1.md`: [here](./docs/README_template_1.md)) and (`README_template_2.md`: [here](./docs/README_template_2.md)). Rename the chosen file to `README.md` (this will override this, but that's fine). + +From now on the `README.md` will include basic information about your Protocol that might be interesting to Users. It also links to a Provider tutorial on how to easily integrate with your Protocol. So the last thing you need to do is customize the information by filling out the missing parts in your PT's `README.md` as well as in the `README_Become_a_Provider.md`. + +### 4. Grow Your Protocol + +Congratulations! You have registered in the Protocol and created your Protocol. Now, publish your Provider Template and inform potential Providers and Validators on how to participate in your Protocol. diff --git a/data/details/.gitkeep b/data/details/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/data/details/pc-details.example.json b/data/details/pc-details.example.json new file mode 100644 index 0000000..439b75f --- /dev/null +++ b/data/details/pc-details.example.json @@ -0,0 +1,45 @@ +{ + "name": "PostgreSQL", + "softwareStack": "Database", + "tests": [], + "offerParams": [ + { + "name": "CPU", + "unit": "Cores", + "priority": 100, + "isPrimary": true + }, + { + "name": "RAM", + "unit": "GB", + "priority": 90, + "isPrimary": true + }, + { + "name": "Disk Type", + "unit": ["SSD", "HDD", "M2"], + "priority": 80, + "isPrimary": true + }, + { + "name": "Disk Size", + "unit": "GB", + "priority": 70 + }, + { + "name": "Virtualization", + "unit": ["VM", "Container"], + "priority": 60 + }, + { + "name": "CPU Architecture", + "unit": ["x86", "ARM"] + }, + { + "name": "Isolation", + "unit": ["Shared", "Dedicated"], + "isFilterable": false, + "priority": 40 + } + ] +} diff --git a/data/details/pco-details.example.json b/data/details/pco-details.example.json new file mode 100644 index 0000000..1c5b09e --- /dev/null +++ b/data/details/pco-details.example.json @@ -0,0 +1,4 @@ +{ + "name": "MongoDB PCO", + "description": "Builds the best environment for MongoDB products" +} diff --git a/data/details/prov-details.example.json b/data/details/prov-details.example.json new file mode 100644 index 0000000..c6fd4a6 --- /dev/null +++ b/data/details/prov-details.example.json @@ -0,0 +1,5 @@ +{ + "name": "Hebele Inc.", + "description": "Provides best prices for managed OSS solutions", + "homepage": "https://hebele.io" +} diff --git a/data/spec.yaml b/data/spec.yaml new file mode 100644 index 0000000..be7a4b2 --- /dev/null +++ b/data/spec.yaml @@ -0,0 +1,140 @@ +# This is the example OpenAPI spec. As Protocol Owner, +# you need to edit it according to your Protocol definitions +openapi: 3.0.0 +info: + # Command name that going to be appear in CLI. [MUST] + x-forest-cli-command: psql + + # Aliases for the command defined above. [OPTIONAL] + x-forest-cli-aliases: + - postgresql + - postgre-sql + title: PostgreSQL Protocol + description: API for PostgreSQL Protocol + version: "1.0" + license: + name: MIT +paths: + /query: + post: + # Specifies if this endpoint is Provider specific or not. + # If that is true, user has to send provider id within the request to + # point which Provider that he is going to communicate from the target operator + x-forest-provider-endpoint: true + + description: Executes the given SQL query in the database + requestBody: + content: + application/json: + schema: + type: object + required: + - id + - pt + - sql + properties: + id: + type: number + description: ID of the resource + pt: + type: string + description: Protocol address that the resource created in + sql: + type: string + description: SQL query to be executed. + responses: + "200": + description: SQL executed successfully + content: + application/json: + schema: + type: object + properties: + results: + type: array + description: Results + items: + type: object + /resources: + get: + summary: Get one or all of the resources + description: Gets one or all resources of the caller. If "id" and "pt" values are not given, retrieves all of the Resources that caller has. Caller must be the owner of those resource(s). + requestBody: + content: + application/json: + schema: + type: object + properties: + id: + type: number + description: ID of the resource/agreement + pt: + type: string + description: Protocol address + responses: + "200": + description: "Resources" + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: number + name: + type: string + deploymentStatus: + type: string + enum: + - Running + - Deploying + - Closed + - Unknown + - Failed + details: + type: object + groupName: + type: string + isActive: + type: boolean + ownerAddress: + type: string + example: "0x1231231231231231231231231231231231231231" + offerId: + type: number + providerId: + type: number + providerAddress: + type: string + example: "0x1231231231231231231231231231231231231231" + ptAddress: + type: string + example: "0x1231231231231231231231231231231231231231" + "404": + description: "Resource not found" + /details: + get: + summary: Gets the detail file(s) + description: Gets the detail files for the given CIDs if they there are in this operator + requestBody: + description: CIDs of the desired detail files + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + "200": + description: Returns the file contents. If the CID is not found, it won't be included in the response array + content: + application/json: + schema: + type: array + items: + type: string + "404": + description: If one CID is given and it is not found diff --git a/docs/README_template_1.md b/docs/README_template_1.md new file mode 100644 index 0000000..1267480 --- /dev/null +++ b/docs/README_template_1.md @@ -0,0 +1,71 @@ +# Protocol: `{Protocol Name, make it functionally descriptive}` + +## Description + +`{Describe the goal of this Protocol}` + +## Basic Info + +| Name | Value | +| ------------------------- | -------------------------- | +| PT Smart Contract Address | `{Smart Contract Address}` | +| PT Registration Date | `{Date of registration}` | +| PT Details File CID | `{CID}` | +| PT Owner Wallet Address | `{Public Wallet Address}` | +| PT Owner Details File CID | `{CID}` | + +## Supported Actions (Endpoints) + +| Method-Path | Params/Body | Response | Description | +| ---------------- | --------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `GET /details` | `body: string[]` | `string[]` | Retrieves the contents of detail files for the given CIDs. If one CID is given and corresponding file is not found, returns 404/Not Found. Otherwise returns an array of contents of the files | +| `GET /resources` | `params: { id?: number, pc?: Address }` | `Resource[] \| Resource` | If `id` and `pc` is given, retrieves one resource information. Otherwise returns all resources of the requester | +| `{Endpoint}` | `{Body}` | `{Return Type}` | `{Description}` | + +## Configuration Parameters + +This Protocol has the following configuration. Some of them are enforced by the logic of the on-chain smart contracts and the others are part of the Validator code hence enforced by the Validator consensus. + +| Config | Value | Enforced by | +| ---------------------------------------- | -------------------------- | -------------- | +| Maximum Number of Validators | `{Number}` | Smart Contract | +| Maximum Number of Providers | `{Number}` | Smart Contract | +| Minimum Collateral | `{Amount of FOREST Token}` | Smart Contract | +| Validator Registration Fee | `{Amount of FOREST Token}` | Smart Contract | +| Provider Registration Fee | `{Amount of FOREST Token}` | Smart Contract | +| Offer Registration Fee | `{Amount of FOREST Token}` | Smart Contract | +| Update Delay for Terms Change | `{Block Count}` | Smart Contract | +| Validators Share of Emissions | `{+Percentage}` | Smart Contract | +| Providers Share of Emissions | `{+Percentage}` | Smart Contract | +| PT Owner Share of Emissions | `{+Percentage}` | Smart Contract | +| CID of the Details File | `{CID}` | Smart Contract | +| Performance Optimization Weight | `{*Percentage}` | Validator | +| Price Optimization Weight | `{*Percentage}` | Validator | +| Price-to-Performance Optimization Weight | `{*Percentage}` | Validator | +| Popularity Optimization Weight | `{*Percentage}` | Validator | + +> Sum of the percentages mentioned with `+` sign must equal to 100. Same thing applies for `*` too. + +You can always double-check the on-chain values e.g. [here](https://sepolia-optimism.etherscan.io/address/`{Smart Contract Address}`#readContract) + +## Performance Requirements + +The Validators are performing a number of tests on Resources to ensure quality across the board. Below is a list of checked Benchmarks: + +| Name | Units | Threshold Value | Min / Max | +| --------------- | --------- | --------------- | ------------- | +| `{Test Name 1}` | `{Units}` | `{Value}` | `{Min / Max}` | +| `{Test Name 2}` | `{Units}` | `{Value}` | `{Min / Max}` | +| `{Test Name 3}` | `{Units}` | `{Value}` | `{Min / Max}` | + +More in-depth descriptions of the Tests (optional): + +| Name | Description | +| ------------- | ----------------------- | +| {Test Name 1} | {Long form description} | +| {Test Name 2} | {Long form description} | +| {Test Name 3} | {Long form description} | + +## Become a Provider in this Protocol + +If you want to start providing services in this Protocol follow this tutorial: [link](become-a-provider.md) diff --git a/docs/README_template_2.md b/docs/README_template_2.md new file mode 100644 index 0000000..d2d48a0 --- /dev/null +++ b/docs/README_template_2.md @@ -0,0 +1,63 @@ +# Name the Protocol, make it functionally descriptive + +## Goal + +Describe the goal of this Protocol. + +## Basic Info + +| Name | Value | +| ------------------------- | -------------------------- | +| PT Smart Contract Address | `{Smart Contract Address}` | +| PT Registration Date | `{Date of registration}` | +| PT Details File CID | `{CID}` | +| PT Owner Wallet Address | `{Public Wallet Address}` | +| PT Owner Details File CID | `{CID}` | + +## Evaluation + +How will validators score AI models provided. It should be described in high level English. One can define hard constraints that should force a score of zero or other more advanced heuristic point systems in addition to the English definition. + +## Supported Actions + +### `details()` + +- **Params**: + - `cids` (body: string[]): An array of CIDs for files that a user is interested in fetching +- **Returns**: + - `string[]`: Retrieves the contents of detail files for the given CIDs. If one CID is given and corresponding file is not found, returns 404/Not Found. Otherwise returns an array of contents of the files + +### `resources()` + +- **Params**: + - `id` (number, optional): Id of the resource for which to fetch information + - `pt` (Address, optional): PT address where the resource of interest lives +- **Returns**: + - `Resource[] | Resource`: If `id` and `pt` is given, retrieves one resource information. Otherwise returns all resources of the requester + +### `actionNameSimilarToFunctionName()` + +- **Params**: + - `parameterName` (datatype): Describe parameter. Optionally state max character length or other restrictions. + - `otherParameter` (string, optional): specific options avaliable `realistic`, `cartoon` or `minimalist`. +- **Returns**: + - `returnDataTypeFormat` Describe the return object + - `subElementRetenred` (string): If the returned object is JSON or another composite data type one can describe sub elements here also + - Add any functional requirements that are not directly linekd to a datatype here + +## Performance Requirements + +- How quickly must the actions return Query response within 60 minutes for a video up to 1 minute length. +- Or how many queries must one be able to send per hour. + +## Example + +``` +Give an exact example of input +``` + +And exact example of output + +## Become a Provider in this Protocol + +If you want to start providing services in this Protol follow this tutorial: [link](become-a-provider.md) diff --git a/README_template.md b/docs/become-a-provider.md similarity index 53% rename from README_template.md rename to docs/become-a-provider.md index 36e0435..34f077d 100644 --- a/README_template.md +++ b/docs/become-a-provider.md @@ -1,81 +1,27 @@ -# Product Category: `{Product Category Name}` +# Become a Provider in this Protocol -## Description +If you want to start providing services in this Protocol follow the steps below. -{Explanation about what this Product Category for} +1. [Register in the Network](#1-register-in-the-network), +2. [Register in this Protocol](#2-register-in-this-protocol), +3. [Register Offers](#3-register-offers), +4. [Fork and Implement This Repository](#4-fork-and-implement-this-repository), +5. [Run the Provider Daemon](#5-run-the-provider-daemon). -## Basic Info - -| Name | Value | -| ------------------------- | ----------------------------------------------- | -| PC Smart Contract Address | `{Smart Contract Address}` | -| PC Registration Date | `{Date of registration}` | -| PC Owner Website | `{Website address link}` | -| PC Owner Contact Info | `{Contact info (e-mail, social accounts etc.)}` | -| PC Owner Wallet Address | `{Public Wallet Address}` | -| PC Owner Details File CID | `{CID}` | - -## Configuration Parameters - -This Product Category has the following configuration. Some of them are enforced by the logic of the on-chain smart contract an the others are Validator code. - -| Config | Value | Enforced by | -| ---------------------------------------- | -------------------------- | -------------- | -| Maximum Number of Validators | `{Number}` | Smart Contract | -| Maximum Number of Providers | `{Number}` | Smart Contract | -| Minimum Collateral | `{Amount of FOREST Token}` | Smart Contract | -| Validator Registration Fee | `{Amount of FOREST Token}` | Smart Contract | -| Provider Registration Fee | `{Amount of FOREST Token}` | Smart Contract | -| Offer Registration Fee | `{Amount of FOREST Token}` | Smart Contract | -| Update Delay for Terms Change | `{Block Count}` | Smart Contract | -| Validators Share of Emissions | `{+Percentage}` | Smart Contract | -| Providers Share of Emissions | `{+Percentage}` | Smart Contract | -| PC Owner Share of Emissions | `{+Percentage}` | Smart Contract | -| CID of the Details File | `{CID}` | Smart Contract | -| Performance Optimization Weight | `{*Percentage}` | Validator | -| Price Optimization Weight | `{*Percentage}` | Validator | -| Price-to-Performance Optimization Weight | `{*Percentage}` | Validator | -| Popularity Optimization Weight | `{*Percentage}` | Validator | - -> Sum of the percentages mentioned with `+` sign must equal to 100. Same thing applies for `*` too. - -You can always double-check the on-chain values e.g. [here](https://sepolia-optimism.etherscan.io/address/`{Smart Contract Address}`#readContract) - -## Endpoints - -| Path | Params/Body | Response | Description | -| ------------ | --------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/details` | `body: string[]` | `string[]` | Retrieves the contents of detail files for the given CIDs. If one CID is given and corresponding file is not found, returns 404/Not Found. Otherwise returns an array of contents of the files | -| `/resources` | `params: { id?: number, pc?: Address }` | `Resource[] \| Resource` | If `id` and `pc` is given, retrieves one resource information. Otherwise returns all resources of the requester | -| `{Endpoint}` | `{Body}` | `{Return Type}` | `{Description}` | - -## Tests and Quality Thresholds [WIP] - -The Validators are performing a number of tests on Resources to ensure quality across the board. Below is a list of checked Benchmarks: - -| Name | Units | Threshold Value | Min / Max | -| ------------- | --------- | --------------- | ----------- | -| {Test Name 1} | `{Units}` | `{Value}` | {Min / Max} | -| {Test Name 2} | `{Units}` | `{Value}` | {Min / Max} | -| {Test Name 3} | `{Units}` | `{Value}` | {Min / Max} | - -More in-depth descriptions of the Tests: - -| Name | Description | -| ------------- | ----------------------- | -| {Test Name 1} | {Long form description} | -| {Test Name 2} | {Long form description} | -| {Test Name 3} | {Long form description} | +### Step-by-step instructions -## Become a Provider +#### Prerequisites -### Step-by-step instructions +Install [Node.js](https://nodejs.org) (min version 22.12.0) environment, Forest Protocols [CLI](https://github.com/Forest-Protocols/forest-cli) tool and a functional PostgreSQL (min version 16) database for the daemon. To install that environment, you can check the links below; -In order to start providing services for this Product Category you need to follow the steps below. But before, you need to install Forest Protocols CLI by following these instructions: [link](https://github.com/Forest-Protocols/forest-cli) +- Node.js [official](https://nodejs.org/en/download) downloads page +- [nvm](https://github.com/nvm-sh/nvm) - Node Version Manager, helps to manage multiple Node versions at the same time. +- PostgreSQL [official](https://www.postgresql.org/download/) downloads page (if you want to run Postgres natively) +- Docker [image](https://hub.docker.com/_/postgres) for PostgreSQL (if you want to run dockerized Postgres) -#### 1. Register in the Protocol +#### 1. Register in the Network -> You can skip this part if you are already registered in the Protocol as a Provider. +> You can skip this part if you are already registered in the Network as a Provider. 1. Create a JSON detail file in the following schema and save it somewhere: @@ -91,38 +37,46 @@ In order to start providing services for this Product Category you need to follo 3. Take that account's private key and save it to a file. 4. Put the JSON file and that private key file into the same folder. 5. Open up a terminal in that folder. - > If you are planning to use different accounts for billing and operating, you need to pass additional flags: `--billing
` and `--operator
`. If you don't need that, just skip those flags. -6. Run the following command: + > If you are planning to use different accounts for billing and operating, you need to pass additional flags: `--billing
` and `--operator
`. This separation increases security of your configuration. Setting a billing address allows for having a separate address / identity for claiming your earnings and rewards while setting an operator allows you to delegate the operational work of running a daemon and servicing user requests to a third-party or a hotkey. If you don't need that, just skip those flags and the logic of the Protocol will use your main address as your billing and operator address. +6. Run the following command to register in the Protocol to be allowed to interact with Protocol's resources: ```sh forest register provider \ --details \ --account ``` + TESTNET NOTE: if you need testnet tokens reach out to the Forest Protocols team on [Discord](https://discord.gg/2MsTWq2tc7). 7. Save your detail file somewhere. Later you'll place this file into `data/details` folder. -#### 2. Register in this Product Category +#### 2. Register in this Protocol -Use the following command to register in this Product Category: +You can take part in many Protocols. In order to join this one run the following command: ```shell forest provider register-in \ --account \ - {Product Category Smart Contract Address} \ - {Minimum Collateral} + --protocol \ + --collateral ``` #### 3. Register Offers -Now that you are registered in the Protocol and this Product Category, the next step is to register your Offers. +Now that you are registered in the Network and this Protocol, the next step is to register your Offers. First, create files that contain details for each Offer you plan to register. You have two options for these detail files: -- Create a plain text or Markdown file with human-readable Offer details. This approach does not allow parameterization of Offers. Also these details won't be visible in the CLI. -- Create a JSON file following the schema below. This approach makes Offer details visible and filterable in the CLI and marketplace while also allowing parameterization of resource creation. +- Create a plain text or Markdown file with human-readable Offer details. This approach does not allow parameterization of Offers. Also these details won't be visible in the CLI. However this approach is often good enough for a number of use cases like API access. +- Create a JSON file following the schema below. This approach makes Offer details visible and filterable in the CLI and the Marketplace while also allowing parameterization of resource creation. -##### 3.1 JSON Schemed Offer Details +##### 3.1 Creating the Offer details file -**If you are not using this option, you may skip this section.** +**Plain text example** + +``` +Minimum of 2 requests per minute. +At least 200 API calls per subscription per month. +``` + +**JSON schemed example** Create a JSON file following the type definitions below: @@ -175,12 +129,18 @@ An example JSON file based on these type definitions: } ``` -After creating the Offer details file, save it in an accessible location. Now register your Offer using the following command: +##### 3.2 Saving the file + +After creating the Offer details file, save it in an accessible location. + +##### 3.3 Registering the Offer on-chain + +Now register your Offer using the following command: ```shell forest provider register-offer \ - {Product Category Smart Contract Address} \ --account \ + --protocol \ --details \ --fee 1 \ --stock 100 @@ -193,7 +153,7 @@ forest provider register-offer \ Fork this repository, then clone it locally. -Open the `src/product-category/provider.ts` file and implement all of the following methods; +Open the `src/protocol/provider.ts` file and implement all of the following methods; | Method | Description | | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -217,7 +177,7 @@ Now, create a `.env` file based on the example (`.env.example`) and configure th Then rename `data/providers.example.jsonc` to `data/providers.json`, clear the comments inside of it and fill the `main` tag with your private keys. -As the last step, don't forget to put detail files of the Provider, Product Category and Offers into `data/details` folder. +As the last step, don't forget to put detail files of the Provider, Protocol and Offers into `data/details` folder. #### 5. Run the Provider Daemon @@ -259,18 +219,3 @@ docker compose up # Add "-d" to run in detached mode ``` That's all folks! - -## Become a Validator [WIP] - -#### Step-by-step instructions - -In order to start providing validation services for this Product Category you need to: - -1. Run your Validator Node based on the code from this repository. Detailed instructions here: [link](https://github.com/this_repo/validator/README.md) -2. Install a Forest Protocols CLI by following these instructions: [link](https://github.com/forest-protocols/cli....) -3. Using the CLI register in the Protocol as a Validator: - a. `command 1` - b. `command 2` -4. Using the CLI register in our Product Category: - a. `command 1` - b. `command 2` diff --git a/docs/how-to-sync-repository.md b/docs/how-to-sync-repository.md new file mode 100644 index 0000000..7a48e3c --- /dev/null +++ b/docs/how-to-sync-repository.md @@ -0,0 +1,63 @@ +# How to Sync a Forked Repository with the Base Repository + +Once you have completed your implementation, you will need to keep your forked repository up to date with the base repository from which you originally forked. + +- If you are a **PTO**, the base repository would be the **Base Provider Template**, created by the Forest Network team. +- If you are a **PROV**, the base repository would be the one created by the **PTO of the Protocol** you have registered with. + +## Why Do I Need to Do This? + +Over time, the base repositories may receive new features, bug fixes, and other improvements. Keeping your forked repository updated ensures that you can take advantage of these updates and stay compatible with the rest of the Network. + +## Tutorial + +### 1. Clone Your Forked Repository + +First, clone your forked repository to your local environment using the `git clone` command. Navigate into the cloned directory and add the base repository as a remote: + +```shell +git remote add upstream git@github.com:Forest-Protocols/provider-template.git +``` + +> If you are syncing with a different repository, replace the URL with the appropriate repository address. + +### 2. Merge the Base Repository into Your Branch + +Ensure you are in your own branch, then merge it with the base repository: + +Before, merge you have to fetch the remote branch to your local with following command: + +```shell +git fetch --all +``` + +Then, merge the base repository into your branch: + +```shell +git merge remotes/upstream/main +``` + +This will attempt to merge all changes automatically, but conflicts may occur in the following files: + +- README.md (and other README files), +- `src/protocol/base-provider.ts`, +- `src/protocol/provider.ts` + +### 3. Resolving Merge Conflicts + +To resolve conflicts, you can use a terminal-based diff tool (such as [delta](https://github.com/dandavison/delta)) or, if you are using [VSCode](https://code.visualstudio.com/), the [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) extension can be helpful. + +- **README files**: Since these files contain human-readable information, carefully choose which content to retain. Ensure that the information remains aligned with updates from the **Base Provider Template**. +- **`src/protocol/base-provider.ts` and `src/protocol/provider.ts`**: While you can keep your versions of these files, make sure that they align with the latest coding approaches used in the **Base Provider Template**. Check for changes in function calls, arguments, variable/class definitions, and naming conventions. + +### 4. Finalizing the Merge + +Once all conflicts are resolved and your template is working correctly, complete the merge and commit your changes. Then, push the updated repository to GitHub: + +```shell +git push origin +``` + +### 5. Additional Note for Providers + +If you are a **Provider**, you should follow the same steps but use your **base provider template repository address** instead of the one listed in the examples above. diff --git a/docs/openapi-spec.md b/docs/openapi-spec.md new file mode 100644 index 0000000..f596c87 --- /dev/null +++ b/docs/openapi-spec.md @@ -0,0 +1,22 @@ +# Creating an OpenAPI Specification for Your Protocol + +As the owner of a Protocol, you can create an OpenAPI specification file for the endpoints/actions you've defined. This will enable these endpoints to be accessible via the [Forest CLI](https://github.com/Forest-Protocols/forest-cli). + +## Quickstart + +We use [OpenAPI Specification version 3.0](https://spec.openapis.org/oas/v3.0.0.html). You can define your endpoints using either JSON or YAML format and place it under `data` directory. + +- If an endpoint supports multiple methods, they will be listed in the CLI as `-`. For example, if your `query` path supports both `POST` and `GET`, the CLI will recognize two commands: `query-get` and `query-post`. +- If an endpoint expects a request body, the properties within that body will be available as `--body.` in the CLI. If the body is a primitive type (array, number, or string), it will be accessible via `--body`. +- Query parameters will follow a similar pattern, appearing as `--params.` in the CLI. +- If a property is an array, it must be passed as a JSON string via the command line. + +### Additional Fields for CLI Integration + +To make your OpenAPI spec compatible with the `forest` CLI, you must include some additional fields. These fields are outlined below: + +| Path | Name | Possible Values | Description | Required | +| --------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | +| `root -> info -> x-forest-cli-command` | `x-forest-cli-command` | A string without spaces or non-ASCII characters. | Specifies the command name for calling this API via `forest api`. | Yes | +| `root -> info -> x-forest-cli-aliases` | `x-forest-cli-aliases` | An array of strings without spaces or non-ASCII characters. | Defines alternative command names for `x-forest-cli-command`. | No | +| `root -> paths -> (any path) -> (any method) -> x-forest-provider-endpoint` | `x-forest-provider-endpoint` | Boolean (`true` or `false`). | If set to `true`, an additional required option will be introduced to specify `providerId` for the request. | No | diff --git a/docs/user-network-interaction.md b/docs/user-network-interaction.md new file mode 100644 index 0000000..4ea4917 --- /dev/null +++ b/docs/user-network-interaction.md @@ -0,0 +1,67 @@ +# User interaction with an Agreement with Forest CLI Tool + +In our case, to showcase how Provider works within the Network and handle the service, we would use the LLM model with the API keys from [Open Router](https://openrouter.ai). From user perspective, we would send an XTMP request to the Provider and check if the Provider can handle the request with the response. + +> Note: Make sure that you have enough Optimism Sepolia ETH to cover transaction costs, you need also USDC testnet tokens to pay 2 month prepayment for the service. +> Note: To register the Agreement within the Network, the Provider should be spinned up and listen to blockchain to register Resource and Agreement that user is going to enter based on the Protocol address and Offer id. + +> TESTNET NOTE: if you need testnet tokens reach out to the Forest Protocols team on [Discord](https://discord.gg/2MsTWq2tc7). + +1. You have to prepare your private key to enter an Agreement and copy/paste your command within the new opened terminal. +2. Check your balance with the following command: + +```sh +forest wallet balance +``` + +Return value will be like the example below: + +```txt +0.01 ETH +2.8 USDC +1000 FOREST +``` + +3. Run the command to find the Offer you want to enter an Agreement with. You can do it with the following command: + +```sh +forest get offers +``` + +Return value will be like the example below: + +```txt +ID @ Protocol: 0 @ 0xf833d786374AEbC580eC389BE21A4CC340B543CD +Provider: 0x354cc7AC43c4681976bd926271524f6E28db2c96 +Status: Active +Fee Per Second: 0.000001 USDC +Fee Per Month: 2.6352 USDC +Total Stock: 100 +Active Agreements: 0 +CID: bagaaiera3by5b3mykt7n2q2e4yvglgoh33ssoat5qczvgo75ii5yrxamo2aq +``` + +4. Enter an Agreement for specific offer hosted by Provider within the PT: + +```sh +forest agreement enter \ +--account \ +--deposit \ +--offer \ +--protocol \ +``` + +## Congratulations! + +You have entered the Agreement with the Provider for the specific offer within the Protocol. + +Right now we want to test if User can send a request to Provider and get a response. To do that, we will use the following command: + +```sh +forest pipe --to \ +--method POST \ +--path "/chat/completions" \ +--body '{"providerId": 13,"id": 7, +"model": "any type of model that is free", "messages" : [{"role": "user","content": "Say hello world" } ], "pt": "0xf833d786374AEbC580eC389BE21A4CC340B543CD" }' \ +--account +``` diff --git a/drizzle/0001_bouncy_the_spike.sql b/drizzle/0001_bouncy_the_spike.sql new file mode 100644 index 0000000..26fa3c5 --- /dev/null +++ b/drizzle/0001_bouncy_the_spike.sql @@ -0,0 +1,9 @@ +ALTER TABLE "product_categories" RENAME TO "protocols";--> statement-breakpoint +ALTER TABLE "resources" RENAME COLUMN "pc_address_id" TO "pt_address_id";--> statement-breakpoint +ALTER TABLE "protocols" DROP CONSTRAINT "product_categories_address_unique";--> statement-breakpoint +ALTER TABLE "resources" DROP CONSTRAINT "resources_pc_address_id_product_categories_id_fk"; +--> statement-breakpoint +ALTER TABLE "resources" DROP CONSTRAINT "resources_id_pc_address_id_pk";--> statement-breakpoint +ALTER TABLE "resources" ADD CONSTRAINT "resources_id_pt_address_id_pk" PRIMARY KEY("id","pt_address_id");--> statement-breakpoint +ALTER TABLE "resources" ADD CONSTRAINT "resources_pt_address_id_protocols_id_fk" FOREIGN KEY ("pt_address_id") REFERENCES "public"."protocols"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "protocols" ADD CONSTRAINT "protocols_address_unique" UNIQUE("address"); \ No newline at end of file diff --git a/drizzle/0002_serious_salo.sql b/drizzle/0002_serious_salo.sql new file mode 100644 index 0000000..e4f697c --- /dev/null +++ b/drizzle/0002_serious_salo.sql @@ -0,0 +1 @@ +ALTER TABLE "blockchain_transactions" DROP COLUMN "is_processed"; \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..c0d65f9 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,296 @@ +{ + "id": "b2ddce64-d841-4a99-926e-2026ea6a3381", + "prevId": "c871189e-91fe-467b-a286-ae9e3243e24b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.blockchain_transactions": { + "name": "blockchain_transactions", + "schema": "", + "columns": { + "height": { + "name": "height", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(70)", + "primaryKey": false, + "notNull": true + }, + "is_processed": { + "name": "is_processed", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "blockchain_transactions_height_hash_pk": { + "name": "blockchain_transactions_height_hash_pk", + "columns": [ + "height", + "hash" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.detail_files": { + "name": "detail_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "detail_files_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "cid": { + "name": "cid", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "detail_files_cid_unique": { + "name": "detail_files_cid_unique", + "nullsNotDistinct": false, + "columns": [ + "cid" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.protocols": { + "name": "protocols", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "protocols_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "address": { + "name": "address", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "protocols_address_unique": { + "name": "protocols_address_unique", + "nullsNotDistinct": false, + "columns": [ + "address" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "owner_address": { + "name": "owner_address", + "type": "varchar(65)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "providers_owner_address_unique": { + "name": "providers_owner_address_unique", + "nullsNotDistinct": false, + "columns": [ + "owner_address" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resources": { + "name": "resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "owner_address": { + "name": "owner_address", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "deployment_status": { + "name": "deployment_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "offer_id": { + "name": "offer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pt_address_id": { + "name": "pt_address_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resources_provider_id_providers_id_fk": { + "name": "resources_provider_id_providers_id_fk", + "tableFrom": "resources", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "resources_pt_address_id_protocols_id_fk": { + "name": "resources_pt_address_id_protocols_id_fk", + "tableFrom": "resources", + "tableTo": "protocols", + "columnsFrom": [ + "pt_address_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "resources_id_pt_address_id_pk": { + "name": "resources_id_pt_address_id_pk", + "columns": [ + "id", + "pt_address_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..bc621bf --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,290 @@ +{ + "id": "c39a132d-c967-4e99-9a37-aa1e7100c579", + "prevId": "b2ddce64-d841-4a99-926e-2026ea6a3381", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.blockchain_transactions": { + "name": "blockchain_transactions", + "schema": "", + "columns": { + "height": { + "name": "height", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(70)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "blockchain_transactions_height_hash_pk": { + "name": "blockchain_transactions_height_hash_pk", + "columns": [ + "height", + "hash" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.detail_files": { + "name": "detail_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "detail_files_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "cid": { + "name": "cid", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "detail_files_cid_unique": { + "name": "detail_files_cid_unique", + "nullsNotDistinct": false, + "columns": [ + "cid" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.protocols": { + "name": "protocols", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "protocols_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "address": { + "name": "address", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "protocols_address_unique": { + "name": "protocols_address_unique", + "nullsNotDistinct": false, + "columns": [ + "address" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "owner_address": { + "name": "owner_address", + "type": "varchar(65)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "providers_owner_address_unique": { + "name": "providers_owner_address_unique", + "nullsNotDistinct": false, + "columns": [ + "owner_address" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resources": { + "name": "resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "owner_address": { + "name": "owner_address", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "deployment_status": { + "name": "deployment_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "offer_id": { + "name": "offer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pt_address_id": { + "name": "pt_address_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resources_provider_id_providers_id_fk": { + "name": "resources_provider_id_providers_id_fk", + "tableFrom": "resources", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "resources_pt_address_id_protocols_id_fk": { + "name": "resources_pt_address_id_protocols_id_fk", + "tableFrom": "resources", + "tableTo": "protocols", + "columnsFrom": [ + "pt_address_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "resources_id_pt_address_id_pk": { + "name": "resources_id_pt_address_id_pk", + "columns": [ + "id", + "pt_address_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 322fc15..ac24ac5 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,20 @@ "when": 1739026654124, "tag": "0000_watery_martin_li", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1740142488145, + "tag": "0001_bouncy_the_spike", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1740388254993, + "tag": "0002_serious_salo", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f77945a..9356e11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,11 @@ "license": "MIT", "dependencies": { "@dotenvx/dotenvx": "^1.32.1", - "@forest-protocols/sdk": "^1.24.2", + "@forest-protocols/sdk": "^1.32.0", "ansis": "^3.7.0", "drizzle-orm": "^0.38.3", "ethers": "^6.13.5", + "express": "^4.21.2", "pg": "^8.13.1", "unique-names-generator": "^4.7.1", "viem": "^2.22.7", @@ -22,6 +23,7 @@ "zod-validation-error": "^3.4.0" }, "devDependencies": { + "@types/express": "^5.0.0", "@types/node": "^22.10.5", "@types/pg": "^8.11.10", "drizzle-kit": "^0.30.1", @@ -1025,9 +1027,9 @@ } }, "node_modules/@forest-protocols/sdk": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/@forest-protocols/sdk/-/sdk-1.24.2.tgz", - "integrity": "sha512-DsDHSDWftUMlERSCcNQ+p7JPSVwqem0RLS3URqPWXX1KarPJNVRoxI49nQ8VZyjggHg37+BV3XyXgjSjIFB1fw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@forest-protocols/sdk/-/sdk-1.32.0.tgz", + "integrity": "sha512-Yplozbekg59BiX/rXl3vKe/IS4CQX1mjD+J8rt9mJd95AWgkllNSbjznr/WeKOcuuYuy5Edk1eFjq32z2Sob7A==", "license": "MIT", "dependencies": { "@xmtp/xmtp-js": "^13.0.4", @@ -1628,6 +1630,27 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1635,6 +1658,46 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.10.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", @@ -1718,6 +1781,43 @@ "node": ">=12" } }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -1753,9 +1853,9 @@ } }, "node_modules/@xmtp/proto": { - "version": "3.73.0", - "resolved": "https://registry.npmjs.org/@xmtp/proto/-/proto-3.73.0.tgz", - "integrity": "sha512-Rgtfme8KeQ5lMI62o1Wq+7fJhoOS4QR94fx84+83NEZyYT7VBd+V/13B5a/OSzz6EnWWEyYEg+Dv7ZxV4KouQA==", + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/@xmtp/proto/-/proto-3.74.0.tgz", + "integrity": "sha512-UJIk2HmRfAZzsYgkwTHswnp7AFRG8LXdYuPX03dCu9BxRYGCiypi8QQn73eOkcqoDssl6vJpvc0JuuUwEh6oHA==", "license": "MIT", "dependencies": { "long": "^5.2.0", @@ -1964,6 +2064,19 @@ } } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", @@ -2026,6 +2139,12 @@ "node": ">= 8" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2077,6 +2196,45 @@ "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", "license": "MIT" }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2129,6 +2287,15 @@ "esbuild": ">=0.18" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -2139,6 +2306,35 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2248,6 +2444,42 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2301,6 +2533,25 @@ } } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2897,6 +3148,20 @@ } } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2921,6 +3186,12 @@ "node": ">=16" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -2949,6 +3220,45 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -3002,6 +3312,21 @@ "esbuild": ">=0.12 <1" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ethers": { "version": "6.13.5", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", @@ -3125,6 +3450,73 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -3171,6 +3563,39 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -3207,6 +3632,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3222,6 +3665,52 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3302,6 +3791,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -3312,6 +3825,18 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3323,6 +3848,22 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3332,6 +3873,18 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3347,6 +3900,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -3532,9 +4094,9 @@ } }, "node_modules/long": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", - "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", "license": "Apache-2.0" }, "node_modules/lru-cache": { @@ -3544,6 +4106,33 @@ "dev": true, "license": "ISC" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3560,6 +4149,15 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3574,6 +4172,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -3628,9 +4259,9 @@ "license": "MIT" }, "node_modules/multiformats": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.1.tgz", - "integrity": "sha512-QxowxTNwJ3r5RMctoGA5p13w5RbRT2QDkoM+yFlqfLiioBp78nhDjnRLvmSBI9+KAqN4VdgOVWM9c0CHd86m3g==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", "license": "Apache-2.0 OR MIT" }, "node_modules/mylas": { @@ -3659,6 +4290,15 @@ "thenify-all": "^1.0.0" } }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3691,6 +4331,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-treeify": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", @@ -3707,6 +4359,18 @@ "devOptional": true, "license": "MIT" }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3767,6 +4431,15 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4067,6 +4740,19 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4077,6 +4763,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-lit": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", @@ -4108,6 +4809,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -4267,6 +4992,81 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4288,6 +5088,78 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4394,6 +5266,15 @@ "node": "*" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4640,6 +5521,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -5241,6 +6131,19 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -5282,12 +6185,30 @@ "node": ">=8" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", @@ -5301,6 +6222,15 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/viem": { "version": "2.22.7", "resolved": "https://registry.npmjs.org/viem/-/viem-2.22.7.tgz", diff --git a/package.json b/package.json index b87a08b..d248d92 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { "name": "provider-template", "version": "0.0.1", - "description": "Base provider template for new product categories", - "homepage": "https://github.com/dbForest/provider-template#readme", + "description": "Base Provider template for new Protocols", + "homepage": "https://github.com/Forest-Protocols/provider-template#readme", "bugs": { - "url": "https://github.com/dbForest/provider-template/issues" + "url": "https://github.com/Forest-Protocols/provider-template/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/dbForest/provider-template.git" + "url": "git+https://github.com/Forest-Protocols/provider-template.git" }, "license": "MIT", - "author": "Forest Protocols Development Team", + "author": "Forest Network Development Team", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,6 +26,7 @@ "db:push": "drizzle-kit push" }, "devDependencies": { + "@types/express": "^5.0.0", "@types/node": "^22.10.5", "@types/pg": "^8.11.10", "drizzle-kit": "^0.30.1", @@ -36,10 +37,11 @@ }, "dependencies": { "@dotenvx/dotenvx": "^1.32.1", - "@forest-protocols/sdk": "^1.24.2", + "@forest-protocols/sdk": "^1.32.0", "ansis": "^3.7.0", "drizzle-orm": "^0.38.3", "ethers": "^6.13.5", + "express": "^4.21.2", "pg": "^8.13.1", "unique-names-generator": "^4.7.1", "viem": "^2.22.7", diff --git a/src/abstract/AbstractProvider.ts b/src/abstract/AbstractProvider.ts index e7b84c4..3487c18 100644 --- a/src/abstract/AbstractProvider.ts +++ b/src/abstract/AbstractProvider.ts @@ -22,17 +22,19 @@ import { Agreement, PipeMethod, PipeResponseCode, - ProductCategory, + Protocol, Registry, XMTPPipe, } from "@forest-protocols/sdk"; -import { red, yellow } from "ansis"; +import { yellow } from "ansis"; +import { readFileSync, statSync } from "fs"; +import { join } from "path"; import { Account, Address } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { z } from "zod"; /** - * Abstract provider that needs to be extended by the Product Category Owner. + * Abstract Provider that Protocol Owners has to extend from. * @responsible Admin */ export abstract class AbstractProvider< @@ -40,7 +42,7 @@ export abstract class AbstractProvider< > { registry!: Registry; - pcClients: { [address: string]: ProductCategory } = {}; + protocols: { [address: string]: Protocol } = {}; account!: Account; @@ -51,14 +53,14 @@ export abstract class AbstractProvider< logger = logger.child({ context: this.constructor.name }); /** - * Initializes the provider if it needs some async operation to be done before start to use it. + * Initializes the Provider. */ async init(providerTag: string): Promise { const providerConfig = config.providers[providerTag]; if (!providerConfig) { this.logger.error( - `Provider config not found for provider tag "${providerTag}". Please check your data/providers.json file` + `Provider config not found for Provider tag "${providerTag}". Please check your data/providers.json file or environment variables` ); process.exit(1); } @@ -71,13 +73,11 @@ export abstract class AbstractProvider< // Initialize clients this.registry = Registry.createWithClient(rpcClient, this.account); - this.logger.info("Checking in Protocol Actor information"); + this.logger.info("Checking in Network Actor registration"); const provider = await this.registry.getActor(this.account.address); if (!provider) { this.logger.error( - red( - `Provider address "${this.account.address}" is not registered in the Protocol. Please register it and try again.` - ) + `Provider "${this.account.address}" is not registered in the Network. Please register it and try again.` ); process.exit(1); } @@ -95,17 +95,16 @@ export abstract class AbstractProvider< // `DB.upsertProvider` already checked the existence of the details file this.details = tryParseJSON(provDetailFile.content); - const pcAddresses = await this.registry.getRegisteredPCsOfProvider( + const ptAddresses = await this.registry.getRegisteredPTsOfProvider( provider.id ); - for (const pcAddress of pcAddresses) { - this.pcClients[pcAddress.toLowerCase()] = - ProductCategory.createWithClient( - rpcClient, - pcAddress as Address, - this.account - ); + for (const ptAddress of ptAddresses) { + this.protocols[ptAddress.toLowerCase()] = Protocol.createWithClient( + rpcClient, + ptAddress as Address, + this.account + ); } // Initialize pipe for this operator address if it is not instantiated yet. @@ -133,6 +132,39 @@ export abstract class AbstractProvider< // Setup operator specific endpoints + this.operatorRoute(PipeMethod.GET, "/spec", async (req) => { + try { + const possibleSpecFiles = [ + "spec.yaml", + "spec.json", + "oas.json", + "oas.yaml", + ]; + for (const specFile of possibleSpecFiles) { + const path = join(process.cwd(), "data", specFile); + const stat = statSync(path, { throwIfNoEntry: false }); + + if (stat && stat.isFile()) { + const content = readFileSync(path, { + encoding: "utf-8", + }).toString(); + return { + code: PipeResponseCode.OK, + body: content, + }; + } + } + } catch (err: any) { + this.logger.error(`Couldn't load OpenAPI spec file: ${err.message}`); + return { + code: PipeResponseCode.OK, + body: "", + }; + } + + throw new PipeErrorNotFound(`OpenAPI spec file`); + }); + /** * Retrieves detail file(s) */ @@ -155,18 +187,24 @@ export abstract class AbstractProvider< */ this.operatorRoute(PipeMethod.GET, "/resources", async (req) => { const params = validateBodyOrParams( - req.params, + req.body || req.params, z.object({ /** ID of the resource. */ id: z.number().optional(), - /** Product category address that the resource created in. */ - pc: addressSchema.optional(), // A pre-defined Zod schema for smart contract addresses. + /** Protocol address that the resource created in. */ + pt: addressSchema.optional(), // A pre-defined Zod schema for smart contract addresses. + pc: addressSchema.optional(), // Alternative name for `pt` parameter }) ); + // If `pc` alias is given, use it. + if (params.pc) { + params.pt = params.pc; + } + // If not both of them are given, send all resources of the requester - if (params.id === undefined || params.pc === undefined) { + if (params.id === undefined || params.pt === undefined) { return { code: PipeResponseCode.OK, body: await DB.getAllResourcesOfUser(req.requester as Address), @@ -177,11 +215,11 @@ export abstract class AbstractProvider< // Since XMTP has its own authentication layer, we don't need to worry about // if this request really sent by the owner of the resource. So if the sender is // different from owner of the resource, basically the resource won't be found because - // we are looking to the database with agreement id + requester address + product category address. + // we are looking to the database with agreement id + requester address + protocol address. const resource = await DB.getResource( params.id, req.requester, - params.pc as Address + params.pt as Address ); if (!resource) { @@ -209,24 +247,24 @@ export abstract class AbstractProvider< } /** - * Gets the Product Category client from the registered product category list of this provider. + * Gets the Protocol instance from the registered Protocols list. */ - getPcClient(pcAddress: Address) { - return this.pcClients[pcAddress.toLowerCase()]; + getProtocol(ptAddress: Address) { + return this.protocols[ptAddress.toLowerCase()]; } /** * Gets a resource that stored in the database and the corresponding agreement from blockchain * @param id ID of the resource/agreement - * @param pcAddress Product category address + * @param ptAddress Protocol address * @param requester Requester of this resource */ protected async getResource( id: number, - pcAddress: Address, + ptAddress: Address, requester: string ) { - const resource = await DB.getResource(id, requester, pcAddress); + const resource = await DB.getResource(id, requester, ptAddress); if ( !resource || // Resource does not exist @@ -236,13 +274,13 @@ export abstract class AbstractProvider< throw new PipeErrorNotFound("Resource"); } - const pcClient = this.getPcClient(pcAddress); // Product category client. - const agreement = await pcClient.getAgreement(resource.id); // Retrieve the agreement details from chain + const protocol = this.getProtocol(ptAddress); // Protocol instance. + const agreement = await protocol.getAgreement(resource.id); // Retrieve the agreement details from chain return { resource, agreement, - pcClient, + protocol, }; } diff --git a/src/config.ts b/src/config.ts index f8ee330..0659ead 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { cyan, red } from "ansis"; -import { readFileSync } from "fs"; +import { readFileSync, statSync } from "fs"; import { join } from "path"; import { ForestRegistryAddress, @@ -9,6 +9,7 @@ import { } from "@forest-protocols/sdk"; import { nonEmptyStringSchema } from "./validation/schemas"; import { fromError } from "zod-validation-error"; +import { Address } from "viem"; function parseEnv() { const environmentSchema = z.object({ @@ -17,6 +18,7 @@ function parseEnv() { NODE_ENV: z.enum(["dev", "production"]).default("dev"), RPC_HOST: nonEmptyStringSchema, CHAIN: z.enum(["anvil", "optimism", "optimism-sepolia"]).default("anvil"), + PORT: z.coerce.number().default(3000), }); const parsedEnv = environmentSchema.safeParse(process.env, {}); @@ -42,25 +44,70 @@ function parseProviderConfig() { [providerTag: string]: z.infer; } = {}; - try { - const fileContent = readFileSync( - join(process.cwd(), "data/providers.json") - ).toString(); - const rootObject = JSON.parse(fileContent); - console.log(`Reading ${cyan("providers.json")}`); - - for (const [name, info] of Object.entries(rootObject)) { - // Validate each provider object - const provider = providerSchema.safeParse(info, {}); - if (provider.error) { - throw new Error(fromError(provider.error).toString()); + const path = join(process.cwd(), "data/providers.json"); + + if (statSync(path, { throwIfNoEntry: false })?.isFile()) { + try { + const fileContent = readFileSync(path).toString(); + const rootObject = JSON.parse(fileContent); + console.log(`Reading ${cyan("providers.json")}`); + + for (const [name, info] of Object.entries(rootObject)) { + // Validate each provider object + const provider = providerSchema.safeParse(info, {}); + if (provider.error) { + throw new Error(fromError(provider.error).toString()); + } + + providers[name] = provider.data!; } + } catch (err: any) { + console.error(red(`Invalid providers.json file: ${err.message}`)); + process.exit(1); + } + } else { + const regex = /^(PROVIDER|BILLING|OPERATOR)_PRIVATE_KEY_([\w]+)$/; + for (const [name, value] of Object.entries(process.env)) { + const match = name.match(regex); + if (match) { + const keyType = match[1]; + const providerTag = match[2]; + + if (!providers[providerTag]) { + providers[providerTag] = { + billingWalletPrivateKey: "0x", + operatorWalletPrivateKey: "0x", + providerWalletPrivateKey: "0x", + }; + } - providers[name] = provider.data!; + switch (keyType) { + case "PROVIDER": + providers[providerTag].providerWalletPrivateKey = value as Address; + break; + case "OPERATOR": + providers[providerTag].operatorWalletPrivateKey = value as Address; + break; + case "BILLING": + providers[providerTag].billingWalletPrivateKey = value as Address; + break; + } + } + } + + for (const [providerTag, keys] of Object.entries(providers)) { + const validation = providerSchema.safeParse(keys); + + if (validation.error) { + const error = validation.error.errors[0]; + console.error( + red( + `Invalid Provider configuration for tag "${providerTag}": ${error.path}: ${error.message}` + ) + ); + process.exit(1); + } } - } catch (err: any) { - console.error(red(`Invalid providers.json file: ${err.message}`)); - process.exit(1); } return providers; diff --git a/src/database/Database.ts b/src/database/Database.ts index fab0360..9e72488 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -40,7 +40,7 @@ class Database { */ async updateResource( id: number, - pcAddress: Address, + ptAddress: Address, values: { name?: string; details?: any; @@ -49,11 +49,11 @@ class Database { isActive?: boolean; } ) { - const pc = await this.getProductCategory(pcAddress); + const pt = await this.getProtocol(ptAddress); - if (!pc) { + if (!pt) { this.logger.error( - `Product category not found ${pcAddress} while looking for the resource #${id}` + `Protocol not found ${ptAddress} while looking for the resource #${id}` ); return; } @@ -64,7 +64,7 @@ class Database { .where( and( eq(schema.resourcesTable.id, id), - eq(schema.resourcesTable.pcAddressId, pc.id) + eq(schema.resourcesTable.ptAddressId, pt.id) ) ); } @@ -72,8 +72,8 @@ class Database { /** * Marks a resource record as deleted (not active) and deletes its details. */ - async deleteResource(id: number, pcAddress: Address) { - await this.updateResource(id, pcAddress, { + async deleteResource(id: number, ptAddress: Address) { + await this.updateResource(id, ptAddress, { isActive: false, deploymentStatus: DeploymentStatus.Closed, details: {}, // TODO: Should we delete all the details (including credentials)? @@ -93,19 +93,22 @@ class Database { async getResource( id: number, ownerAddress: string, - pcAddress: Address + ptAddress: Address ): Promise { - const pc = await this.getProductCategory(pcAddress); + const pt = await this.getProtocol(ptAddress); - if (!pc) { + if (!pt) { return; } - const [resource] = await this.resourceQuery(pc.address).where( + const [resource] = await this.resourceQuery(pt.address).where( and( eq(schema.resourcesTable.id, id), - eq(schema.resourcesTable.ownerAddress, ownerAddress), - eq(schema.resourcesTable.pcAddressId, pc.id) + eq( + sql`LOWER(${schema.resourcesTable.ownerAddress})`, + ownerAddress.toLowerCase() + ), + eq(schema.resourcesTable.ptAddressId, pt.id) ) ); @@ -117,8 +120,8 @@ class Database { /** * Builds a Resource select query */ - private resourceQuery(pcAddress?: string) { - if (!pcAddress) { + private resourceQuery(ptAddress?: string) { + if (!ptAddress) { return this.client .select({ id: schema.resourcesTable.id, @@ -130,15 +133,17 @@ class Database { ownerAddress: sql
`${schema.resourcesTable.ownerAddress}`, offerId: schema.resourcesTable.offerId, providerId: schema.resourcesTable.providerId, - pcAddress: sql
`${schema.productCategoriesTable.address}`, + providerAddress: sql
`${schema.providersTable.ownerAddress}`, + ptAddress: sql
`${schema.protocolsTable.address}`, }) .from(schema.resourcesTable) .innerJoin( - schema.productCategoriesTable, - eq( - schema.productCategoriesTable.address, - schema.resourcesTable.pcAddressId - ) + schema.protocolsTable, + eq(schema.protocolsTable.id, schema.resourcesTable.ptAddressId) + ) + .innerJoin( + schema.providersTable, + eq(schema.providersTable.id, schema.resourcesTable.providerId) ) .$dynamic(); } @@ -154,9 +159,14 @@ class Database { ownerAddress: sql
`${schema.resourcesTable.ownerAddress}`, offerId: schema.resourcesTable.offerId, providerId: schema.resourcesTable.providerId, - pcAddress: sql
`${pcAddress}`, + providerAddress: sql
`${schema.providersTable.ownerAddress}`, + ptAddress: sql
`${ptAddress}`, }) .from(schema.resourcesTable) + .innerJoin( + schema.providersTable, + eq(schema.providersTable.id, schema.resourcesTable.providerId) + ) .$dynamic(); } @@ -168,15 +178,15 @@ class Database { } /** - * Gets product category info stored in the database. + * Gets Protocol from the database. */ - async getProductCategory(address: Address) { - const [pc] = await this.client + async getProtocol(address: Address) { + const [pt] = await this.client .select() - .from(schema.productCategoriesTable) - .where(eq(schema.productCategoriesTable.address, address?.toLowerCase())); + .from(schema.protocolsTable) + .where(eq(schema.protocolsTable.address, address?.toLowerCase())); - return pc; + return pt; } /** @@ -186,7 +196,6 @@ class Database { const [lastBlock] = await this.client .select() .from(schema.blockchainTxsTable) - .orderBy(desc(schema.blockchainTxsTable.height)) .limit(1); return lastBlock?.height; @@ -228,28 +237,36 @@ class Database { * Saves a transaction as processed * @param blockHeight */ - async saveTxAsProcessed(blockHeight: bigint, hash: string) { + async saveTransaction(blockHeight: bigint, hash: string) { await this.client.transaction(async (tx) => { const [transaction] = await tx .select() .from(schema.blockchainTxsTable) - .where(eq(schema.blockchainTxsTable.height, blockHeight)); - - if (!transaction) { - await tx.insert(schema.blockchainTxsTable).values({ - height: blockHeight, - isProcessed: true, - hash, - }); - } else if (transaction.isProcessed === false) { - await tx - .update(schema.blockchainTxsTable) - .set({ isProcessed: true }) - .where(eq(schema.blockchainTxsTable.height, blockHeight)); - } + .where( + and( + eq(schema.blockchainTxsTable.height, blockHeight), + eq(schema.blockchainTxsTable.hash, hash) + ) + ); + + if (transaction) return; + + await this.client.insert(schema.blockchainTxsTable).values({ + height: blockHeight, + hash, + }); }); } + /** + * Deletes all of the records that belongs to a particular block height + */ + async clearBlocks(blockHeight: bigint) { + await this.client + .delete(schema.blockchainTxsTable) + .where(eq(schema.blockchainTxsTable.height, blockHeight)); + } + async saveDetailFiles(contents: string[]) { const values: schema.DbDetailFileInsert[] = []; @@ -261,10 +278,14 @@ class Database { }); } - await this.client - .insert(schema.detailFilesTable) - .values(values) - .onConflictDoNothing(); // TODO: Should we clear the database and sync detail files again? + await this.client.transaction(async (tx) => { + await tx.delete(schema.detailFilesTable); + + await tx + .insert(schema.detailFilesTable) + .values(values) + .onConflictDoNothing(); + }); } async upsertProvider(id: number, detailsLink: string, ownerAddress: Address) { @@ -280,11 +301,6 @@ class Database { ) ); - if (existingProvider) { - // TODO: Update provider - return; - } - const [detailFile] = await tx .select({ id: schema.detailFilesTable.id, @@ -298,6 +314,11 @@ class Database { ); } + if (existingProvider) { + // TODO: Update provider + return; + } + await tx.insert(schema.providersTable).values({ id, ownerAddress: ownerAddress, @@ -306,22 +327,15 @@ class Database { } /** - * Saves a product category to the database. - * @param address Smart contract address of the product category. + * Saves a Protocol to the database. + * @param address Smart contract address of the Protocol. */ - async upsertProductCategory(address: Address, detailsLink: any) { + async upsertProtocol(address: Address, detailsLink: any) { await this.client.transaction(async (tx) => { - const [pc] = await tx + const [pt] = await tx .select() - .from(schema.productCategoriesTable) - .where( - eq(schema.productCategoriesTable.address, address.toLowerCase()) - ); - - // TODO: Update PC - if (pc) { - return; - } + .from(schema.protocolsTable) + .where(eq(schema.protocolsTable.address, address.toLowerCase())); const [detailFile] = await tx .select({ @@ -332,11 +346,16 @@ class Database { if (!detailFile) { throw new Error( - `Details file not found for Product Category ${address}. Please be sure you've placed the details of the Product Category into "data/details/[filename]"` + `Details file not found for Protocol ${address}. Please be sure you've placed the details of the Protocol into "data/details/[filename]"` ); } - await tx.insert(schema.productCategoriesTable).values({ + // TODO: Update Protocol + if (pt) { + return; + } + + await tx.insert(schema.protocolsTable).values({ address: address.toLowerCase(), }); }); diff --git a/src/database/schema.ts b/src/database/schema.ts index c4c11b2..9e4ceff 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -29,13 +29,13 @@ export const resourcesTable = pgTable( providerId: integer("provider_id") .references(() => providersTable.id) .notNull(), - pcAddressId: integer("pc_address_id") - .references(() => productCategoriesTable.id) + ptAddressId: integer("pt_address_id") + .references(() => protocolsTable.id) .notNull(), }, (table) => [ primaryKey({ - columns: [table.id, table.pcAddressId], + columns: [table.id, table.ptAddressId], }), ] ); @@ -44,9 +44,9 @@ relations(resourcesTable, ({ one }) => ({ fields: [resourcesTable.providerId], references: [providersTable.id], }), - productCategory: one(productCategoriesTable, { - fields: [resourcesTable.pcAddressId], - references: [productCategoriesTable.id], + protocol: one(protocolsTable, { + fields: [resourcesTable.ptAddressId], + references: [protocolsTable.id], }), })); @@ -58,11 +58,11 @@ relations(providersTable, ({ many, one }) => ({ resources: many(resourcesTable), })); -export const productCategoriesTable = pgTable("product_categories", { +export const protocolsTable = pgTable("protocols", { id: integer("id").primaryKey().generatedByDefaultAsIdentity(), address: varchar({ length: 100 }).notNull().unique(), }); -relations(productCategoriesTable, ({ many, one }) => ({ +relations(protocolsTable, ({ many, one }) => ({ resources: many(resourcesTable), })); @@ -71,7 +71,6 @@ export const blockchainTxsTable = pgTable( { height: bigint({ mode: "bigint" }).notNull(), hash: varchar({ length: 70 }).notNull(), - isProcessed: boolean("is_processed").notNull(), }, (table) => [ primaryKey({ @@ -89,4 +88,3 @@ export const detailFilesTable = pgTable("detail_files", { export type DbDetailFileInsert = typeof detailFilesTable.$inferInsert; export type DbResource = typeof resourcesTable.$inferSelect; export type DbResourceInsert = typeof resourcesTable.$inferInsert; -export type DbProductCategory = typeof productCategoriesTable.$inferSelect; diff --git a/src/index.ts b/src/index.ts index ce5671d..c2af3ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { Agreement, DeploymentStatus, Offer, - ProductCategoryABI, + ProtocolABI, Status, } from "@forest-protocols/sdk"; import { Address, parseEventLogs } from "viem"; @@ -12,7 +12,7 @@ import { logger } from "./logger"; import { rpcClient } from "./clients"; import { AbstractProvider } from "./abstract/AbstractProvider"; import * as ansis from "ansis"; -import { MainProviderImplementation } from "./product-category/provider"; +import { MainProviderImplementation } from "./protocol/provider"; import { adjectives, animals, @@ -22,6 +22,9 @@ import { import { join } from "path"; import { readdirSync, readFileSync, statSync } from "fs"; import { tryParseJSON } from "./utils"; +import { DetailedOffer } from "./types"; +import express from "express"; +import { config } from "./config"; async function sleep(ms: number) { return await new Promise((res) => setTimeout(res, ms)); @@ -42,11 +45,19 @@ class Program { main: new MainProviderImplementation(), }; - listenedPCAddresses: string[] = []; + listenedPTAddresses: string[] = []; constructor() {} async init() { + // Start a healthcheck HTTP server + const app = express(); + app.get("/health", (_, res) => { + res.send("Running"); + }); + + app.listen(config.PORT); + // Load detail files into the database logger.info("Detail files are loading to the database"); const basePath = join(process.cwd(), "data/details"); @@ -66,19 +77,19 @@ class Program { logger.info(`Provider "${tag}" initializing`); await provider.init(tag); - // Fetch detail links of the Product Categories - logger.info(`Checking Product Categories of "${tag}"`); - const pcs = await Promise.all( - Object.keys(provider.pcClients).map(async (address) => ({ + // Fetch detail links of the Protocols + logger.info(`Checking Protocols of "${tag}"`); + const pts = await Promise.all( + Object.keys(provider.protocols).map(async (address) => ({ address, - detailsLink: await provider.pcClients[address].getDetailsLink(), + detailsLink: await provider.protocols[address].getDetailsLink(), })) ); - // Save PCs to the database - for (const pc of pcs) { - this.listenedPCAddresses.push(pc.address); - await DB.upsertProductCategory(pc.address as Address, pc.detailsLink); + // Save addresses of the Protocols to the database + for (const pt of pts) { + this.listenedPTAddresses.push(pt.address); + await DB.upsertProtocol(pt.address as Address, pt.detailsLink); } logger.info( @@ -89,7 +100,7 @@ class Program { } // Delete duplicated addresses - this.listenedPCAddresses = [...new Set(this.listenedPCAddresses)]; + this.listenedPTAddresses = [...new Set(this.listenedPTAddresses)]; // Check agreement balances at startup then in every minute this.checkAgreementBalances(); @@ -99,7 +110,7 @@ class Program { async processAgreementCreated( agreement: Agreement, offer: Offer, - pcAddress: Address, + ptAddress: Address, provider: AbstractProvider ) { try { @@ -107,17 +118,17 @@ class Program { if (!offerDetailFile) { logger.warning( - `Details file is not found for Offer ${agreement.offerId}@${pcAddress} (Provider ID: ${provider.actorInfo.id})` + `Details file is not found for Offer ${agreement.offerId}@${ptAddress} (Provider ID: ${provider.actorInfo.id})` ); } - const productCategory = await DB.getProductCategory(pcAddress); - const detailedOffer = { + const protocol = await DB.getProtocol(ptAddress); + const detailedOffer: DetailedOffer = { ...offer, // TODO: Validate schema - // If it is a JSON file, try to parse details, if not don't use undefined - details: tryParseJSON(offerDetailFile?.content, false), + // If it is a JSON file, parse it. Otherwise return it as a string. + details: tryParseJSON(offerDetailFile?.content), }; const details = await provider.create(agreement, detailedOffer); @@ -134,7 +145,7 @@ class Program { }), offerId: offer.id, ownerAddress: agreement.userAddr, - pcAddressId: productCategory.id, + ptAddressId: protocol.id, providerId: provider.actorInfo.id, details: { ...details, @@ -160,7 +171,7 @@ class Program { const resource = await DB.getResource( agreement.id, agreement.userAddr, - pcAddress + ptAddress ); if (!resource || !resource.isActive) { @@ -185,7 +196,7 @@ class Program { ); // Update the status and gathered details - await DB.updateResource(agreement.id, pcAddress, { + await DB.updateResource(agreement.id, ptAddress, { deploymentStatus: DeploymentStatus.Running, details: resourceDetails, }); @@ -210,40 +221,40 @@ class Program { logger.error(`Error while creating the resource: ${err.stack}`); // Save the resource as failed - const pc = await DB.getProductCategory(pcAddress); - - // Save that resource as a failed deployment - await DB.createResource({ - id: agreement.id, - deploymentStatus: DeploymentStatus.Failed, - name: "", - pcAddressId: pc.id, - offerId: agreement.offerId, - providerId: provider.actorInfo.id, - ownerAddress: agreement.userAddr, - details: {}, - }); + const pt = await DB.getProtocol(ptAddress); + + // Save that resource as a failed deployment + await DB.createResource({ + id: agreement.id, + deploymentStatus: DeploymentStatus.Failed, + name: "", + ptAddressId: pt.id, + offerId: agreement.offerId, + providerId: provider.actorInfo.id, + ownerAddress: agreement.userAddr, + details: {}, + }); } } async processAgreementClosed( agreement: Agreement, offer: Offer, - pcAddress: Address, + ptAddress: Address, provider: AbstractProvider ) { try { const resource = await DB.getResource( agreement.id, agreement.userAddr, - pcAddress + ptAddress ); if (resource) { const [offerDetailFile] = await DB.getDetailFiles([offer.detailsLink]); if (!offerDetailFile) { logger.warning( - `Details file is not found for Offer ${agreement.offerId}@${pcAddress} (Provider ID: ${provider.actorInfo.id})` + `Details file is not found for Offer ${agreement.offerId}@${ptAddress} (Provider ID: ${provider.actorInfo.id})` ); } @@ -251,7 +262,7 @@ class Program { agreement, { ...offer, - details: tryParseJSON(offerDetailFile?.content, false), + details: tryParseJSON(offerDetailFile?.content), }, resource ); @@ -271,13 +282,13 @@ class Program { logger.error(`Error while deleting the resource: ${err.stack}`); } - await DB.deleteResource(agreement.id, pcAddress); + await DB.deleteResource(agreement.id, ptAddress); } - getProductCategoryByAddress(address: Address) { + getProtocolByAddress(address: Address) { for (const [_, provider] of Object.entries(this.providers)) { - for (const [pcAddress, pc] of Object.entries(provider.pcClients)) { - if (pcAddress == address.toLowerCase()) return pc; + for (const [ptAddress, pt] of Object.entries(provider.protocols)) { + if (ptAddress == address.toLowerCase()) return pt; } } } @@ -311,15 +322,15 @@ class Program { currentBlockNumber )}, skipping...` ); - await DB.saveTxAsProcessed(currentBlockNumber, ""); + await DB.saveTransaction(currentBlockNumber, ""); currentBlockNumber++; continue; } logger.info(`Processing block ${colorNumber(block.number)}`); for (const tx of block.transactions) { - // If the TX is not belong to any of the product category contracts, skip it. - if (!this.listenedPCAddresses.includes(tx.to?.toLowerCase() || "")) { + // If the TX is not belong to any of the Protocol contracts that we are listening, just skip it. + if (!this.listenedPTAddresses.includes(tx.to?.toLowerCase() || "")) { continue; } @@ -334,7 +345,7 @@ class Program { const txRecord = await DB.getTransaction(tx.blockNumber, tx.hash); - if (txRecord?.isProcessed) { + if (txRecord) { logger.info( `TX (${colorHex(tx.hash)}) is already processed, skipping...` ); @@ -342,7 +353,7 @@ class Program { } const events = parseEventLogs({ - abi: ProductCategoryABI, + abi: ProtocolABI, logs: receipt.logs, }); @@ -351,11 +362,11 @@ class Program { event.eventName == "AgreementCreated" || event.eventName == "AgreementClosed" ) { - // Theoretically there is no way for pc to be not found + // Theoretically there is no way for a Protocol to be not found // Because at startup, they are added based on blockchain data. - const pc = this.getProductCategoryByAddress(tx.to!)!; - const agreement = await pc.getAgreement(event.args.id as number); - const offer = await pc.getOffer(agreement.offerId); + const pt = this.getProtocolByAddress(tx.to!)!; + const agreement = await pt.getAgreement(event.args.id as number); + const offer = await pt.getOffer(agreement.offerId); const provider = this.getProviderByAddress(offer.ownerAddr); // NOTE: Is it possible for a provider to be not found? @@ -365,11 +376,11 @@ class Program { logger.warning( `Provider (id: ${ event.args.id - }) not found in product category ${colorHex( - tx.to! - )} for ${colorKeyword(event.eventName)} event. Skipping...` + }) not found in Protocol ${colorHex(tx.to!)} for ${colorKeyword( + event.eventName + )} event. Skipping...` ); - await DB.saveTxAsProcessed( + await DB.saveTransaction( event.blockNumber, event.transactionHash ); @@ -399,16 +410,16 @@ class Program { } // Save the TX as processed - await DB.saveTxAsProcessed( - event.blockNumber, - event.transactionHash - ); + await DB.saveTransaction(event.blockNumber, event.transactionHash); } } } // Empty hash means block itself, so this block is completely processed - await DB.saveTxAsProcessed(currentBlockNumber, ""); + await DB.saveTransaction(currentBlockNumber, ""); + + // Clear all of the data that belongs to the previous block because we have a new "last processed block" + await DB.clearBlocks(currentBlockNumber - 1n); currentBlockNumber++; } } @@ -417,10 +428,10 @@ class Program { logger.info("Checking balances of the agreements", { context: "Checker" }); const closingRequests: Promise[] = []; - // Check all agreements for all providers in all product categories + // Check all agreements for all providers in all Protocols for (const [_, provider] of Object.entries(this.providers)) { - for (const [_, pc] of Object.entries(provider.pcClients)) { - const agreements = await pc.getAllProviderAgreements( + for (const [_, pt] of Object.entries(provider.protocols)) { + const agreements = await pt.getAllProviderAgreements( provider.account!.address ); @@ -429,7 +440,7 @@ class Program { continue; } - const balance = await pc.getAgreementBalance(agreement.id); + const balance = await pt.getAgreementBalance(agreement.id); // If balance of the agreement is ran out of, if (balance <= 0n) { @@ -443,7 +454,7 @@ class Program { // Queue closeAgreement call to the promise list. closingRequests.push( - pc.closeAgreement(agreement.id).catch((err) => { + pt.closeAgreement(agreement.id).catch((err) => { logger.error( `Error thrown while trying to force close agreement ${colorNumber( agreement.id diff --git a/src/product-category/base-provider.ts b/src/protocol/base-provider.ts similarity index 74% rename from src/product-category/base-provider.ts rename to src/protocol/base-provider.ts index cb9829e..1a07710 100644 --- a/src/product-category/base-provider.ts +++ b/src/protocol/base-provider.ts @@ -11,28 +11,27 @@ import { z } from "zod"; import { Address } from "viem"; /** - * The details gathered by the provider from the resource source. - * This is the "details" type of each resource and it is stored in the database. - * @responsible Product Category Owner + * The details will be stored for each created Resource. + * @responsible Protocol Owner */ -export type ExampleProductDetails = ResourceDetails & { +export type ExampleResourceDetails = ResourceDetails & { Example_Detail: number; - /* A detail provided by the resource but won't be sent when the user requested it */ + /* This field won't be sent when the User requested it */ _examplePrivateDetailWontSentToUser: string; }; /** - * Base provider that defines what kind of actions needs to be implemented for the product category. - * @responsible Product Category Owner + * Base Provider that defines what kind of actions needs to be implemented for the Protocol. + * @responsible Protocol Owner */ -export abstract class BaseExampleProductProvider extends AbstractProvider { +export abstract class BaseExampleServiceProvider extends AbstractProvider { /** - * An example function that represents product specific action. This - * function has to be implemented by all of the providers that wants - * to provide this product. + * An example function that represents service specific action. This + * function has to be implemented by all of the Providers who wants to. + * participate to this Protocol. * - * The definition is up to product category owner. So if some of the + * The definition is up to Protocol Owner. So if some of the * arguments are not needed, they can be deleted. Like `agreement` or * `resource` can be deleted if they are unnecessary for the implementation. * @param agreement On-chain agreement data. @@ -50,7 +49,7 @@ export abstract class BaseExampleProductProvider extends AbstractProvider { + ): Promise { /** * TODO: Implement how the resource will be created. */ @@ -51,7 +51,7 @@ export class MainProviderImplementation extends BaseExampleProductProvider { agreement: Agreement, offer: DetailedOffer, resource: Resource - ): Promise { + ): Promise { /** * TODO: Implement how the details retrieved from the resource source. */ diff --git a/src/types.ts b/src/types.ts index 081730d..00a27c1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,7 @@ export type ResourceDetails = { }; /** - * Resource details from the database. + * Resource record from the database. */ export type Resource = { id: number; @@ -34,7 +34,8 @@ export type Resource = { ownerAddress: Address; offerId: number; providerId: number; - pcAddress: Address; + providerAddress: Address; + ptAddress: Address; }; export type DetailedOffer = Offer & {