diff --git a/.github/workflows/cd-core.yaml b/.github/workflows/cd-core.yaml index bbcdbf7793..7594b71299 100644 --- a/.github/workflows/cd-core.yaml +++ b/.github/workflows/cd-core.yaml @@ -13,6 +13,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc + cache: yarn - run: yarn --ignore-scripts name: Install dependencies - run: yarn build diff --git a/.github/workflows/cd-deploy-contracts.yaml b/.github/workflows/cd-deploy-contracts.yaml index f12b081415..134df7ab6b 100644 --- a/.github/workflows/cd-deploy-contracts.yaml +++ b/.github/workflows/cd-deploy-contracts.yaml @@ -38,8 +38,9 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc + cache: yarn - name: Install dependencies - run: npm install --global yarn && yarn --ignore-scripts + run: yarn --ignore-scripts - run: yarn build name: Build core package working-directory: ./packages/core diff --git a/.github/workflows/cd-node-sdk.yaml b/.github/workflows/cd-node-sdk.yaml index 7e9664a9dc..417781d8d3 100644 --- a/.github/workflows/cd-node-sdk.yaml +++ b/.github/workflows/cd-node-sdk.yaml @@ -18,6 +18,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc + cache: yarn - run: yarn --ignore-scripts name: Install dependencies - run: yarn build diff --git a/.github/workflows/cd-python-sdk.yaml b/.github/workflows/cd-python-sdk.yaml index 12d2366e5f..b61c49b815 100644 --- a/.github/workflows/cd-python-sdk.yaml +++ b/.github/workflows/cd-python-sdk.yaml @@ -15,10 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: yarn --ignore-scripts - name: Install dependencies - - run: yarn build - name: Build core package + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + - name: Install dependencies + run: yarn --ignore-scripts + - name: Build core package + run: yarn build working-directory: ./packages/core - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/cd-subgraph.yaml b/.github/workflows/cd-subgraph.yaml index 271dc42ad9..2158b4903f 100644 --- a/.github/workflows/cd-subgraph.yaml +++ b/.github/workflows/cd-subgraph.yaml @@ -27,10 +27,10 @@ jobs: max-parallel: 3 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - name: Set up Node.js + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc + cache: yarn - name: Filter Networks id: filter_networks run: | @@ -47,30 +47,30 @@ jobs: done echo "Match found: $MATCH" echo "::set-output name=continue::$MATCH" - - run: npm install --global yarn && yarn - name: Install dependencies + - name: Install dependencies if: steps.filter_networks.outputs.continue == 'true' - - run: yarn build - name: Build core package + run: yarn + - name: Build core package + if: steps.filter_networks.outputs.continue == 'true' + run: yarn build working-directory: ./packages/core + - name: Install Graph CLI if: steps.filter_networks.outputs.continue == 'true' - - run: yarn global add @graphprotocol/graph-cli@0.71.2 - name: Install Graph CLI + run: yarn global add @graphprotocol/graph-cli@0.71.2 + - name: Authenticate Graph CLI if: steps.filter_networks.outputs.continue == 'true' - - run: graph auth --studio ${API_KEY} - name: Authenticate Graph CLI + run: graph auth --studio ${API_KEY} env: API_KEY: ${{ secrets.HP_GRAPH_API_KEY }} + - name: Generate and build Subgraph if: steps.filter_networks.outputs.continue == 'true' - - run: yarn generate && yarn build - name: Generate and build Subgraph + run: yarn generate && yarn build working-directory: ./packages/sdk/typescript/subgraph env: NETWORK: ${{ matrix.network.name }} + - name: Deploy Subgraph if: steps.filter_networks.outputs.continue == 'true' - - run: graph deploy --studio ${NETWORK} -l ${{ github.event.inputs.label }} - name: Deploy Subgraph + run: graph deploy --studio ${NETWORK} -l ${{ github.event.inputs.label }} working-directory: ./packages/sdk/typescript/subgraph env: NETWORK: ${{ matrix.network.name }} - if: steps.filter_networks.outputs.continue == 'true' diff --git a/.github/workflows/ci-dependency-review.yaml b/.github/workflows/ci-dependency-review.yaml index 860a59df2b..c871472b2f 100644 --- a/.github/workflows/ci-dependency-review.yaml +++ b/.github/workflows/ci-dependency-review.yaml @@ -1,5 +1,9 @@ -name: "Dependency Review" -on: [pull_request] +name: Dependency Review +on: + pull_request: + branches: + - develop + - main permissions: contents: read @@ -8,7 +12,6 @@ jobs: dependency-review: runs-on: ubuntu-latest steps: - - name: "Checkout Repository" - uses: actions/checkout@v4.1.1 - - name: "Dependency Review" + - uses: actions/checkout@v4.1.1 + - name: Dependency Review uses: actions/dependency-review-action@v4.5.0 diff --git a/.github/workflows/ci-lint.yaml b/.github/workflows/ci-lint.yaml index 7b4aa086c1..893fa834ca 100644 --- a/.github/workflows/ci-lint.yaml +++ b/.github/workflows/ci-lint.yaml @@ -2,10 +2,8 @@ name: Lint check on: push: - branches: - - "main" - pull_request: - workflow_dispatch: + paths-ignore: + - 'packages/examples/cvat/**' jobs: lint: @@ -16,7 +14,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn lint - name: Run lint + cache: yarn + - name: Install dependencies + run: yarn + - name: Run lint + run: yarn lint diff --git a/.github/workflows/ci-test-core.yaml b/.github/workflows/ci-test-core.yaml index 1b88a8c06b..38b669eea3 100644 --- a/.github/workflows/ci-test-core.yaml +++ b/.github/workflows/ci-test-core.yaml @@ -2,12 +2,8 @@ name: Protocol check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - workflow_dispatch: jobs: core-test: @@ -18,7 +14,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn --ignore-scripts - name: Install dependencies - - run: yarn workspace @human-protocol/core test - name: Run protocol test + cache: yarn + - name: Install dependencies + run: yarn --ignore-scripts + - name: Run protocol test + run: yarn workspace @human-protocol/core test diff --git a/.github/workflows/ci-test-dashboard.yaml b/.github/workflows/ci-test-dashboard.yaml index 39a38a767d..77f50070c6 100644 --- a/.github/workflows/ci-test-dashboard.yaml +++ b/.github/workflows/ci-test-dashboard.yaml @@ -2,14 +2,10 @@ name: Dashboard Check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/dashboard/**' - workflow_dispatch: jobs: dashboard-server-test: @@ -20,7 +16,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/dashboard-server test - name: Run dashboard Server test + cache: yarn + - name: Install dependencies + run: yarn + - name: Run dashboard Server test + run: yarn workspace @human-protocol/dashboard-server test diff --git a/.github/workflows/ci-test-faucet-server.yaml b/.github/workflows/ci-test-faucet-server.yaml index d52fcc56bb..d96490a924 100644 --- a/.github/workflows/ci-test-faucet-server.yaml +++ b/.github/workflows/ci-test-faucet-server.yaml @@ -2,14 +2,10 @@ name: Faucet server check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/faucet/server/**' - workflow_dispatch: jobs: faucet-server-test: @@ -20,10 +16,11 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: cp .env.example .env - name: Create .env file + cache: yarn + - name: Install dependencies + run: yarn + - name: Create .env file + run: cp .env.example .env working-directory: packages/apps/faucet/server - - run: yarn workspace @human-protocol/faucet-server test - name: Run faucet/server test + - name: Run faucet/server test + run: yarn workspace @human-protocol/faucet-server test diff --git a/.github/workflows/ci-test-fortune.yaml b/.github/workflows/ci-test-fortune.yaml index 3b361ef475..71c3ed24ce 100644 --- a/.github/workflows/ci-test-fortune.yaml +++ b/.github/workflows/ci-test-fortune.yaml @@ -2,14 +2,10 @@ name: Fortune check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/fortune/**' - workflow_dispatch: jobs: fortune-exchange-oracle-test: @@ -20,16 +16,22 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/fortune-exchange-oracle-server test - name: Run Exchange Oracle tests + cache: yarn + - name: Install dependencies + run: yarn + - name: Run Exchange Oracle tests + run: yarn workspace @human-protocol/fortune-exchange-oracle-server test + fortune-recording-oracle-test: name: Fortune Recording Oracle Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/fortune-recording-oracle test - name: Run Recording Oracle tests + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + - name: Install dependencies + run: yarn + - name: Run Recording Oracle tests + run: yarn workspace @human-protocol/fortune-recording-oracle test diff --git a/.github/workflows/ci-test-human-app.yaml b/.github/workflows/ci-test-human-app.yaml index 9fb8396dda..2a8a17a4e5 100644 --- a/.github/workflows/ci-test-human-app.yaml +++ b/.github/workflows/ci-test-human-app.yaml @@ -2,14 +2,10 @@ name: Human App Check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/human-app/**' - workflow_dispatch: jobs: job-app-server-test: @@ -20,7 +16,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/human-app-server test - name: Run Job Human App unit tests + cache: yarn + - name: Install dependencies + run: yarn + - name: Run Job Human App unit tests + run: yarn workspace @human-protocol/human-app-server test diff --git a/.github/workflows/ci-test-job-launcher.yaml b/.github/workflows/ci-test-job-launcher.yaml index 0511923a5e..48c3ce0807 100644 --- a/.github/workflows/ci-test-job-launcher.yaml +++ b/.github/workflows/ci-test-job-launcher.yaml @@ -2,14 +2,10 @@ name: Job Launcher Check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/job-launcher/**' - workflow_dispatch: jobs: job-launcher-server-test: @@ -17,7 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/job-launcher-server test - name: Run Job Launcher Server test + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + - name: Install dependencies + run: yarn + - name: Run Job Launcher Server test + run: yarn workspace @human-protocol/job-launcher-server test diff --git a/.github/workflows/ci-test-node-sdk.yaml b/.github/workflows/ci-test-node-sdk.yaml index 93a89709c2..d623ebf051 100644 --- a/.github/workflows/ci-test-node-sdk.yaml +++ b/.github/workflows/ci-test-node-sdk.yaml @@ -2,13 +2,9 @@ name: Node.js SDK check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - workflow_dispatch: jobs: node-sdk-test: @@ -19,10 +15,11 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn --ignore-scripts - name: Install dependencies - - run: yarn build - name: Build core package + cache: yarn + - name: Install dependencies + run: yarn --ignore-scripts + - name: Build core package + run: yarn build working-directory: ./packages/core - - run: yarn workspace @human-protocol/sdk test - name: Run Node.js SDK test + - name: Run Node.js SDK test + run: yarn workspace @human-protocol/sdk test diff --git a/.github/workflows/ci-test-python-sdk.yaml b/.github/workflows/ci-test-python-sdk.yaml index 1094c03b05..c91078749f 100644 --- a/.github/workflows/ci-test-python-sdk.yaml +++ b/.github/workflows/ci-test-python-sdk.yaml @@ -2,13 +2,9 @@ name: Python SDK check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/python/human-protocol-sdk/**' - workflow_dispatch: jobs: python-test: @@ -16,10 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install Node + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: yarn + - name: Install core package dependencies run: yarn --ignore-scripts - - run: yarn build - name: Build core package + - name: Build core package + run: yarn build working-directory: ./packages/core - name: Set up Python 3.10 uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-test-reputation-oracle.yaml b/.github/workflows/ci-test-reputation-oracle.yaml index ccaacf1e5d..c354f74b00 100644 --- a/.github/workflows/ci-test-reputation-oracle.yaml +++ b/.github/workflows/ci-test-reputation-oracle.yaml @@ -2,14 +2,10 @@ name: Reputation Oracle Check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/human-protocol-sdk/**' - 'packages/apps/reputation-oracle/**' - workflow_dispatch: jobs: reputation-oracle-test: @@ -20,7 +16,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/reputation-oracle test - name: Run reputation oracle test + cache: yarn + - name: Install dependencies + run: yarn + - name: Run reputation oracle test + run: yarn workspace @human-protocol/reputation-oracle test diff --git a/.github/workflows/ci-test-subgraph.yaml b/.github/workflows/ci-test-subgraph.yaml index 30f369d90c..cca177b802 100644 --- a/.github/workflows/ci-test-subgraph.yaml +++ b/.github/workflows/ci-test-subgraph.yaml @@ -2,13 +2,9 @@ name: Subgraph check on: push: - branches: - - 'main' - pull_request: paths: - 'packages/core/**' - 'packages/sdk/typescript/subgraph/**' - workflow_dispatch: jobs: subgraph-test: @@ -20,7 +16,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - run: npm install --global yarn && yarn - name: Install dependencies - - run: yarn workspace @human-protocol/subgraph test - name: Run subgraph test + cache: yarn + - name: Install dependencies + run: yarn + - name: Run subgraph test + run: yarn workspace @human-protocol/subgraph test diff --git a/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md b/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md index 9d4645d552..7638365935 100644 --- a/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md +++ b/docs/sdk/python/human_protocol_sdk.escrow.escrow_utils.md @@ -108,45 +108,48 @@ Get an array of escrow addresses based on the specified filter parameters. ) ``` -#### *static* get_status_events(chain_id, statuses=None, date_from=None, date_to=None, launcher=None, first=10, skip=0, order_direction=OrderDirection.DESC) +#### *static* get_payouts(filter) + +Fetch payouts from the subgraph based on the provided filter. + +* **Parameters:** + **filter** ([`PayoutFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.PayoutFilter)) – Object containing all the necessary parameters to filter payouts. +* **Return List[Payout]:** + List of payouts matching the query parameters. +* **Raises:** + [**EscrowClientError**](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an unsupported chain ID or invalid addresses are provided. +* **Return type:** + `List`[[`Payout`](#human_protocol_sdk.escrow.escrow_utils.Payout)] + +#### *static* get_status_events(filter) Retrieve status events for specified networks and statuses within a date range. * **Parameters:** - * **chain_id** ([`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)) – Network to request data. - * **(****Optional****[****List****[****Status****]****]****)** (*statuses*) – List of statuses to filter by. - * **(****Optional****[****datetime****]****)** (*date_to*) – Start date for the query range. - * **(****Optional****[****datetime****]****)** – End date for the query range. - * **(****Optional****[****str****]****)** (*launcher*) – Address of the launcher to filter by. - * **(****int****)** (*skip*) – Number of items per page. - * **(****int****)** – Page number to retrieve. - * **(****OrderDirection****)** (*order_direction*) – Order of results, “asc” or “desc”. + **filter** ([`StatusEventFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatusEventFilter)) – Object containing all the necessary parameters to filter status events. * **Return List[StatusEvent]:** List of status events matching the query parameters. * **Raises:** [**EscrowClientError**](human_protocol_sdk.escrow.escrow_client.md#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an unsupported chain ID or invalid launcher address is provided. -* **Example:** - ```python - from datetime import datetime - from human_protocol_sdk.constants import ChainId, Status - from human_protocol_sdk.escrow import EscrowUtils - - print( - EscrowUtils.get_status_events( - chain_id=ChainId.POLYGON_AMOY, - statuses=[Status.Pending, Status.Paid], - date_from=datetime(2023, 1, 1), - date_to=datetime(2023, 12, 31), - launcher="0x1234567890abcdef1234567890abcdef12345678", - first=20, - skip=0, - order_direction=OrderDirection.DESC - ) - ) - ``` * **Return type:** `List`[[`StatusEvent`](#human_protocol_sdk.escrow.escrow_utils.StatusEvent)] +### *class* human_protocol_sdk.escrow.escrow_utils.Payout(id, escrow_address, recipient, amount, created_at) + +Bases: `object` + +Initializes a Payout instance. + +* **Parameters:** + * **id** (`str`) – The id of the payout. + * **chain_id** – The chain identifier where the payout occurred. + * **escrow_address** (`str`) – The address of the escrow that executed the payout. + * **recipient** (`str`) – The address of the recipient. + * **amount** (`int`) – The amount of the payout. + * **created_at** (`int`) – The time of creation of the payout. + +#### \_\_init_\_(id, escrow_address, recipient, amount, created_at) + ### *class* human_protocol_sdk.escrow.escrow_utils.StatusEvent(timestamp, status, chain_id, escrow_address) Bases: `object` diff --git a/docs/sdk/python/human_protocol_sdk.escrow.md b/docs/sdk/python/human_protocol_sdk.escrow.md index 8ee3c93e5b..deed876bfc 100644 --- a/docs/sdk/python/human_protocol_sdk.escrow.md +++ b/docs/sdk/python/human_protocol_sdk.escrow.md @@ -48,6 +48,9 @@ obtain information from both the contracts and subgraph. * [`EscrowUtils`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils) * [`EscrowUtils.get_escrow()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils.get_escrow) * [`EscrowUtils.get_escrows()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils.get_escrows) + * [`EscrowUtils.get_payouts()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils.get_payouts) * [`EscrowUtils.get_status_events()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils.get_status_events) + * [`Payout`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.Payout) + * [`Payout.__init__()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.Payout.__init__) * [`StatusEvent`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.StatusEvent) * [`StatusEvent.__init__()`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.StatusEvent.__init__) diff --git a/docs/sdk/python/human_protocol_sdk.filter.md b/docs/sdk/python/human_protocol_sdk.filter.md index 9572274f11..ae0d301983 100644 --- a/docs/sdk/python/human_protocol_sdk.filter.md +++ b/docs/sdk/python/human_protocol_sdk.filter.md @@ -30,21 +30,25 @@ Bases: `Exception` Raises when some error happens when building filter object. -### *class* human_protocol_sdk.filter.PayoutFilter(escrow_address=None, recipient=None, date_from=None, date_to=None) +### *class* human_protocol_sdk.filter.PayoutFilter(chain_id, escrow_address=None, recipient=None, date_from=None, date_to=None, first=10, skip=0, order_direction=OrderDirection.DESC) Bases: `object` A class used to filter payout requests. -#### \_\_init_\_(escrow_address=None, recipient=None, date_from=None, date_to=None) +#### \_\_init_\_(chain_id, escrow_address=None, recipient=None, date_from=None, date_to=None, first=10, skip=0, order_direction=OrderDirection.DESC) -Initializes a PayoutFilter instance. +Initializes a filter for payouts. * **Parameters:** - * **escrow_address** (`Optional`[`str`]) – Escrow address - * **recipient** (`Optional`[`str`]) – Recipient address - * **date_from** (`Optional`[`datetime`]) – Created from date - * **date_to** (`Optional`[`datetime`]) – Created to date + * **chain_id** ([`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)) – The chain ID where the payouts are recorded. + * **escrow_address** (`Optional`[`str`]) – Optional escrow address to filter payouts. + * **recipient** (`Optional`[`str`]) – Optional recipient address to filter payouts. + * **date_from** (`Optional`[`datetime`]) – Optional start date for filtering. + * **date_to** (`Optional`[`datetime`]) – Optional end date for filtering. + * **first** (`int`) – Optional number of payouts per page. Default is 10. + * **skip** (`int`) – Optional number of payouts to skip. Default is 0. + * **order_direction** ([`OrderDirection`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.OrderDirection)) – Optional order direction. Default is DESC. ### *class* human_protocol_sdk.filter.StatisticsFilter(date_from=None, date_to=None, first=10, skip=0, order_direction=OrderDirection.ASC) @@ -74,6 +78,24 @@ A class used to filter statistical data. #### \_\_init_\_(date_from=None, date_to=None, first=10, skip=0, order_direction=OrderDirection.ASC) +### *class* human_protocol_sdk.filter.StatusEventFilter(chain_id, statuses=None, date_from=None, date_to=None, launcher=None, first=10, skip=0, order_direction=OrderDirection.DESC) + +Bases: `object` + +#### \_\_init_\_(chain_id, statuses=None, date_from=None, date_to=None, launcher=None, first=10, skip=0, order_direction=OrderDirection.DESC) + +Initializes a filter for status events. + +* **Parameters:** + * **chain_id** ([`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)) – The chain ID where the events are recorded. + * **statuses** (`Optional`[`List`[[`Status`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.Status)]]) – Optional list of statuses to filter by. + * **date_from** (`Optional`[`datetime`]) – Optional start date for filtering. + * **date_to** (`Optional`[`datetime`]) – Optional end date for filtering. + * **launcher** (`Optional`[`str`]) – Optional launcher address to filter by. + * **first** (`int`) – Optional number of events per page. Default is 10. + * **skip** (`int`) – Optional number of events to skip. Default is 0. + * **order_direction** ([`OrderDirection`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.OrderDirection)) – Optional order direction. Default is DESC. + ### *class* human_protocol_sdk.filter.TransactionFilter(chain_id, from_address=None, to_address=None, start_date=None, end_date=None, start_block=None, end_block=None, first=10, skip=0, order_direction=OrderDirection.DESC) Bases: `object` @@ -97,3 +119,21 @@ Initializes a TransactionsFilter instance. * **order** – Order of results, “asc” or “desc” * **Raises:** **ValueError** – If start_date is after end_date + +### *class* human_protocol_sdk.filter.WorkerFilter(chain_id, worker_address=None, order_by=None, order_direction=OrderDirection.DESC, first=10, skip=0) + +Bases: `object` + +A class used to filter workers. + +#### \_\_init_\_(chain_id, worker_address=None, order_by=None, order_direction=OrderDirection.DESC, first=10, skip=0) + +Initializes a WorkerFilter instance. + +* **Parameters:** + * **chain_id** ([`ChainId`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.ChainId)) – Chain ID to request data + * **worker_address** (`Optional`[`str`]) – Address to filter by + * **order_by** (`Optional`[`str`]) – Property to order by, e.g., “payoutCount” + * **order_direction** ([`OrderDirection`](human_protocol_sdk.constants.md#human_protocol_sdk.constants.OrderDirection)) – Order direction of results, “asc” or “desc” + * **first** (`int`) – Number of items per page + * **skip** (`int`) – Number of items to skip (for pagination) diff --git a/docs/sdk/python/human_protocol_sdk.legacy_encryption.md b/docs/sdk/python/human_protocol_sdk.legacy_encryption.md index 920fcd5dcd..5677a8464c 100644 --- a/docs/sdk/python/human_protocol_sdk.legacy_encryption.md +++ b/docs/sdk/python/human_protocol_sdk.legacy_encryption.md @@ -17,7 +17,7 @@ Encryption class specialized in encrypting and decrypting a byte string. #### CIPHER -Cipher algorithm defintion. +Cipher algorithm definition. alias of `AES` diff --git a/docs/sdk/python/human_protocol_sdk.md b/docs/sdk/python/human_protocol_sdk.md index 23fa99ab62..7f19d62801 100644 --- a/docs/sdk/python/human_protocol_sdk.md +++ b/docs/sdk/python/human_protocol_sdk.md @@ -46,6 +46,7 @@ * [Module](human_protocol_sdk.escrow.escrow_utils.md#module) * [`EscrowData`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowData) * [`EscrowUtils`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.EscrowUtils) + * [`Payout`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.Payout) * [`StatusEvent`](human_protocol_sdk.escrow.escrow_utils.md#human_protocol_sdk.escrow.escrow_utils.StatusEvent) * [human_protocol_sdk.kvstore package](human_protocol_sdk.kvstore.md) * [Submodules](human_protocol_sdk.kvstore.md#submodules) @@ -164,8 +165,12 @@ * [`PayoutFilter.__init__()`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.PayoutFilter.__init__) * [`StatisticsFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatisticsFilter) * [`StatisticsFilter.__init__()`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatisticsFilter.__init__) + * [`StatusEventFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatusEventFilter) + * [`StatusEventFilter.__init__()`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatusEventFilter.__init__) * [`TransactionFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.TransactionFilter) * [`TransactionFilter.__init__()`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.TransactionFilter.__init__) + * [`WorkerFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.WorkerFilter) + * [`WorkerFilter.__init__()`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.WorkerFilter.__init__) * [human_protocol_sdk.legacy_encryption module](human_protocol_sdk.legacy_encryption.md) * [`DecryptionError`](human_protocol_sdk.legacy_encryption.md#human_protocol_sdk.legacy_encryption.DecryptionError) * [`Encryption`](human_protocol_sdk.legacy_encryption.md#human_protocol_sdk.legacy_encryption.Encryption) diff --git a/docs/sdk/python/index.md b/docs/sdk/python/index.md index 950c44c068..75b6eaeb1e 100644 --- a/docs/sdk/python/index.md +++ b/docs/sdk/python/index.md @@ -55,7 +55,9 @@ pip install human-protocol-sdk[agreement] * [`FilterError`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.FilterError) * [`PayoutFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.PayoutFilter) * [`StatisticsFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatisticsFilter) + * [`StatusEventFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.StatusEventFilter) * [`TransactionFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.TransactionFilter) + * [`WorkerFilter`](human_protocol_sdk.filter.md#human_protocol_sdk.filter.WorkerFilter) * [human_protocol_sdk.legacy_encryption module](human_protocol_sdk.legacy_encryption.md) * [`DecryptionError`](human_protocol_sdk.legacy_encryption.md#human_protocol_sdk.legacy_encryption.DecryptionError) * [`Encryption`](human_protocol_sdk.legacy_encryption.md#human_protocol_sdk.legacy_encryption.Encryption) diff --git a/docs/sdk/typescript/base/classes/BaseEthersClient.md b/docs/sdk/typescript/base/classes/BaseEthersClient.md index 4487e3c0cb..a0277ae359 100644 --- a/docs/sdk/typescript/base/classes/BaseEthersClient.md +++ b/docs/sdk/typescript/base/classes/BaseEthersClient.md @@ -6,7 +6,7 @@ # Class: `abstract` BaseEthersClient -Defined in: [base.ts:10](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L10) +Defined in: [base.ts:10](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L10) ## Introduction @@ -24,7 +24,7 @@ This class is used as a base class for other clients making on-chain calls. > **new BaseEthersClient**(`runner`, `networkData`): [`BaseEthersClient`](BaseEthersClient.md) -Defined in: [base.ts:20](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) +Defined in: [base.ts:20](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L20) **BaseClient constructor** @@ -52,7 +52,7 @@ The network information required to connect to the contracts > **networkData**: [`NetworkData`](../../types/type-aliases/NetworkData.md) -Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) *** @@ -60,4 +60,4 @@ Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06 > `protected` **runner**: `ContractRunner` -Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) diff --git a/docs/sdk/typescript/encryption/classes/Encryption.md b/docs/sdk/typescript/encryption/classes/Encryption.md index 8b4e009617..822c353959 100644 --- a/docs/sdk/typescript/encryption/classes/Encryption.md +++ b/docs/sdk/typescript/encryption/classes/Encryption.md @@ -6,7 +6,7 @@ # Class: Encryption -Defined in: [encryption.ts:58](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L58) +Defined in: [encryption.ts:58](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L58) ## Introduction @@ -53,7 +53,7 @@ const encryption = await Encryption.build(privateKey, passphrase); > **new Encryption**(`privateKey`): [`Encryption`](Encryption.md) -Defined in: [encryption.ts:66](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L66) +Defined in: [encryption.ts:66](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L66) Constructor for the Encryption class. @@ -75,7 +75,7 @@ The private key. > **decrypt**(`message`, `publicKey`?): `Promise`\<`Uint8Array`\<`ArrayBufferLike`\>\> -Defined in: [encryption.ts:194](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L194) +Defined in: [encryption.ts:194](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L194) This function decrypts messages using the private key. In addition, the public key can be added for signature verification. @@ -129,7 +129,7 @@ const resultMessage = await encryption.decrypt('message'); > **sign**(`message`): `Promise`\<`string`\> -Defined in: [encryption.ts:251](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L251) +Defined in: [encryption.ts:251](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L251) This function signs a message using the private key used to initialize the client. @@ -165,7 +165,7 @@ const resultMessage = await encryption.sign('message'); > **signAndEncrypt**(`message`, `publicKeys`): `Promise`\<`string`\> -Defined in: [encryption.ts:142](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L142) +Defined in: [encryption.ts:142](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L142) This function signs and encrypts a message using the private key used to initialize the client and the specified public keys. @@ -232,7 +232,7 @@ const resultMessage = await encryption.signAndEncrypt('message', publicKeys); > `static` **build**(`privateKeyArmored`, `passphrase`?): `Promise`\<[`Encryption`](Encryption.md)\> -Defined in: [encryption.ts:77](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L77) +Defined in: [encryption.ts:77](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L77) Builds an Encryption instance by decrypting the private key from an encrypted private key and passphrase. diff --git a/docs/sdk/typescript/encryption/classes/EncryptionUtils.md b/docs/sdk/typescript/encryption/classes/EncryptionUtils.md index 9e02dcd753..8d1127cea6 100644 --- a/docs/sdk/typescript/encryption/classes/EncryptionUtils.md +++ b/docs/sdk/typescript/encryption/classes/EncryptionUtils.md @@ -6,7 +6,7 @@ # Class: EncryptionUtils -Defined in: [encryption.ts:290](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L290) +Defined in: [encryption.ts:290](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L290) ## Introduction @@ -48,7 +48,7 @@ const keyPair = await EncryptionUtils.generateKeyPair('Human', 'human@hmt.ai'); > `static` **encrypt**(`message`, `publicKeys`): `Promise`\<`string`\> -Defined in: [encryption.ts:444](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L444) +Defined in: [encryption.ts:444](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L444) This function encrypts a message using the specified public keys. @@ -111,7 +111,7 @@ const result = await EncryptionUtils.encrypt('message', publicKeys); > `static` **generateKeyPair**(`name`, `email`, `passphrase`): `Promise`\<[`IKeyPair`](../../interfaces/interfaces/IKeyPair.md)\> -Defined in: [encryption.ts:382](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L382) +Defined in: [encryption.ts:382](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L382) This function generates a key pair for encryption and decryption. @@ -158,7 +158,7 @@ const result = await EncryptionUtils.generateKeyPair(name, email, passphrase); > `static` **getSignedData**(`message`): `Promise`\<`string`\> -Defined in: [encryption.ts:351](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L351) +Defined in: [encryption.ts:351](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L351) This function gets signed data from a signed message. @@ -190,7 +190,7 @@ const signedData = await EncryptionUtils.getSignedData('message'); > `static` **isEncrypted**(`message`): `boolean` -Defined in: [encryption.ts:494](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L494) +Defined in: [encryption.ts:494](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L494) Verifies if a message appears to be encrypted with OpenPGP. @@ -238,7 +238,7 @@ if (isEncrypted) { > `static` **verify**(`message`, `publicKey`): `Promise`\<`boolean`\> -Defined in: [encryption.ts:318](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L318) +Defined in: [encryption.ts:318](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts#L318) This function verifies the signature of a signed message using the public key. diff --git a/docs/sdk/typescript/enums/enumerations/ChainId.md b/docs/sdk/typescript/enums/enumerations/ChainId.md index fc57b00227..5627a76ac9 100644 --- a/docs/sdk/typescript/enums/enumerations/ChainId.md +++ b/docs/sdk/typescript/enums/enumerations/ChainId.md @@ -6,7 +6,7 @@ # Enumeration: ChainId -Defined in: [enums.ts:1](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L1) +Defined in: [enums.ts:1](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L1) ## Enumeration Members @@ -14,7 +14,7 @@ Defined in: [enums.ts:1](https://github.com/humanprotocol/human-protocol/blob/06 > **ALL**: `-1` -Defined in: [enums.ts:2](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L2) +Defined in: [enums.ts:2](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L2) *** @@ -22,7 +22,7 @@ Defined in: [enums.ts:2](https://github.com/humanprotocol/human-protocol/blob/06 > **BSC\_MAINNET**: `56` -Defined in: [enums.ts:5](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L5) +Defined in: [enums.ts:5](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L5) *** @@ -30,7 +30,7 @@ Defined in: [enums.ts:5](https://github.com/humanprotocol/human-protocol/blob/06 > **BSC\_TESTNET**: `97` -Defined in: [enums.ts:6](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L6) +Defined in: [enums.ts:6](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L6) *** @@ -38,7 +38,7 @@ Defined in: [enums.ts:6](https://github.com/humanprotocol/human-protocol/blob/06 > **LOCALHOST**: `1338` -Defined in: [enums.ts:9](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L9) +Defined in: [enums.ts:9](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L9) *** @@ -46,7 +46,7 @@ Defined in: [enums.ts:9](https://github.com/humanprotocol/human-protocol/blob/06 > **MAINNET**: `1` -Defined in: [enums.ts:3](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L3) +Defined in: [enums.ts:3](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L3) *** @@ -54,7 +54,7 @@ Defined in: [enums.ts:3](https://github.com/humanprotocol/human-protocol/blob/06 > **POLYGON**: `137` -Defined in: [enums.ts:7](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L7) +Defined in: [enums.ts:7](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L7) *** @@ -62,7 +62,7 @@ Defined in: [enums.ts:7](https://github.com/humanprotocol/human-protocol/blob/06 > **POLYGON\_AMOY**: `80002` -Defined in: [enums.ts:8](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L8) +Defined in: [enums.ts:8](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L8) *** @@ -70,4 +70,4 @@ Defined in: [enums.ts:8](https://github.com/humanprotocol/human-protocol/blob/06 > **SEPOLIA**: `11155111` -Defined in: [enums.ts:4](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L4) +Defined in: [enums.ts:4](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L4) diff --git a/docs/sdk/typescript/enums/enumerations/OperatorCategory.md b/docs/sdk/typescript/enums/enumerations/OperatorCategory.md index db2feb91a4..3d5d13af20 100644 --- a/docs/sdk/typescript/enums/enumerations/OperatorCategory.md +++ b/docs/sdk/typescript/enums/enumerations/OperatorCategory.md @@ -6,7 +6,7 @@ # Enumeration: OperatorCategory -Defined in: [enums.ts:17](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L17) +Defined in: [enums.ts:17](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L17) ## Enumeration Members @@ -14,7 +14,7 @@ Defined in: [enums.ts:17](https://github.com/humanprotocol/human-protocol/blob/0 > **MACHINE\_LEARNING**: `"machine_learning"` -Defined in: [enums.ts:18](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L18) +Defined in: [enums.ts:18](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L18) *** @@ -22,4 +22,4 @@ Defined in: [enums.ts:18](https://github.com/humanprotocol/human-protocol/blob/0 > **MARKET\_MAKING**: `"market_making"` -Defined in: [enums.ts:19](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L19) +Defined in: [enums.ts:19](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L19) diff --git a/docs/sdk/typescript/enums/enumerations/OrderDirection.md b/docs/sdk/typescript/enums/enumerations/OrderDirection.md index a78e620292..e980964850 100644 --- a/docs/sdk/typescript/enums/enumerations/OrderDirection.md +++ b/docs/sdk/typescript/enums/enumerations/OrderDirection.md @@ -6,7 +6,7 @@ # Enumeration: OrderDirection -Defined in: [enums.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L12) +Defined in: [enums.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L12) ## Enumeration Members @@ -14,7 +14,7 @@ Defined in: [enums.ts:12](https://github.com/humanprotocol/human-protocol/blob/0 > **ASC**: `"asc"` -Defined in: [enums.ts:13](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L13) +Defined in: [enums.ts:13](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L13) *** @@ -22,4 +22,4 @@ Defined in: [enums.ts:13](https://github.com/humanprotocol/human-protocol/blob/0 > **DESC**: `"desc"` -Defined in: [enums.ts:14](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L14) +Defined in: [enums.ts:14](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/enums.ts#L14) diff --git a/docs/sdk/typescript/escrow/classes/EscrowClient.md b/docs/sdk/typescript/escrow/classes/EscrowClient.md index 9a27e2ca4e..445e354d15 100644 --- a/docs/sdk/typescript/escrow/classes/EscrowClient.md +++ b/docs/sdk/typescript/escrow/classes/EscrowClient.md @@ -6,7 +6,7 @@ # Class: EscrowClient -Defined in: [escrow.ts:134](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L134) +Defined in: [escrow.ts:141](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L141) ## Introduction @@ -86,7 +86,7 @@ const escrowClient = await EscrowClient.build(provider); > **new EscrowClient**(`runner`, `networkData`): [`EscrowClient`](EscrowClient.md) -Defined in: [escrow.ts:143](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L143) +Defined in: [escrow.ts:150](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L150) **EscrowClient constructor** @@ -118,7 +118,7 @@ The network information required to connect to the Escrow contract > **networkData**: [`NetworkData`](../../types/type-aliases/NetworkData.md) -Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) #### Inherited from @@ -130,7 +130,7 @@ Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06 > `protected` **runner**: `ContractRunner` -Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) #### Inherited from @@ -142,7 +142,7 @@ Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06 > **addTrustedHandlers**(`escrowAddress`, `trustedHandlers`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:771](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L771) +Defined in: [escrow.ts:778](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L778) This function adds an array of addresses to the trusted handlers list. @@ -197,7 +197,7 @@ await escrowClient.addTrustedHandlers('0x62dD51230A30401C455c8398d06F85e4EaB6309 > **bulkPayOut**(`escrowAddress`, `recipients`, `amounts`, `finalResultsUrl`, `finalResultsHash`, `txId`, `forceComplete`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:604](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L604) +Defined in: [escrow.ts:611](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L611) This function pays out the amounts specified to the workers and sets the URL of the final results file. @@ -287,7 +287,7 @@ await escrowClient.bulkPayOut('0x62dD51230A30401C455c8398d06F85e4EaB6309f', reci > **cancel**(`escrowAddress`, `txOptions`?): `Promise`\<[`EscrowCancel`](../../types/type-aliases/EscrowCancel.md)\> -Defined in: [escrow.ts:685](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L685) +Defined in: [escrow.ts:692](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L692) This function cancels the specified escrow and sends the balance to the canceler. @@ -335,7 +335,7 @@ await escrowClient.cancel('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); > **complete**(`escrowAddress`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:543](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L543) +Defined in: [escrow.ts:550](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L550) This function sets the status of an escrow to completed. @@ -383,7 +383,7 @@ await escrowClient.complete('0x62dD51230A30401C455c8398d06F85e4EaB6309f'); > **createBulkPayoutTransaction**(`escrowAddress`, `recipients`, `amounts`, `finalResultsUrl`, `finalResultsHash`, `txId`, `forceComplete`, `txOptions`?): `Promise`\<[`TransactionLikeWithNonce`](../../types/type-aliases/TransactionLikeWithNonce.md)\> -Defined in: [escrow.ts:940](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L940) +Defined in: [escrow.ts:947](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L947) Creates a prepared transaction for bulk payout without immediately sending it. @@ -477,7 +477,7 @@ console.log('Tx hash:', ethers.keccak256(signedTransaction)); > **createEscrow**(`tokenAddress`, `trustedHandlers`, `jobRequesterId`, `txOptions`?): `Promise`\<`string`\> -Defined in: [escrow.ts:223](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L223) +Defined in: [escrow.ts:230](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L230) This function creates an escrow contract that uses the token passed to pay oracle fees and reward workers. @@ -540,7 +540,7 @@ const escrowAddress = await escrowClient.createEscrow(tokenAddress, trustedHandl > **fund**(`escrowAddress`, `amount`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:414](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L414) +Defined in: [escrow.ts:421](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L421) This function adds funds of the chosen token to the escrow. @@ -593,7 +593,7 @@ await escrowClient.fund('0x62dD51230A30401C455c8398d06F85e4EaB6309f', amount); > **getBalance**(`escrowAddress`): `Promise`\<`bigint`\> -Defined in: [escrow.ts:1085](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1085) +Defined in: [escrow.ts:1092](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1092) This function returns the balance for a specified escrow address. @@ -631,7 +631,7 @@ const balance = await escrowClient.getBalance('0x62dD51230A30401C455c8398d06F85e > **getExchangeOracleAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1471](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1471) +Defined in: [escrow.ts:1478](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1478) This function returns the exchange oracle address for a given escrow. @@ -669,7 +669,7 @@ const oracleAddress = await escrowClient.getExchangeOracleAddress('0x62dD51230A3 > **getFactoryAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1509](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1509) +Defined in: [escrow.ts:1516](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1516) This function returns the escrow factory address for a given escrow. @@ -707,7 +707,7 @@ const factoryAddress = await escrowClient.getFactoryAddress('0x62dD51230A30401C4 > **getIntermediateResultsUrl**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1243](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1243) +Defined in: [escrow.ts:1250](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1250) This function returns the intermediate results file URL. @@ -745,7 +745,7 @@ const intermediateResultsUrl = await escrowClient.getIntermediateResultsUrl('0x6 > **getJobLauncherAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1395](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1395) +Defined in: [escrow.ts:1402](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1402) This function returns the job launcher address for a given escrow. @@ -783,7 +783,7 @@ const jobLauncherAddress = await escrowClient.getJobLauncherAddress('0x62dD51230 > **getManifestHash**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1129](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1129) +Defined in: [escrow.ts:1136](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1136) This function returns the manifest file hash. @@ -821,7 +821,7 @@ const manifestHash = await escrowClient.getManifestHash('0x62dD51230A30401C455c8 > **getManifestUrl**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1167](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1167) +Defined in: [escrow.ts:1174](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1174) This function returns the manifest file URL. @@ -859,7 +859,7 @@ const manifestUrl = await escrowClient.getManifestUrl('0x62dD51230A30401C455c839 > **getRecordingOracleAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1357](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1357) +Defined in: [escrow.ts:1364](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1364) This function returns the recording oracle address for a given escrow. @@ -897,7 +897,7 @@ const oracleAddress = await escrowClient.getRecordingOracleAddress('0x62dD51230A > **getReputationOracleAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1433](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1433) +Defined in: [escrow.ts:1440](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1440) This function returns the reputation oracle address for a given escrow. @@ -935,7 +935,7 @@ const oracleAddress = await escrowClient.getReputationOracleAddress('0x62dD51230 > **getResultsUrl**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1205](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1205) +Defined in: [escrow.ts:1212](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1212) This function returns the results file URL. @@ -973,7 +973,7 @@ const resultsUrl = await escrowClient.getResultsUrl('0x62dD51230A30401C455c8398d > **getStatus**(`escrowAddress`): `Promise`\<[`EscrowStatus`](../../types/enumerations/EscrowStatus.md)\> -Defined in: [escrow.ts:1319](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1319) +Defined in: [escrow.ts:1326](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1326) This function returns the current status of the escrow. @@ -1011,7 +1011,7 @@ const status = await escrowClient.getStatus('0x62dD51230A30401C455c8398d06F85e4E > **getTokenAddress**(`escrowAddress`): `Promise`\<`string`\> -Defined in: [escrow.ts:1281](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1281) +Defined in: [escrow.ts:1288](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1288) This function returns the token address used for funding the escrow. @@ -1049,7 +1049,7 @@ const tokenAddress = await escrowClient.getTokenAddress('0x62dD51230A30401C455c8 > **setup**(`escrowAddress`, `escrowConfig`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:304](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L304) +Defined in: [escrow.ts:311](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L311) This function sets up the parameters of the escrow. @@ -1114,7 +1114,7 @@ await escrowClient.setup(escrowAddress, escrowConfig); > **storeResults**(`escrowAddress`, `url`, `hash`, `txOptions`?): `Promise`\<`void`\> -Defined in: [escrow.ts:479](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L479) +Defined in: [escrow.ts:486](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L486) This function stores the results URL and hash. @@ -1174,7 +1174,7 @@ await escrowClient.storeResults('0x62dD51230A30401C455c8398d06F85e4EaB6309f', 'h > **withdraw**(`escrowAddress`, `tokenAddress`, `txOptions`?): `Promise`\<[`EscrowWithdraw`](../../types/type-aliases/EscrowWithdraw.md)\> -Defined in: [escrow.ts:837](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L837) +Defined in: [escrow.ts:844](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L844) This function withdraws additional tokens in the escrow to the canceler. @@ -1231,7 +1231,7 @@ await escrowClient.withdraw( > `static` **build**(`runner`): `Promise`\<[`EscrowClient`](EscrowClient.md)\> -Defined in: [escrow.ts:161](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L161) +Defined in: [escrow.ts:168](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L168) Creates an instance of EscrowClient from a Runner. diff --git a/docs/sdk/typescript/escrow/classes/EscrowUtils.md b/docs/sdk/typescript/escrow/classes/EscrowUtils.md index 3c559646be..7622cb9120 100644 --- a/docs/sdk/typescript/escrow/classes/EscrowUtils.md +++ b/docs/sdk/typescript/escrow/classes/EscrowUtils.md @@ -6,7 +6,7 @@ # Class: EscrowUtils -Defined in: [escrow.ts:1558](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1558) +Defined in: [escrow.ts:1565](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1565) ## Introduction @@ -54,7 +54,7 @@ const escrowAddresses = new EscrowUtils.getEscrows({ > `static` **getEscrow**(`chainId`, `escrowAddress`): `Promise`\<[`EscrowData`](../../graphql/types/type-aliases/EscrowData.md)\> -Defined in: [escrow.ts:1773](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1773) +Defined in: [escrow.ts:1780](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1780) This function returns the escrow data for a given address. @@ -133,7 +133,7 @@ const escrowData = new EscrowUtils.getEscrow(ChainId.POLYGON_AMOY, "0x1234567890 > `static` **getEscrows**(`filter`): `Promise`\<[`EscrowData`](../../graphql/types/type-aliases/EscrowData.md)[]\> -Defined in: [escrow.ts:1655](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1655) +Defined in: [escrow.ts:1662](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1662) This function returns an array of escrows based on the specified filter parameters. @@ -241,11 +241,55 @@ const escrowDatas = await EscrowUtils.getEscrows(filters); *** +### getPayouts() + +> `static` **getPayouts**(`filter`): `Promise`\<[`Payout`](../../types/type-aliases/Payout.md)[]\> + +Defined in: [escrow.ts:1950](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1950) + +This function returns the payouts for a given set of networks. + +> This uses Subgraph + +**Input parameters** +Fetch payouts from the subgraph. + +#### Parameters + +##### filter + +[`IPayoutFilter`](../../interfaces/interfaces/IPayoutFilter.md) + +Filter parameters. + +#### Returns + +`Promise`\<[`Payout`](../../types/type-aliases/Payout.md)[]\> + +List of payouts matching the filters. + +**Code example** + +```ts +import { ChainId, EscrowUtils } from '@human-protocol/sdk'; + +const payouts = await EscrowUtils.getPayouts({ + chainId: ChainId.POLYGON, + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + from: new Date('2023-01-01'), + to: new Date('2023-12-31') +}); +console.log(payouts); +``` + +*** + ### getStatusEvents() -> `static` **getStatusEvents**(`chainId`, `statuses`?, `from`?, `to`?, `launcher`?, `first`?, `skip`?, `orderDirection`?): `Promise`\<[`StatusEvent`](../../graphql/types/type-aliases/StatusEvent.md)[]\> +> `static` **getStatusEvents**(`filter`): `Promise`\<[`StatusEvent`](../../graphql/types/type-aliases/StatusEvent.md)[]\> -Defined in: [escrow.ts:1860](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1860) +Defined in: [escrow.ts:1859](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts#L1859) This function returns the status events for a given set of networks within an optional date range. @@ -283,53 +327,11 @@ type Status = { #### Parameters -##### chainId - -[`ChainId`](../../enums/enumerations/ChainId.md) - -List of network IDs to query for status events. - -##### statuses? - -[`EscrowStatus`](../../types/enumerations/EscrowStatus.md)[] - -Optional array of statuses to query for. If not provided, queries for all statuses. - -##### from? - -`Date` - -Optional start date to filter events. - -##### to? - -`Date` - -Optional end date to filter events. - -##### launcher? - -`string` - -Optional launcher address to filter events. Must be a valid Ethereum address. - -##### first? - -`number` - -Optional number of transactions per page. Default is 10. - -##### skip? - -`number` - -Optional number of transactions to skip. Default is 0. - -##### orderDirection? +##### filter -[`OrderDirection`](../../enums/enumerations/OrderDirection.md) +[`IStatusEventFilter`](../../interfaces/interfaces/IStatusEventFilter.md) -Optional order of the results. Default is DESC. +Filter parameters. #### Returns @@ -345,12 +347,12 @@ import { ChainId, EscrowUtils, EscrowStatus } from '@human-protocol/sdk'; (async () => { const fromDate = new Date('2023-01-01'); const toDate = new Date('2023-12-31'); - const statusEvents = await EscrowUtils.getStatusEvents( - [ChainId.POLYGON, ChainId.MAINNET], - [EscrowStatus.Pending, EscrowStatus.Complete], - fromDate, - toDate - ); + const statusEvents = await EscrowUtils.getStatusEvents({ + chainId: ChainId.POLYGON, + statuses: [EscrowStatus.Pending, EscrowStatus.Complete], + from: fromDate, + to: toDate + }); console.log(statusEvents); })(); ``` diff --git a/docs/sdk/typescript/graphql/types/type-aliases/DailyEscrowData.md b/docs/sdk/typescript/graphql/types/type-aliases/DailyEscrowData.md index a710721fe7..d1338d66b5 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/DailyEscrowData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/DailyEscrowData.md @@ -8,7 +8,7 @@ > **DailyEscrowData**: `object` -Defined in: [graphql/types.ts:83](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L83) +Defined in: [graphql/types.ts:83](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L83) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/DailyHMTData.md b/docs/sdk/typescript/graphql/types/type-aliases/DailyHMTData.md index 0007ba8dc0..9244bdd1ec 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/DailyHMTData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/DailyHMTData.md @@ -8,7 +8,7 @@ > **DailyHMTData**: `object` -Defined in: [graphql/types.ts:127](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L127) +Defined in: [graphql/types.ts:127](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L127) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/DailyPaymentData.md b/docs/sdk/typescript/graphql/types/type-aliases/DailyPaymentData.md index 25769b81cb..5407a1faf6 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/DailyPaymentData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/DailyPaymentData.md @@ -8,7 +8,7 @@ > **DailyPaymentData**: `object` -Defined in: [graphql/types.ts:106](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L106) +Defined in: [graphql/types.ts:106](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L106) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/DailyTaskData.md b/docs/sdk/typescript/graphql/types/type-aliases/DailyTaskData.md index c71d718fe8..32d8aac1c6 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/DailyTaskData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/DailyTaskData.md @@ -8,7 +8,7 @@ > **DailyTaskData**: `object` -Defined in: [graphql/types.ts:148](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L148) +Defined in: [graphql/types.ts:148](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L148) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/DailyWorkerData.md b/docs/sdk/typescript/graphql/types/type-aliases/DailyWorkerData.md index e39d40f6e4..decaf5e782 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/DailyWorkerData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/DailyWorkerData.md @@ -8,7 +8,7 @@ > **DailyWorkerData**: `object` -Defined in: [graphql/types.ts:97](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L97) +Defined in: [graphql/types.ts:97](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L97) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/EscrowData.md b/docs/sdk/typescript/graphql/types/type-aliases/EscrowData.md index 6af340f156..1b73e121ee 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/EscrowData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/EscrowData.md @@ -8,7 +8,7 @@ > **EscrowData**: `object` -Defined in: [graphql/types.ts:3](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L3) +Defined in: [graphql/types.ts:3](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L3) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatistics.md b/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatistics.md index b7d33856ff..6e6c5426c3 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatistics.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatistics.md @@ -8,7 +8,7 @@ > **EscrowStatistics**: `object` -Defined in: [graphql/types.ts:92](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L92) +Defined in: [graphql/types.ts:92](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L92) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatisticsData.md b/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatisticsData.md index c65ac8ca5a..d11549fd67 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatisticsData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/EscrowStatisticsData.md @@ -8,7 +8,7 @@ > **EscrowStatisticsData**: `object` -Defined in: [graphql/types.ts:42](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L42) +Defined in: [graphql/types.ts:42](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L42) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/EventDayData.md b/docs/sdk/typescript/graphql/types/type-aliases/EventDayData.md index 6a888a4c4f..1bdd057e5f 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/EventDayData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/EventDayData.md @@ -8,7 +8,7 @@ > **EventDayData**: `object` -Defined in: [graphql/types.ts:55](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L55) +Defined in: [graphql/types.ts:55](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L55) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/HMTHolder.md b/docs/sdk/typescript/graphql/types/type-aliases/HMTHolder.md index 7dbf85afbb..3c2354fba4 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/HMTHolder.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/HMTHolder.md @@ -8,7 +8,7 @@ > **HMTHolder**: `object` -Defined in: [graphql/types.ts:122](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L122) +Defined in: [graphql/types.ts:122](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L122) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/HMTHolderData.md b/docs/sdk/typescript/graphql/types/type-aliases/HMTHolderData.md index fbc4efeef7..7a2407ca15 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/HMTHolderData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/HMTHolderData.md @@ -8,7 +8,7 @@ > **HMTHolderData**: `object` -Defined in: [graphql/types.ts:117](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L117) +Defined in: [graphql/types.ts:117](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L117) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/HMTStatistics.md b/docs/sdk/typescript/graphql/types/type-aliases/HMTStatistics.md index cbaf3a162d..e8e3e7364e 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/HMTStatistics.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/HMTStatistics.md @@ -8,7 +8,7 @@ > **HMTStatistics**: `object` -Defined in: [graphql/types.ts:135](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L135) +Defined in: [graphql/types.ts:135](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L135) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/HMTStatisticsData.md b/docs/sdk/typescript/graphql/types/type-aliases/HMTStatisticsData.md index c4b4a2b58c..d1401b9486 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/HMTStatisticsData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/HMTStatisticsData.md @@ -8,7 +8,7 @@ > **HMTStatisticsData**: `object` -Defined in: [graphql/types.ts:33](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L33) +Defined in: [graphql/types.ts:33](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L33) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/IMData.md b/docs/sdk/typescript/graphql/types/type-aliases/IMData.md index a815345c60..9fe78a572b 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/IMData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/IMData.md @@ -8,4 +8,4 @@ > **IMData**: `Record`\<`string`, [`IMDataEntity`](IMDataEntity.md)\> -Defined in: [graphql/types.ts:146](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L146) +Defined in: [graphql/types.ts:146](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L146) diff --git a/docs/sdk/typescript/graphql/types/type-aliases/IMDataEntity.md b/docs/sdk/typescript/graphql/types/type-aliases/IMDataEntity.md index e522d32d83..04813d3002 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/IMDataEntity.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/IMDataEntity.md @@ -8,7 +8,7 @@ > **IMDataEntity**: `object` -Defined in: [graphql/types.ts:141](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L141) +Defined in: [graphql/types.ts:141](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L141) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/KVStoreData.md b/docs/sdk/typescript/graphql/types/type-aliases/KVStoreData.md index 5f33fc202e..bf4a21515f 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/KVStoreData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/KVStoreData.md @@ -8,7 +8,7 @@ > **KVStoreData**: `object` -Defined in: [graphql/types.ts:165](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L165) +Defined in: [graphql/types.ts:165](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L165) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/PaymentStatistics.md b/docs/sdk/typescript/graphql/types/type-aliases/PaymentStatistics.md index 2e3759c6f8..183cb8fc7b 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/PaymentStatistics.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/PaymentStatistics.md @@ -8,7 +8,7 @@ > **PaymentStatistics**: `object` -Defined in: [graphql/types.ts:113](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L113) +Defined in: [graphql/types.ts:113](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L113) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/PayoutData.md b/docs/sdk/typescript/graphql/types/type-aliases/PayoutData.md index bb0362bb47..411724cd7c 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/PayoutData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/PayoutData.md @@ -8,7 +8,7 @@ > **PayoutData**: `object` -Defined in: [graphql/types.ts:25](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L25) +Defined in: [graphql/types.ts:25](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L25) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/RewardAddedEventData.md b/docs/sdk/typescript/graphql/types/type-aliases/RewardAddedEventData.md index e6a086e104..1c754efa3c 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/RewardAddedEventData.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/RewardAddedEventData.md @@ -8,7 +8,7 @@ > **RewardAddedEventData**: `object` -Defined in: [graphql/types.ts:76](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L76) +Defined in: [graphql/types.ts:76](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L76) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/StatusEvent.md b/docs/sdk/typescript/graphql/types/type-aliases/StatusEvent.md index 6eac1851f1..05c2380ae1 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/StatusEvent.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/StatusEvent.md @@ -8,7 +8,7 @@ > **StatusEvent**: `object` -Defined in: [graphql/types.ts:158](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L158) +Defined in: [graphql/types.ts:158](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L158) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/TaskStatistics.md b/docs/sdk/typescript/graphql/types/type-aliases/TaskStatistics.md index dcc05e52cf..1e57acbfdd 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/TaskStatistics.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/TaskStatistics.md @@ -8,7 +8,7 @@ > **TaskStatistics**: `object` -Defined in: [graphql/types.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L154) +Defined in: [graphql/types.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L154) ## Type declaration diff --git a/docs/sdk/typescript/graphql/types/type-aliases/WorkerStatistics.md b/docs/sdk/typescript/graphql/types/type-aliases/WorkerStatistics.md index 60aa3c5894..60ffd457ee 100644 --- a/docs/sdk/typescript/graphql/types/type-aliases/WorkerStatistics.md +++ b/docs/sdk/typescript/graphql/types/type-aliases/WorkerStatistics.md @@ -8,7 +8,7 @@ > **WorkerStatistics**: `object` -Defined in: [graphql/types.ts:102](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L102) +Defined in: [graphql/types.ts:102](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts#L102) ## Type declaration diff --git a/docs/sdk/typescript/interfaces/README.md b/docs/sdk/typescript/interfaces/README.md index 5f4b10c270..efff08ad39 100644 --- a/docs/sdk/typescript/interfaces/README.md +++ b/docs/sdk/typescript/interfaces/README.md @@ -23,6 +23,9 @@ - [IReputationNetworkSubgraph](interfaces/IReputationNetworkSubgraph.md) - [IReward](interfaces/IReward.md) - [IStatisticsFilter](interfaces/IStatisticsFilter.md) +- [IStatusEventFilter](interfaces/IStatusEventFilter.md) - [ITransaction](interfaces/ITransaction.md) - [ITransactionsFilter](interfaces/ITransactionsFilter.md) +- [IWorker](interfaces/IWorker.md) +- [IWorkersFilter](interfaces/IWorkersFilter.md) - [StakerInfo](interfaces/StakerInfo.md) diff --git a/docs/sdk/typescript/interfaces/interfaces/IEscrowConfig.md b/docs/sdk/typescript/interfaces/interfaces/IEscrowConfig.md index fa0a787f88..0683a22619 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IEscrowConfig.md +++ b/docs/sdk/typescript/interfaces/interfaces/IEscrowConfig.md @@ -6,7 +6,7 @@ # Interface: IEscrowConfig -Defined in: [interfaces.ts:79](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L79) +Defined in: [interfaces.ts:79](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L79) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:79](https://github.com/humanprotocol/human-protocol/b > **exchangeOracle**: `string` -Defined in: [interfaces.ts:82](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L82) +Defined in: [interfaces.ts:82](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L82) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:82](https://github.com/humanprotocol/human-protocol/b > **exchangeOracleFee**: `bigint` -Defined in: [interfaces.ts:85](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L85) +Defined in: [interfaces.ts:85](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L85) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:85](https://github.com/humanprotocol/human-protocol/b > **manifestHash**: `string` -Defined in: [interfaces.ts:87](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L87) +Defined in: [interfaces.ts:87](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L87) *** @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:87](https://github.com/humanprotocol/human-protocol/b > **manifestUrl**: `string` -Defined in: [interfaces.ts:86](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L86) +Defined in: [interfaces.ts:86](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L86) *** @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:86](https://github.com/humanprotocol/human-protocol/b > **recordingOracle**: `string` -Defined in: [interfaces.ts:80](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L80) +Defined in: [interfaces.ts:80](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L80) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:80](https://github.com/humanprotocol/human-protocol/b > **recordingOracleFee**: `bigint` -Defined in: [interfaces.ts:83](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L83) +Defined in: [interfaces.ts:83](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L83) *** @@ -62,7 +62,7 @@ Defined in: [interfaces.ts:83](https://github.com/humanprotocol/human-protocol/b > **reputationOracle**: `string` -Defined in: [interfaces.ts:81](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L81) +Defined in: [interfaces.ts:81](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L81) *** @@ -70,4 +70,4 @@ Defined in: [interfaces.ts:81](https://github.com/humanprotocol/human-protocol/b > **reputationOracleFee**: `bigint` -Defined in: [interfaces.ts:84](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L84) +Defined in: [interfaces.ts:84](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L84) diff --git a/docs/sdk/typescript/interfaces/interfaces/IEscrowsFilter.md b/docs/sdk/typescript/interfaces/interfaces/IEscrowsFilter.md index 2d9db02620..add8356780 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IEscrowsFilter.md +++ b/docs/sdk/typescript/interfaces/interfaces/IEscrowsFilter.md @@ -6,7 +6,7 @@ # Interface: IEscrowsFilter -Defined in: [interfaces.ts:67](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L67) +Defined in: [interfaces.ts:67](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L67) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:67](https://github.com/humanprotocol/human-protocol/b > **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) -Defined in: [interfaces.ts:76](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L76) +Defined in: [interfaces.ts:76](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L76) *** @@ -26,7 +26,7 @@ Defined in: [interfaces.ts:76](https://github.com/humanprotocol/human-protocol/b > `optional` **exchangeOracle**: `string` -Defined in: [interfaces.ts:71](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L71) +Defined in: [interfaces.ts:71](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L71) *** @@ -34,7 +34,7 @@ Defined in: [interfaces.ts:71](https://github.com/humanprotocol/human-protocol/b > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) #### Inherited from @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **from**: `Date` -Defined in: [interfaces.ts:74](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L74) +Defined in: [interfaces.ts:74](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L74) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:74](https://github.com/humanprotocol/human-protocol/b > `optional` **jobRequesterId**: `string` -Defined in: [interfaces.ts:72](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L72) +Defined in: [interfaces.ts:72](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L72) *** @@ -62,7 +62,7 @@ Defined in: [interfaces.ts:72](https://github.com/humanprotocol/human-protocol/b > `optional` **launcher**: `string` -Defined in: [interfaces.ts:68](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L68) +Defined in: [interfaces.ts:68](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L68) *** @@ -70,7 +70,7 @@ Defined in: [interfaces.ts:68](https://github.com/humanprotocol/human-protocol/b > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) #### Inherited from @@ -82,7 +82,7 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **recordingOracle**: `string` -Defined in: [interfaces.ts:70](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L70) +Defined in: [interfaces.ts:70](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L70) *** @@ -90,7 +90,7 @@ Defined in: [interfaces.ts:70](https://github.com/humanprotocol/human-protocol/b > `optional` **reputationOracle**: `string` -Defined in: [interfaces.ts:69](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L69) +Defined in: [interfaces.ts:69](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L69) *** @@ -98,7 +98,7 @@ Defined in: [interfaces.ts:69](https://github.com/humanprotocol/human-protocol/b > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) #### Inherited from @@ -110,7 +110,7 @@ Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/ > `optional` **status**: [`EscrowStatus`](../../types/enumerations/EscrowStatus.md) -Defined in: [interfaces.ts:73](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L73) +Defined in: [interfaces.ts:73](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L73) *** @@ -118,4 +118,4 @@ Defined in: [interfaces.ts:73](https://github.com/humanprotocol/human-protocol/b > `optional` **to**: `Date` -Defined in: [interfaces.ts:75](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L75) +Defined in: [interfaces.ts:75](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L75) diff --git a/docs/sdk/typescript/interfaces/interfaces/IHMTHoldersParams.md b/docs/sdk/typescript/interfaces/interfaces/IHMTHoldersParams.md index efa88e282b..20e651e721 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IHMTHoldersParams.md +++ b/docs/sdk/typescript/interfaces/interfaces/IHMTHoldersParams.md @@ -6,7 +6,7 @@ # Interface: IHMTHoldersParams -Defined in: [interfaces.ts:102](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L102) +Defined in: [interfaces.ts:102](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L102) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:102](https://github.com/humanprotocol/human-protocol/ > `optional` **address**: `string` -Defined in: [interfaces.ts:103](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L103) +Defined in: [interfaces.ts:103](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L103) *** @@ -26,7 +26,7 @@ Defined in: [interfaces.ts:103](https://github.com/humanprotocol/human-protocol/ > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) #### Inherited from @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) #### Inherited from @@ -50,7 +50,7 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) #### Inherited from diff --git a/docs/sdk/typescript/interfaces/interfaces/IKVStore.md b/docs/sdk/typescript/interfaces/interfaces/IKVStore.md index 24c492d012..d7afd90199 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IKVStore.md +++ b/docs/sdk/typescript/interfaces/interfaces/IKVStore.md @@ -6,7 +6,7 @@ # Interface: IKVStore -Defined in: [interfaces.ts:113](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L113) +Defined in: [interfaces.ts:114](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L114) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:113](https://github.com/humanprotocol/human-protocol/ > **key**: `string` -Defined in: [interfaces.ts:114](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L114) +Defined in: [interfaces.ts:115](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L115) *** @@ -22,4 +22,4 @@ Defined in: [interfaces.ts:114](https://github.com/humanprotocol/human-protocol/ > **value**: `string` -Defined in: [interfaces.ts:115](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L115) +Defined in: [interfaces.ts:116](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L116) diff --git a/docs/sdk/typescript/interfaces/interfaces/IKeyPair.md b/docs/sdk/typescript/interfaces/interfaces/IKeyPair.md index 175caaaa7d..66e2c69336 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IKeyPair.md +++ b/docs/sdk/typescript/interfaces/interfaces/IKeyPair.md @@ -6,7 +6,7 @@ # Interface: IKeyPair -Defined in: [interfaces.ts:90](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L90) +Defined in: [interfaces.ts:90](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L90) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:90](https://github.com/humanprotocol/human-protocol/b > **passphrase**: `string` -Defined in: [interfaces.ts:93](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L93) +Defined in: [interfaces.ts:93](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L93) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:93](https://github.com/humanprotocol/human-protocol/b > **privateKey**: `string` -Defined in: [interfaces.ts:91](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L91) +Defined in: [interfaces.ts:91](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L91) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:91](https://github.com/humanprotocol/human-protocol/b > **publicKey**: `string` -Defined in: [interfaces.ts:92](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L92) +Defined in: [interfaces.ts:92](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L92) *** @@ -38,4 +38,4 @@ Defined in: [interfaces.ts:92](https://github.com/humanprotocol/human-protocol/b > `optional` **revocationCertificate**: `string` -Defined in: [interfaces.ts:94](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L94) +Defined in: [interfaces.ts:94](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L94) diff --git a/docs/sdk/typescript/interfaces/interfaces/IOperator.md b/docs/sdk/typescript/interfaces/interfaces/IOperator.md index 19de7da259..79945ef14c 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IOperator.md +++ b/docs/sdk/typescript/interfaces/interfaces/IOperator.md @@ -6,7 +6,7 @@ # Interface: IOperator -Defined in: [interfaces.ts:9](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L9) +Defined in: [interfaces.ts:9](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L9) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:9](https://github.com/humanprotocol/human-protocol/bl > **address**: `string` -Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L12) +Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L12) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/b > **amountJobsProcessed**: `bigint` -Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L19) +Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L19) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/b > **amountLocked**: `bigint` -Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L14) +Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L14) *** @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/b > **amountSlashed**: `bigint` -Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L17) +Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L17) *** @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/b > **amountStaked**: `bigint` -Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L13) +Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L13) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/b > **amountWithdrawn**: `bigint` -Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L16) +Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L16) *** @@ -62,7 +62,7 @@ Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/b > `optional` **category**: `string` -Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L31) +Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L31) *** @@ -70,7 +70,7 @@ Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/b > **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) -Defined in: [interfaces.ts:11](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L11) +Defined in: [interfaces.ts:11](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L11) *** @@ -78,7 +78,7 @@ Defined in: [interfaces.ts:11](https://github.com/humanprotocol/human-protocol/b > `optional` **fee**: `bigint` -Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L21) +Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L21) *** @@ -86,7 +86,7 @@ Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/b > **id**: `string` -Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L10) +Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L10) *** @@ -94,7 +94,7 @@ Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/b > `optional` **jobTypes**: `string`[] -Defined in: [interfaces.ts:26](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L26) +Defined in: [interfaces.ts:26](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L26) *** @@ -102,7 +102,7 @@ Defined in: [interfaces.ts:26](https://github.com/humanprotocol/human-protocol/b > **lockedUntilTimestamp**: `bigint` -Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L15) +Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L15) *** @@ -110,7 +110,7 @@ Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/b > `optional` **name**: `string` -Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L30) +Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L30) *** @@ -118,7 +118,7 @@ Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/b > `optional` **publicKey**: `string` -Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L22) +Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L22) *** @@ -126,7 +126,7 @@ Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/b > `optional` **registrationInstructions**: `string` -Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L28) +Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L28) *** @@ -134,7 +134,7 @@ Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/b > `optional` **registrationNeeded**: `boolean` -Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L27) +Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L27) *** @@ -142,7 +142,7 @@ Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/b > `optional` **reputationNetworks**: `string`[] -Defined in: [interfaces.ts:29](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L29) +Defined in: [interfaces.ts:29](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L29) *** @@ -150,7 +150,7 @@ Defined in: [interfaces.ts:29](https://github.com/humanprotocol/human-protocol/b > **reward**: `bigint` -Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L18) +Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L18) *** @@ -158,7 +158,7 @@ Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/b > `optional` **role**: `string` -Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L20) +Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L20) *** @@ -166,7 +166,7 @@ Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/b > `optional` **url**: `string` -Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L25) +Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L25) *** @@ -174,7 +174,7 @@ Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/b > `optional` **webhookUrl**: `string` -Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L23) +Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L23) *** @@ -182,4 +182,4 @@ Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/b > `optional` **website**: `string` -Defined in: [interfaces.ts:24](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L24) +Defined in: [interfaces.ts:24](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L24) diff --git a/docs/sdk/typescript/interfaces/interfaces/IOperatorSubgraph.md b/docs/sdk/typescript/interfaces/interfaces/IOperatorSubgraph.md index 67010ab6c1..b27a089c01 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IOperatorSubgraph.md +++ b/docs/sdk/typescript/interfaces/interfaces/IOperatorSubgraph.md @@ -6,7 +6,7 @@ # Interface: IOperatorSubgraph -Defined in: [interfaces.ts:34](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L34) +Defined in: [interfaces.ts:34](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L34) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:34](https://github.com/humanprotocol/human-protocol/b > **address**: `string` -Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L12) +Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L12) #### Inherited from @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:12](https://github.com/humanprotocol/human-protocol/b > **amountJobsProcessed**: `bigint` -Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L19) +Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L19) #### Inherited from @@ -42,7 +42,7 @@ Defined in: [interfaces.ts:19](https://github.com/humanprotocol/human-protocol/b > **amountLocked**: `bigint` -Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L14) +Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L14) #### Inherited from @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:14](https://github.com/humanprotocol/human-protocol/b > **amountSlashed**: `bigint` -Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L17) +Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L17) #### Inherited from @@ -66,7 +66,7 @@ Defined in: [interfaces.ts:17](https://github.com/humanprotocol/human-protocol/b > **amountStaked**: `bigint` -Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L13) +Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L13) #### Inherited from @@ -78,7 +78,7 @@ Defined in: [interfaces.ts:13](https://github.com/humanprotocol/human-protocol/b > **amountWithdrawn**: `bigint` -Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L16) +Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L16) #### Inherited from @@ -90,7 +90,7 @@ Defined in: [interfaces.ts:16](https://github.com/humanprotocol/human-protocol/b > `optional` **category**: `string` -Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L31) +Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L31) #### Inherited from @@ -102,7 +102,7 @@ Defined in: [interfaces.ts:31](https://github.com/humanprotocol/human-protocol/b > `optional` **fee**: `bigint` -Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L21) +Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L21) #### Inherited from @@ -114,7 +114,7 @@ Defined in: [interfaces.ts:21](https://github.com/humanprotocol/human-protocol/b > **id**: `string` -Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L10) +Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L10) #### Inherited from @@ -126,7 +126,7 @@ Defined in: [interfaces.ts:10](https://github.com/humanprotocol/human-protocol/b > `optional` **jobTypes**: `string` -Defined in: [interfaces.ts:36](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L36) +Defined in: [interfaces.ts:36](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L36) *** @@ -134,7 +134,7 @@ Defined in: [interfaces.ts:36](https://github.com/humanprotocol/human-protocol/b > **lockedUntilTimestamp**: `bigint` -Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L15) +Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L15) #### Inherited from @@ -146,7 +146,7 @@ Defined in: [interfaces.ts:15](https://github.com/humanprotocol/human-protocol/b > `optional` **name**: `string` -Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L30) +Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L30) #### Inherited from @@ -158,7 +158,7 @@ Defined in: [interfaces.ts:30](https://github.com/humanprotocol/human-protocol/b > `optional` **publicKey**: `string` -Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L22) +Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L22) #### Inherited from @@ -170,7 +170,7 @@ Defined in: [interfaces.ts:22](https://github.com/humanprotocol/human-protocol/b > `optional` **registrationInstructions**: `string` -Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L28) +Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L28) #### Inherited from @@ -182,7 +182,7 @@ Defined in: [interfaces.ts:28](https://github.com/humanprotocol/human-protocol/b > `optional` **registrationNeeded**: `boolean` -Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L27) +Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L27) #### Inherited from @@ -194,7 +194,7 @@ Defined in: [interfaces.ts:27](https://github.com/humanprotocol/human-protocol/b > `optional` **reputationNetworks**: `object`[] -Defined in: [interfaces.ts:37](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L37) +Defined in: [interfaces.ts:37](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L37) #### address @@ -206,7 +206,7 @@ Defined in: [interfaces.ts:37](https://github.com/humanprotocol/human-protocol/b > **reward**: `bigint` -Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L18) +Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L18) #### Inherited from @@ -218,7 +218,7 @@ Defined in: [interfaces.ts:18](https://github.com/humanprotocol/human-protocol/b > `optional` **role**: `string` -Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L20) +Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L20) #### Inherited from @@ -230,7 +230,7 @@ Defined in: [interfaces.ts:20](https://github.com/humanprotocol/human-protocol/b > `optional` **url**: `string` -Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L25) +Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L25) #### Inherited from @@ -242,7 +242,7 @@ Defined in: [interfaces.ts:25](https://github.com/humanprotocol/human-protocol/b > `optional` **webhookUrl**: `string` -Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L23) +Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L23) #### Inherited from @@ -254,7 +254,7 @@ Defined in: [interfaces.ts:23](https://github.com/humanprotocol/human-protocol/b > `optional` **website**: `string` -Defined in: [interfaces.ts:24](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L24) +Defined in: [interfaces.ts:24](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L24) #### Inherited from diff --git a/docs/sdk/typescript/interfaces/interfaces/IOperatorsFilter.md b/docs/sdk/typescript/interfaces/interfaces/IOperatorsFilter.md index db1065a56d..90e83d1034 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IOperatorsFilter.md +++ b/docs/sdk/typescript/interfaces/interfaces/IOperatorsFilter.md @@ -6,7 +6,7 @@ # Interface: IOperatorsFilter -Defined in: [interfaces.ts:40](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L40) +Defined in: [interfaces.ts:40](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L40) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:40](https://github.com/humanprotocol/human-protocol/b > **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) -Defined in: [interfaces.ts:41](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L41) +Defined in: [interfaces.ts:41](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L41) *** @@ -26,7 +26,7 @@ Defined in: [interfaces.ts:41](https://github.com/humanprotocol/human-protocol/b > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) #### Inherited from @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **minAmountStaked**: `number` -Defined in: [interfaces.ts:43](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L43) +Defined in: [interfaces.ts:43](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L43) *** @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:43](https://github.com/humanprotocol/human-protocol/b > `optional` **orderBy**: `string` -Defined in: [interfaces.ts:44](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L44) +Defined in: [interfaces.ts:44](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L44) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:44](https://github.com/humanprotocol/human-protocol/b > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) #### Inherited from @@ -66,7 +66,7 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **roles**: `string`[] -Defined in: [interfaces.ts:42](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L42) +Defined in: [interfaces.ts:42](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L42) *** @@ -74,7 +74,7 @@ Defined in: [interfaces.ts:42](https://github.com/humanprotocol/human-protocol/b > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) #### Inherited from diff --git a/docs/sdk/typescript/interfaces/interfaces/IPagination.md b/docs/sdk/typescript/interfaces/interfaces/IPagination.md index 61fc8370bd..18e9db9eb6 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IPagination.md +++ b/docs/sdk/typescript/interfaces/interfaces/IPagination.md @@ -6,7 +6,7 @@ # Interface: IPagination -Defined in: [interfaces.ts:152](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L152) +Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) ## Extended by @@ -14,7 +14,10 @@ Defined in: [interfaces.ts:152](https://github.com/humanprotocol/human-protocol/ - [`IEscrowsFilter`](IEscrowsFilter.md) - [`IStatisticsFilter`](IStatisticsFilter.md) - [`IHMTHoldersParams`](IHMTHoldersParams.md) +- [`IPayoutFilter`](IPayoutFilter.md) - [`ITransactionsFilter`](ITransactionsFilter.md) +- [`IStatusEventFilter`](IStatusEventFilter.md) +- [`IWorkersFilter`](IWorkersFilter.md) ## Properties @@ -22,7 +25,7 @@ Defined in: [interfaces.ts:152](https://github.com/humanprotocol/human-protocol/ > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) *** @@ -30,7 +33,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) *** @@ -38,4 +41,4 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) diff --git a/docs/sdk/typescript/interfaces/interfaces/IPayoutFilter.md b/docs/sdk/typescript/interfaces/interfaces/IPayoutFilter.md index 12595076ad..d324186adb 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IPayoutFilter.md +++ b/docs/sdk/typescript/interfaces/interfaces/IPayoutFilter.md @@ -6,15 +6,39 @@ # Interface: IPayoutFilter -Defined in: [interfaces.ts:106](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L106) +Defined in: [interfaces.ts:106](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L106) + +## Extends + +- [`IPagination`](IPagination.md) ## Properties +### chainId + +> **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) + +Defined in: [interfaces.ts:107](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L107) + +*** + ### escrowAddress? > `optional` **escrowAddress**: `string` -Defined in: [interfaces.ts:107](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L107) +Defined in: [interfaces.ts:108](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L108) + +*** + +### first? + +> `optional` **first**: `number` + +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) + +#### Inherited from + +[`IPagination`](IPagination.md).[`first`](IPagination.md#first) *** @@ -22,7 +46,19 @@ Defined in: [interfaces.ts:107](https://github.com/humanprotocol/human-protocol/ > `optional` **from**: `Date` -Defined in: [interfaces.ts:109](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L109) +Defined in: [interfaces.ts:110](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L110) + +*** + +### orderDirection? + +> `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) + +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) + +#### Inherited from + +[`IPagination`](IPagination.md).[`orderDirection`](IPagination.md#orderdirection) *** @@ -30,7 +66,19 @@ Defined in: [interfaces.ts:109](https://github.com/humanprotocol/human-protocol/ > `optional` **recipient**: `string` -Defined in: [interfaces.ts:108](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L108) +Defined in: [interfaces.ts:109](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L109) + +*** + +### skip? + +> `optional` **skip**: `number` + +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) + +#### Inherited from + +[`IPagination`](IPagination.md).[`skip`](IPagination.md#skip) *** @@ -38,4 +86,4 @@ Defined in: [interfaces.ts:108](https://github.com/humanprotocol/human-protocol/ > `optional` **to**: `Date` -Defined in: [interfaces.ts:110](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L110) +Defined in: [interfaces.ts:111](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L111) diff --git a/docs/sdk/typescript/interfaces/interfaces/IReputationNetwork.md b/docs/sdk/typescript/interfaces/interfaces/IReputationNetwork.md index 09c53c2a0c..a78f347469 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IReputationNetwork.md +++ b/docs/sdk/typescript/interfaces/interfaces/IReputationNetwork.md @@ -6,7 +6,7 @@ # Interface: IReputationNetwork -Defined in: [interfaces.ts:47](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L47) +Defined in: [interfaces.ts:47](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L47) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:47](https://github.com/humanprotocol/human-protocol/b > **address**: `string` -Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L49) +Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L49) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/b > **id**: `string` -Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L48) +Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L48) *** @@ -30,4 +30,4 @@ Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/b > **operators**: [`IOperator`](IOperator.md)[] -Defined in: [interfaces.ts:50](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L50) +Defined in: [interfaces.ts:50](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L50) diff --git a/docs/sdk/typescript/interfaces/interfaces/IReputationNetworkSubgraph.md b/docs/sdk/typescript/interfaces/interfaces/IReputationNetworkSubgraph.md index 755b35d79e..79bb41371a 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IReputationNetworkSubgraph.md +++ b/docs/sdk/typescript/interfaces/interfaces/IReputationNetworkSubgraph.md @@ -6,7 +6,7 @@ # Interface: IReputationNetworkSubgraph -Defined in: [interfaces.ts:53](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L53) +Defined in: [interfaces.ts:53](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L53) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:53](https://github.com/humanprotocol/human-protocol/b > **address**: `string` -Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L49) +Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L49) #### Inherited from @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:49](https://github.com/humanprotocol/human-protocol/b > **id**: `string` -Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L48) +Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L48) #### Inherited from @@ -42,4 +42,4 @@ Defined in: [interfaces.ts:48](https://github.com/humanprotocol/human-protocol/b > **operators**: [`IOperatorSubgraph`](IOperatorSubgraph.md)[] -Defined in: [interfaces.ts:55](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L55) +Defined in: [interfaces.ts:55](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L55) diff --git a/docs/sdk/typescript/interfaces/interfaces/IReward.md b/docs/sdk/typescript/interfaces/interfaces/IReward.md index a8cb309f5c..dbf06c2f1f 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IReward.md +++ b/docs/sdk/typescript/interfaces/interfaces/IReward.md @@ -6,7 +6,7 @@ # Interface: IReward -Defined in: [interfaces.ts:4](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L4) +Defined in: [interfaces.ts:4](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L4) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:4](https://github.com/humanprotocol/human-protocol/bl > **amount**: `bigint` -Defined in: [interfaces.ts:6](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L6) +Defined in: [interfaces.ts:6](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L6) *** @@ -22,4 +22,4 @@ Defined in: [interfaces.ts:6](https://github.com/humanprotocol/human-protocol/bl > **escrowAddress**: `string` -Defined in: [interfaces.ts:5](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L5) +Defined in: [interfaces.ts:5](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L5) diff --git a/docs/sdk/typescript/interfaces/interfaces/IStatisticsFilter.md b/docs/sdk/typescript/interfaces/interfaces/IStatisticsFilter.md index 189b653af1..85c8c655a6 100644 --- a/docs/sdk/typescript/interfaces/interfaces/IStatisticsFilter.md +++ b/docs/sdk/typescript/interfaces/interfaces/IStatisticsFilter.md @@ -6,7 +6,7 @@ # Interface: IStatisticsFilter -Defined in: [interfaces.ts:97](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L97) +Defined in: [interfaces.ts:97](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L97) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:97](https://github.com/humanprotocol/human-protocol/b > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) #### Inherited from @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **from**: `Date` -Defined in: [interfaces.ts:98](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L98) +Defined in: [interfaces.ts:98](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L98) *** @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:98](https://github.com/humanprotocol/human-protocol/b > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) #### Inherited from @@ -50,7 +50,7 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) #### Inherited from @@ -62,4 +62,4 @@ Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/ > `optional` **to**: `Date` -Defined in: [interfaces.ts:99](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L99) +Defined in: [interfaces.ts:99](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L99) diff --git a/docs/sdk/typescript/interfaces/interfaces/IStatusEventFilter.md b/docs/sdk/typescript/interfaces/interfaces/IStatusEventFilter.md new file mode 100644 index 0000000000..d57093ca0d --- /dev/null +++ b/docs/sdk/typescript/interfaces/interfaces/IStatusEventFilter.md @@ -0,0 +1,89 @@ +[**@human-protocol/sdk**](../../README.md) + +*** + +[@human-protocol/sdk](../../modules.md) / [interfaces](../README.md) / IStatusEventFilter + +# Interface: IStatusEventFilter + +Defined in: [interfaces.ts:166](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L166) + +## Extends + +- [`IPagination`](IPagination.md) + +## Properties + +### chainId + +> **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) + +Defined in: [interfaces.ts:167](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L167) + +*** + +### first? + +> `optional` **first**: `number` + +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) + +#### Inherited from + +[`IPagination`](IPagination.md).[`first`](IPagination.md#first) + +*** + +### from? + +> `optional` **from**: `Date` + +Defined in: [interfaces.ts:169](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L169) + +*** + +### launcher? + +> `optional` **launcher**: `string` + +Defined in: [interfaces.ts:171](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L171) + +*** + +### orderDirection? + +> `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) + +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) + +#### Inherited from + +[`IPagination`](IPagination.md).[`orderDirection`](IPagination.md#orderdirection) + +*** + +### skip? + +> `optional` **skip**: `number` + +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) + +#### Inherited from + +[`IPagination`](IPagination.md).[`skip`](IPagination.md#skip) + +*** + +### statuses? + +> `optional` **statuses**: [`EscrowStatus`](../../types/enumerations/EscrowStatus.md)[] + +Defined in: [interfaces.ts:168](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L168) + +*** + +### to? + +> `optional` **to**: `Date` + +Defined in: [interfaces.ts:170](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L170) diff --git a/docs/sdk/typescript/interfaces/interfaces/ITransaction.md b/docs/sdk/typescript/interfaces/interfaces/ITransaction.md index b955575a89..b3408ebfb9 100644 --- a/docs/sdk/typescript/interfaces/interfaces/ITransaction.md +++ b/docs/sdk/typescript/interfaces/interfaces/ITransaction.md @@ -6,7 +6,7 @@ # Interface: ITransaction -Defined in: [interfaces.ts:128](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L128) +Defined in: [interfaces.ts:129](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L129) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:128](https://github.com/humanprotocol/human-protocol/ > **block**: `bigint` -Defined in: [interfaces.ts:129](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L129) +Defined in: [interfaces.ts:130](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L130) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:129](https://github.com/humanprotocol/human-protocol/ > `optional` **escrow**: `string` -Defined in: [interfaces.ts:137](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L137) +Defined in: [interfaces.ts:138](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L138) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:137](https://github.com/humanprotocol/human-protocol/ > **from**: `string` -Defined in: [interfaces.ts:131](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L131) +Defined in: [interfaces.ts:132](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L132) *** @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:131](https://github.com/humanprotocol/human-protocol/ > **internalTransactions**: [`InternalTransaction`](InternalTransaction.md)[] -Defined in: [interfaces.ts:139](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L139) +Defined in: [interfaces.ts:140](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L140) *** @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:139](https://github.com/humanprotocol/human-protocol/ > **method**: `string` -Defined in: [interfaces.ts:135](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L135) +Defined in: [interfaces.ts:136](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L136) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:135](https://github.com/humanprotocol/human-protocol/ > `optional` **receiver**: `string` -Defined in: [interfaces.ts:136](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L136) +Defined in: [interfaces.ts:137](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L137) *** @@ -62,7 +62,7 @@ Defined in: [interfaces.ts:136](https://github.com/humanprotocol/human-protocol/ > **timestamp**: `bigint` -Defined in: [interfaces.ts:133](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L133) +Defined in: [interfaces.ts:134](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L134) *** @@ -70,7 +70,7 @@ Defined in: [interfaces.ts:133](https://github.com/humanprotocol/human-protocol/ > **to**: `string` -Defined in: [interfaces.ts:132](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L132) +Defined in: [interfaces.ts:133](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L133) *** @@ -78,7 +78,7 @@ Defined in: [interfaces.ts:132](https://github.com/humanprotocol/human-protocol/ > `optional` **token**: `string` -Defined in: [interfaces.ts:138](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L138) +Defined in: [interfaces.ts:139](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L139) *** @@ -86,7 +86,7 @@ Defined in: [interfaces.ts:138](https://github.com/humanprotocol/human-protocol/ > **txHash**: `string` -Defined in: [interfaces.ts:130](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L130) +Defined in: [interfaces.ts:131](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L131) *** @@ -94,4 +94,4 @@ Defined in: [interfaces.ts:130](https://github.com/humanprotocol/human-protocol/ > **value**: `string` -Defined in: [interfaces.ts:134](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L134) +Defined in: [interfaces.ts:135](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L135) diff --git a/docs/sdk/typescript/interfaces/interfaces/ITransactionsFilter.md b/docs/sdk/typescript/interfaces/interfaces/ITransactionsFilter.md index bf5baaaef9..3fa146d5b4 100644 --- a/docs/sdk/typescript/interfaces/interfaces/ITransactionsFilter.md +++ b/docs/sdk/typescript/interfaces/interfaces/ITransactionsFilter.md @@ -6,7 +6,7 @@ # Interface: ITransactionsFilter -Defined in: [interfaces.ts:142](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L142) +Defined in: [interfaces.ts:143](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L143) ## Extends @@ -18,7 +18,7 @@ Defined in: [interfaces.ts:142](https://github.com/humanprotocol/human-protocol/ > **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) -Defined in: [interfaces.ts:143](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L143) +Defined in: [interfaces.ts:144](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L144) *** @@ -26,7 +26,7 @@ Defined in: [interfaces.ts:143](https://github.com/humanprotocol/human-protocol/ > `optional` **endBlock**: `number` -Defined in: [interfaces.ts:145](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L145) +Defined in: [interfaces.ts:146](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L146) *** @@ -34,7 +34,7 @@ Defined in: [interfaces.ts:145](https://github.com/humanprotocol/human-protocol/ > `optional` **endDate**: `Date` -Defined in: [interfaces.ts:147](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L147) +Defined in: [interfaces.ts:148](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L148) *** @@ -42,7 +42,7 @@ Defined in: [interfaces.ts:147](https://github.com/humanprotocol/human-protocol/ > `optional` **first**: `number` -Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L153) +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) #### Inherited from @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:153](https://github.com/humanprotocol/human-protocol/ > `optional` **fromAddress**: `string` -Defined in: [interfaces.ts:148](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L148) +Defined in: [interfaces.ts:149](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L149) *** @@ -62,7 +62,7 @@ Defined in: [interfaces.ts:148](https://github.com/humanprotocol/human-protocol/ > `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) -Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) #### Inherited from @@ -74,7 +74,7 @@ Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/ > `optional` **skip**: `number` -Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) #### Inherited from @@ -86,7 +86,7 @@ Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/ > `optional` **startBlock**: `number` -Defined in: [interfaces.ts:144](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L144) +Defined in: [interfaces.ts:145](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L145) *** @@ -94,7 +94,7 @@ Defined in: [interfaces.ts:144](https://github.com/humanprotocol/human-protocol/ > `optional` **startDate**: `Date` -Defined in: [interfaces.ts:146](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L146) +Defined in: [interfaces.ts:147](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L147) *** @@ -102,4 +102,4 @@ Defined in: [interfaces.ts:146](https://github.com/humanprotocol/human-protocol/ > `optional` **toAddress**: `string` -Defined in: [interfaces.ts:149](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L149) +Defined in: [interfaces.ts:150](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L150) diff --git a/docs/sdk/typescript/interfaces/interfaces/IWorker.md b/docs/sdk/typescript/interfaces/interfaces/IWorker.md new file mode 100644 index 0000000000..11a8fa9ba5 --- /dev/null +++ b/docs/sdk/typescript/interfaces/interfaces/IWorker.md @@ -0,0 +1,41 @@ +[**@human-protocol/sdk**](../../README.md) + +*** + +[@human-protocol/sdk](../../modules.md) / [interfaces](../README.md) / IWorker + +# Interface: IWorker + +Defined in: [interfaces.ts:174](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L174) + +## Properties + +### address + +> **address**: `string` + +Defined in: [interfaces.ts:176](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L176) + +*** + +### id + +> **id**: `string` + +Defined in: [interfaces.ts:175](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L175) + +*** + +### payoutCount + +> **payoutCount**: `number` + +Defined in: [interfaces.ts:178](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L178) + +*** + +### totalAmountReceived + +> **totalAmountReceived**: `number` + +Defined in: [interfaces.ts:177](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L177) diff --git a/docs/sdk/typescript/interfaces/interfaces/IWorkersFilter.md b/docs/sdk/typescript/interfaces/interfaces/IWorkersFilter.md new file mode 100644 index 0000000000..14a31aaf66 --- /dev/null +++ b/docs/sdk/typescript/interfaces/interfaces/IWorkersFilter.md @@ -0,0 +1,73 @@ +[**@human-protocol/sdk**](../../README.md) + +*** + +[@human-protocol/sdk](../../modules.md) / [interfaces](../README.md) / IWorkersFilter + +# Interface: IWorkersFilter + +Defined in: [interfaces.ts:181](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L181) + +## Extends + +- [`IPagination`](IPagination.md) + +## Properties + +### address? + +> `optional` **address**: `string` + +Defined in: [interfaces.ts:183](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L183) + +*** + +### chainId + +> **chainId**: [`ChainId`](../../enums/enumerations/ChainId.md) + +Defined in: [interfaces.ts:182](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L182) + +*** + +### first? + +> `optional` **first**: `number` + +Defined in: [interfaces.ts:154](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L154) + +#### Inherited from + +[`IPagination`](IPagination.md).[`first`](IPagination.md#first) + +*** + +### orderBy? + +> `optional` **orderBy**: `string` + +Defined in: [interfaces.ts:184](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L184) + +*** + +### orderDirection? + +> `optional` **orderDirection**: [`OrderDirection`](../../enums/enumerations/OrderDirection.md) + +Defined in: [interfaces.ts:156](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L156) + +#### Inherited from + +[`IPagination`](IPagination.md).[`orderDirection`](IPagination.md#orderdirection) + +*** + +### skip? + +> `optional` **skip**: `number` + +Defined in: [interfaces.ts:155](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L155) + +#### Inherited from + +[`IPagination`](IPagination.md).[`skip`](IPagination.md#skip) diff --git a/docs/sdk/typescript/interfaces/interfaces/InternalTransaction.md b/docs/sdk/typescript/interfaces/interfaces/InternalTransaction.md index 7a1993cdb0..8775833e43 100644 --- a/docs/sdk/typescript/interfaces/interfaces/InternalTransaction.md +++ b/docs/sdk/typescript/interfaces/interfaces/InternalTransaction.md @@ -6,7 +6,7 @@ # Interface: InternalTransaction -Defined in: [interfaces.ts:118](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L118) +Defined in: [interfaces.ts:119](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L119) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:118](https://github.com/humanprotocol/human-protocol/ > `optional` **escrow**: `string` -Defined in: [interfaces.ts:124](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L124) +Defined in: [interfaces.ts:125](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L125) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:124](https://github.com/humanprotocol/human-protocol/ > **from**: `string` -Defined in: [interfaces.ts:119](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L119) +Defined in: [interfaces.ts:120](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L120) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:119](https://github.com/humanprotocol/human-protocol/ > **method**: `string` -Defined in: [interfaces.ts:122](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L122) +Defined in: [interfaces.ts:123](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L123) *** @@ -38,7 +38,7 @@ Defined in: [interfaces.ts:122](https://github.com/humanprotocol/human-protocol/ > `optional` **receiver**: `string` -Defined in: [interfaces.ts:123](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L123) +Defined in: [interfaces.ts:124](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L124) *** @@ -46,7 +46,7 @@ Defined in: [interfaces.ts:123](https://github.com/humanprotocol/human-protocol/ > **to**: `string` -Defined in: [interfaces.ts:120](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L120) +Defined in: [interfaces.ts:121](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L121) *** @@ -54,7 +54,7 @@ Defined in: [interfaces.ts:120](https://github.com/humanprotocol/human-protocol/ > `optional` **token**: `string` -Defined in: [interfaces.ts:125](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L125) +Defined in: [interfaces.ts:126](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L126) *** @@ -62,4 +62,4 @@ Defined in: [interfaces.ts:125](https://github.com/humanprotocol/human-protocol/ > **value**: `string` -Defined in: [interfaces.ts:121](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L121) +Defined in: [interfaces.ts:122](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L122) diff --git a/docs/sdk/typescript/interfaces/interfaces/StakerInfo.md b/docs/sdk/typescript/interfaces/interfaces/StakerInfo.md index 6a07d5191d..e0fd825cf4 100644 --- a/docs/sdk/typescript/interfaces/interfaces/StakerInfo.md +++ b/docs/sdk/typescript/interfaces/interfaces/StakerInfo.md @@ -6,7 +6,7 @@ # Interface: StakerInfo -Defined in: [interfaces.ts:158](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L158) +Defined in: [interfaces.ts:159](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L159) ## Properties @@ -14,7 +14,7 @@ Defined in: [interfaces.ts:158](https://github.com/humanprotocol/human-protocol/ > **lockedAmount**: `bigint` -Defined in: [interfaces.ts:160](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L160) +Defined in: [interfaces.ts:161](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L161) *** @@ -22,7 +22,7 @@ Defined in: [interfaces.ts:160](https://github.com/humanprotocol/human-protocol/ > **lockedUntil**: `bigint` -Defined in: [interfaces.ts:161](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L161) +Defined in: [interfaces.ts:162](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L162) *** @@ -30,7 +30,7 @@ Defined in: [interfaces.ts:161](https://github.com/humanprotocol/human-protocol/ > **stakedAmount**: `bigint` -Defined in: [interfaces.ts:159](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L159) +Defined in: [interfaces.ts:160](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L160) *** @@ -38,4 +38,4 @@ Defined in: [interfaces.ts:159](https://github.com/humanprotocol/human-protocol/ > **withdrawableAmount**: `bigint` -Defined in: [interfaces.ts:162](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L162) +Defined in: [interfaces.ts:163](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts#L163) diff --git a/docs/sdk/typescript/kvstore/classes/KVStoreClient.md b/docs/sdk/typescript/kvstore/classes/KVStoreClient.md index d78e6862be..5e7a7d8eef 100644 --- a/docs/sdk/typescript/kvstore/classes/KVStoreClient.md +++ b/docs/sdk/typescript/kvstore/classes/KVStoreClient.md @@ -6,7 +6,7 @@ # Class: KVStoreClient -Defined in: [kvstore.ts:99](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L99) +Defined in: [kvstore.ts:99](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L99) ## Introduction @@ -86,7 +86,7 @@ const kvstoreClient = await KVStoreClient.build(provider); > **new KVStoreClient**(`runner`, `networkData`): [`KVStoreClient`](KVStoreClient.md) -Defined in: [kvstore.ts:108](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L108) +Defined in: [kvstore.ts:108](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L108) **KVStoreClient constructor** @@ -118,7 +118,7 @@ The network information required to connect to the KVStore contract > **networkData**: [`NetworkData`](../../types/type-aliases/NetworkData.md) -Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) #### Inherited from @@ -130,7 +130,7 @@ Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06 > `protected` **runner**: `ContractRunner` -Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) #### Inherited from @@ -142,7 +142,7 @@ Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06 > **set**(`key`, `value`, `txOptions`?): `Promise`\<`void`\> -Defined in: [kvstore.ts:171](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L171) +Defined in: [kvstore.ts:171](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L171) This function sets a key-value pair associated with the address that submits the transaction. @@ -196,7 +196,7 @@ await kvstoreClient.set('Role', 'RecordingOracle'); > **setBulk**(`keys`, `values`, `txOptions`?): `Promise`\<`void`\> -Defined in: [kvstore.ts:214](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L214) +Defined in: [kvstore.ts:214](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L214) This function sets key-value pairs in bulk associated with the address that submits the transaction. @@ -252,7 +252,7 @@ await kvstoreClient.setBulk(keys, values); > **setFileUrlAndHash**(`url`, `urlKey`, `txOptions`?): `Promise`\<`void`\> -Defined in: [kvstore.ts:257](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L257) +Defined in: [kvstore.ts:257](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L257) Sets a URL value for the address that submits the transaction, and its hash. @@ -305,7 +305,7 @@ await kvstoreClient.setFileUrlAndHash('linkedin.com/example', 'linkedin_url'); > `static` **build**(`runner`): `Promise`\<[`KVStoreClient`](KVStoreClient.md)\> -Defined in: [kvstore.ts:126](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L126) +Defined in: [kvstore.ts:126](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L126) Creates an instance of KVStoreClient from a runner. diff --git a/docs/sdk/typescript/kvstore/classes/KVStoreUtils.md b/docs/sdk/typescript/kvstore/classes/KVStoreUtils.md index 25c6968e0a..b2256eac5b 100644 --- a/docs/sdk/typescript/kvstore/classes/KVStoreUtils.md +++ b/docs/sdk/typescript/kvstore/classes/KVStoreUtils.md @@ -6,7 +6,7 @@ # Class: KVStoreUtils -Defined in: [kvstore.ts:318](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L318) +Defined in: [kvstore.ts:318](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L318) ## Introduction @@ -55,7 +55,7 @@ const KVStoreAddresses = await KVStoreUtils.getKVStoreData( > `static` **get**(`chainId`, `address`, `key`): `Promise`\<`string`\> -Defined in: [kvstore.ts:389](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L389) +Defined in: [kvstore.ts:389](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L389) Gets the value of a key-value pair in the KVStore using the subgraph. @@ -116,7 +116,7 @@ console.log(value); > `static` **getFileUrlAndVerifyHash**(`chainId`, `address`, `urlKey`): `Promise`\<`string`\> -Defined in: [kvstore.ts:436](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L436) +Defined in: [kvstore.ts:436](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L436) Gets the URL value of the given entity, and verifies its hash. @@ -164,7 +164,7 @@ console.log(url); > `static` **getKVStoreData**(`chainId`, `address`): `Promise`\<[`IKVStore`](../../interfaces/interfaces/IKVStore.md)[]\> -Defined in: [kvstore.ts:337](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L337) +Defined in: [kvstore.ts:337](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L337) This function returns the KVStore data for a given address. @@ -211,7 +211,7 @@ console.log(kvStoreData); > `static` **getPublicKey**(`chainId`, `address`): `Promise`\<`string`\> -Defined in: [kvstore.ts:496](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L496) +Defined in: [kvstore.ts:496](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts#L496) Gets the public key of the given entity, and verifies its hash. diff --git a/docs/sdk/typescript/operator/classes/OperatorUtils.md b/docs/sdk/typescript/operator/classes/OperatorUtils.md index 64e02a0563..9e675b0bd4 100644 --- a/docs/sdk/typescript/operator/classes/OperatorUtils.md +++ b/docs/sdk/typescript/operator/classes/OperatorUtils.md @@ -6,7 +6,7 @@ # Class: OperatorUtils -Defined in: [operator.ts:27](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L27) +Defined in: [operator.ts:27](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L27) ## Constructors @@ -24,7 +24,7 @@ Defined in: [operator.ts:27](https://github.com/humanprotocol/human-protocol/blo > `static` **getOperator**(`chainId`, `address`): `Promise`\<[`IOperator`](../../interfaces/interfaces/IOperator.md)\> -Defined in: [operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) +Defined in: [operator.ts:43](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L43) This function returns the operator data for the given address. @@ -62,7 +62,7 @@ const operator = await OperatorUtils.getOperator(ChainId.POLYGON_AMOY, '0x62dD51 > `static` **getOperators**(`filter`): `Promise`\<[`IOperator`](../../interfaces/interfaces/IOperator.md)[]\> -Defined in: [operator.ts:109](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L109) +Defined in: [operator.ts:109](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L109) This function returns all the operator details of the protocol. @@ -97,7 +97,7 @@ const operators = await OperatorUtils.getOperators(filter); > `static` **getReputationNetworkOperators**(`chainId`, `address`, `role`?): `Promise`\<[`IOperator`](../../interfaces/interfaces/IOperator.md)[]\> -Defined in: [operator.ts:190](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L190) +Defined in: [operator.ts:190](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L190) Retrieves the reputation network operators of the specified address. @@ -141,7 +141,7 @@ const operators = await OperatorUtils.getReputationNetworkOperators(ChainId.POLY > `static` **getRewards**(`chainId`, `slasherAddress`): `Promise`\<[`IReward`](../../interfaces/interfaces/IReward.md)[]\> -Defined in: [operator.ts:244](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L244) +Defined in: [operator.ts:244](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/operator.ts#L244) This function returns information about the rewards for a given slasher address. diff --git a/docs/sdk/typescript/staking/classes/StakingClient.md b/docs/sdk/typescript/staking/classes/StakingClient.md index 5065b6941f..bd9b92b82e 100644 --- a/docs/sdk/typescript/staking/classes/StakingClient.md +++ b/docs/sdk/typescript/staking/classes/StakingClient.md @@ -6,7 +6,7 @@ # Class: StakingClient -Defined in: [staking.ts:97](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L97) +Defined in: [staking.ts:97](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L97) ## Introduction @@ -86,7 +86,7 @@ const stakingClient = await StakingClient.build(provider); > **new StakingClient**(`runner`, `networkData`): [`StakingClient`](StakingClient.md) -Defined in: [staking.ts:108](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L108) +Defined in: [staking.ts:108](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L108) **StakingClient constructor** @@ -118,7 +118,7 @@ The network information required to connect to the Staking contract > **escrowFactoryContract**: `EscrowFactory` -Defined in: [staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) +Defined in: [staking.ts:100](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L100) *** @@ -126,7 +126,7 @@ Defined in: [staking.ts:100](https://github.com/humanprotocol/human-protocol/blo > **networkData**: [`NetworkData`](../../types/type-aliases/NetworkData.md) -Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) +Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L12) #### Inherited from @@ -138,7 +138,7 @@ Defined in: [base.ts:12](https://github.com/humanprotocol/human-protocol/blob/06 > `protected` **runner**: `ContractRunner` -Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) +Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/base.ts#L11) #### Inherited from @@ -150,7 +150,7 @@ Defined in: [base.ts:11](https://github.com/humanprotocol/human-protocol/blob/06 > **stakingContract**: `Staking` -Defined in: [staking.ts:99](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L99) +Defined in: [staking.ts:99](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L99) *** @@ -158,7 +158,7 @@ Defined in: [staking.ts:99](https://github.com/humanprotocol/human-protocol/blob > **tokenContract**: `HMToken` -Defined in: [staking.ts:98](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L98) +Defined in: [staking.ts:98](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L98) ## Methods @@ -166,7 +166,7 @@ Defined in: [staking.ts:98](https://github.com/humanprotocol/human-protocol/blob > **approveStake**(`amount`, `txOptions`?): `Promise`\<`void`\> -Defined in: [staking.ts:193](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L193) +Defined in: [staking.ts:193](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L193) This function approves the staking contract to transfer a specified amount of tokens when the user stakes. It increases the allowance for the staking contract. @@ -213,7 +213,7 @@ await stakingClient.approveStake(amount); > **getStakerInfo**(`stakerAddress`): `Promise`\<[`StakerInfo`](../../interfaces/interfaces/StakerInfo.md)\> -Defined in: [staking.ts:435](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L435) +Defined in: [staking.ts:435](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L435) Retrieves comprehensive staking information for a staker. @@ -249,7 +249,7 @@ console.log(stakingInfo.tokensStaked); > **slash**(`slasher`, `staker`, `escrowAddress`, `amount`, `txOptions`?): `Promise`\<`void`\> -Defined in: [staking.ts:373](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L373) +Defined in: [staking.ts:373](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L373) This function reduces the allocated amount by a staker in an escrow and transfers those tokens to the reward pool. This allows the slasher to claim them later. @@ -314,7 +314,7 @@ await stakingClient.slash('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0xf39Fd > **stake**(`amount`, `txOptions`?): `Promise`\<`void`\> -Defined in: [staking.ts:247](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L247) +Defined in: [staking.ts:247](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L247) This function stakes a specified amount of tokens on a specific network. @@ -364,7 +364,7 @@ await stakingClient.stake(amount); > **unstake**(`amount`, `txOptions`?): `Promise`\<`void`\> -Defined in: [staking.ts:291](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L291) +Defined in: [staking.ts:291](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L291) This function unstakes tokens from staking contract. The unstaked tokens stay locked for a period of time. @@ -413,7 +413,7 @@ await stakingClient.unstake(amount); > **withdraw**(`txOptions`?): `Promise`\<`void`\> -Defined in: [staking.ts:336](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L336) +Defined in: [staking.ts:336](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L336) This function withdraws unstaked and non-locked tokens from staking contract to the user wallet. @@ -455,7 +455,7 @@ await stakingClient.withdraw(); > `static` **build**(`runner`): `Promise`\<[`StakingClient`](StakingClient.md)\> -Defined in: [staking.ts:136](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L136) +Defined in: [staking.ts:136](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/staking.ts#L136) Creates an instance of StakingClient from a Runner. diff --git a/docs/sdk/typescript/statistics/classes/StatisticsClient.md b/docs/sdk/typescript/statistics/classes/StatisticsClient.md index 6bd87f6e83..3dd493b5e5 100644 --- a/docs/sdk/typescript/statistics/classes/StatisticsClient.md +++ b/docs/sdk/typescript/statistics/classes/StatisticsClient.md @@ -6,7 +6,7 @@ # Class: StatisticsClient -Defined in: [statistics.ts:58](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L58) +Defined in: [statistics.ts:58](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L58) ## Introduction @@ -45,7 +45,7 @@ const statisticsClient = new StatisticsClient(NETWORKS[ChainId.POLYGON_AMOY]); > **new StatisticsClient**(`networkData`): [`StatisticsClient`](StatisticsClient.md) -Defined in: [statistics.ts:67](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L67) +Defined in: [statistics.ts:67](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L67) **StatisticsClient constructor** @@ -67,7 +67,7 @@ The network information required to connect to the Statistics contract > **networkData**: [`NetworkData`](../../types/type-aliases/NetworkData.md) -Defined in: [statistics.ts:59](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L59) +Defined in: [statistics.ts:59](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L59) *** @@ -75,7 +75,7 @@ Defined in: [statistics.ts:59](https://github.com/humanprotocol/human-protocol/b > **subgraphUrl**: `string` -Defined in: [statistics.ts:60](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L60) +Defined in: [statistics.ts:60](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L60) ## Methods @@ -83,7 +83,7 @@ Defined in: [statistics.ts:60](https://github.com/humanprotocol/human-protocol/b > **getEscrowStatistics**(`filter`): `Promise`\<[`EscrowStatistics`](../../graphql/types/type-aliases/EscrowStatistics.md)\> -Defined in: [statistics.ts:120](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L120) +Defined in: [statistics.ts:120](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L120) This function returns the statistical data of escrows. @@ -149,7 +149,7 @@ const escrowStatisticsApril = await statisticsClient.getEscrowStatistics({ > **getHMTDailyData**(`filter`): `Promise`\<[`DailyHMTData`](../../graphql/types/type-aliases/DailyHMTData.md)[]\> -Defined in: [statistics.ts:478](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L478) +Defined in: [statistics.ts:478](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L478) This function returns the statistical data of HMToken day by day. @@ -214,7 +214,7 @@ console.log('HMT statistics from 5/8 - 6/8:', hmtStatisticsRange); > **getHMTHolders**(`params`): `Promise`\<[`HMTHolder`](../../graphql/types/type-aliases/HMTHolder.md)[]\> -Defined in: [statistics.ts:407](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L407) +Defined in: [statistics.ts:407](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L407) This function returns the holders of the HMToken with optional filters and ordering. @@ -257,7 +257,7 @@ console.log('HMT holders:', hmtHolders.map((h) => ({ > **getHMTStatistics**(): `Promise`\<[`HMTStatistics`](../../graphql/types/type-aliases/HMTStatistics.md)\> -Defined in: [statistics.ts:364](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L364) +Defined in: [statistics.ts:364](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L364) This function returns the statistical data of HMToken. @@ -296,7 +296,7 @@ console.log('HMT statistics:', { > **getPaymentStatistics**(`filter`): `Promise`\<[`PaymentStatistics`](../../graphql/types/type-aliases/PaymentStatistics.md)\> -Defined in: [statistics.ts:300](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L300) +Defined in: [statistics.ts:300](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L300) This function returns the statistical data of payments. @@ -380,7 +380,7 @@ console.log( > **getWorkerStatistics**(`filter`): `Promise`\<[`WorkerStatistics`](../../graphql/types/type-aliases/WorkerStatistics.md)\> -Defined in: [statistics.ts:204](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L204) +Defined in: [statistics.ts:204](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts#L204) This function returns the statistical data of workers. diff --git a/docs/sdk/typescript/storage/classes/StorageClient.md b/docs/sdk/typescript/storage/classes/StorageClient.md index 6ce1cde592..6204af8592 100644 --- a/docs/sdk/typescript/storage/classes/StorageClient.md +++ b/docs/sdk/typescript/storage/classes/StorageClient.md @@ -6,7 +6,7 @@ # Class: ~~StorageClient~~ -Defined in: [storage.ts:63](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L63) +Defined in: [storage.ts:63](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L63) ## Deprecated @@ -61,7 +61,7 @@ const storageClient = new StorageClient(params, credentials); > **new StorageClient**(`params`, `credentials`?): [`StorageClient`](StorageClient.md) -Defined in: [storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) +Defined in: [storage.ts:73](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L73) **Storage client constructor** @@ -89,7 +89,7 @@ Optional. Cloud storage access data. If credentials are not provided - use anony > **bucketExists**(`bucket`): `Promise`\<`boolean`\> -Defined in: [storage.ts:262](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L262) +Defined in: [storage.ts:262](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L262) This function checks if a bucket exists. @@ -133,7 +133,7 @@ const exists = await storageClient.bucketExists('bucket-name'); > **downloadFiles**(`keys`, `bucket`): `Promise`\<`any`[]\> -Defined in: [storage.ts:112](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L112) +Defined in: [storage.ts:112](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L112) This function downloads files from a bucket. @@ -181,7 +181,7 @@ const files = await storageClient.downloadFiles(keys, 'bucket-name'); > **listObjects**(`bucket`): `Promise`\<`string`[]\> -Defined in: [storage.ts:292](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L292) +Defined in: [storage.ts:292](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L292) This function lists all file names contained in the bucket. @@ -225,7 +225,7 @@ const fileNames = await storageClient.listObjects('bucket-name'); > **uploadFiles**(`files`, `bucket`): `Promise`\<[`UploadFile`](../../types/type-aliases/UploadFile.md)[]\> -Defined in: [storage.ts:198](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L198) +Defined in: [storage.ts:198](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L198) This function uploads files to a bucket. @@ -278,7 +278,7 @@ const uploadedFiles = await storageClient.uploadFiles(files, 'bucket-name'); > `static` **downloadFileFromUrl**(`url`): `Promise`\<`any`\> -Defined in: [storage.ts:146](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L146) +Defined in: [storage.ts:146](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/storage.ts#L146) This function downloads files from a URL. diff --git a/docs/sdk/typescript/transaction/classes/TransactionUtils.md b/docs/sdk/typescript/transaction/classes/TransactionUtils.md index cb8ed148aa..e6031b2f4d 100644 --- a/docs/sdk/typescript/transaction/classes/TransactionUtils.md +++ b/docs/sdk/typescript/transaction/classes/TransactionUtils.md @@ -6,7 +6,7 @@ # Class: TransactionUtils -Defined in: [transaction.ts:18](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L18) +Defined in: [transaction.ts:18](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L18) ## Constructors @@ -24,7 +24,7 @@ Defined in: [transaction.ts:18](https://github.com/humanprotocol/human-protocol/ > `static` **getTransaction**(`chainId`, `hash`): `Promise`\<[`ITransaction`](../../interfaces/interfaces/ITransaction.md)\> -Defined in: [transaction.ts:34](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L34) +Defined in: [transaction.ts:34](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L34) This function returns the transaction data for the given hash. @@ -62,7 +62,7 @@ const transaction = await TransactionUtils.getTransaction(ChainId.POLYGON, '0x62 > `static` **getTransactions**(`filter`): `Promise`\<[`ITransaction`](../../interfaces/interfaces/ITransaction.md)[]\> -Defined in: [transaction.ts:109](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L109) +Defined in: [transaction.ts:109](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts#L109) This function returns all transaction details based on the provided filter. diff --git a/docs/sdk/typescript/types/README.md b/docs/sdk/typescript/types/README.md index 87ec7b13af..19291e4dc2 100644 --- a/docs/sdk/typescript/types/README.md +++ b/docs/sdk/typescript/types/README.md @@ -15,6 +15,7 @@ - [EscrowCancel](type-aliases/EscrowCancel.md) - [EscrowWithdraw](type-aliases/EscrowWithdraw.md) - [NetworkData](type-aliases/NetworkData.md) +- [Payout](type-aliases/Payout.md) - [~~StorageCredentials~~](type-aliases/StorageCredentials.md) - [~~StorageParams~~](type-aliases/StorageParams.md) - [TransactionLikeWithNonce](type-aliases/TransactionLikeWithNonce.md) diff --git a/docs/sdk/typescript/types/enumerations/EscrowStatus.md b/docs/sdk/typescript/types/enumerations/EscrowStatus.md index 5db7ff8d60..d0e0362ce2 100644 --- a/docs/sdk/typescript/types/enumerations/EscrowStatus.md +++ b/docs/sdk/typescript/types/enumerations/EscrowStatus.md @@ -6,7 +6,7 @@ # Enumeration: EscrowStatus -Defined in: [types.ts:8](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L8) +Defined in: [types.ts:8](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L8) Enum for escrow statuses. @@ -16,7 +16,7 @@ Enum for escrow statuses. > **Cancelled**: `5` -Defined in: [types.ts:32](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L32) +Defined in: [types.ts:32](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L32) Escrow is cancelled. @@ -26,7 +26,7 @@ Escrow is cancelled. > **Complete**: `4` -Defined in: [types.ts:28](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L28) +Defined in: [types.ts:28](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L28) Escrow is finished. @@ -36,7 +36,7 @@ Escrow is finished. > **Launched**: `0` -Defined in: [types.ts:12](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L12) +Defined in: [types.ts:12](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L12) Escrow is launched. @@ -46,7 +46,7 @@ Escrow is launched. > **Paid**: `3` -Defined in: [types.ts:24](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L24) +Defined in: [types.ts:24](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L24) Escrow is fully paid. @@ -56,7 +56,7 @@ Escrow is fully paid. > **Partial**: `2` -Defined in: [types.ts:20](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L20) +Defined in: [types.ts:20](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L20) Escrow is partially paid out. @@ -66,6 +66,6 @@ Escrow is partially paid out. > **Pending**: `1` -Defined in: [types.ts:16](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L16) +Defined in: [types.ts:16](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L16) Escrow is funded, and waiting for the results to be submitted. diff --git a/docs/sdk/typescript/types/type-aliases/EscrowCancel.md b/docs/sdk/typescript/types/type-aliases/EscrowCancel.md index cf30dfbaa7..8d4fd28d84 100644 --- a/docs/sdk/typescript/types/type-aliases/EscrowCancel.md +++ b/docs/sdk/typescript/types/type-aliases/EscrowCancel.md @@ -8,7 +8,7 @@ > **EscrowCancel**: `object` -Defined in: [types.ts:145](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L145) +Defined in: [types.ts:145](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L145) Represents the response data for an escrow cancellation. diff --git a/docs/sdk/typescript/types/type-aliases/EscrowWithdraw.md b/docs/sdk/typescript/types/type-aliases/EscrowWithdraw.md index 4cdfe43b66..cc8658ac8c 100644 --- a/docs/sdk/typescript/types/type-aliases/EscrowWithdraw.md +++ b/docs/sdk/typescript/types/type-aliases/EscrowWithdraw.md @@ -8,7 +8,7 @@ > **EscrowWithdraw**: `object` -Defined in: [types.ts:159](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L159) +Defined in: [types.ts:159](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L159) Represents the response data for an escrow withdrawal. diff --git a/docs/sdk/typescript/types/type-aliases/NetworkData.md b/docs/sdk/typescript/types/type-aliases/NetworkData.md index f00440d4e3..159e72044f 100644 --- a/docs/sdk/typescript/types/type-aliases/NetworkData.md +++ b/docs/sdk/typescript/types/type-aliases/NetworkData.md @@ -8,7 +8,7 @@ > **NetworkData**: `object` -Defined in: [types.ts:95](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L95) +Defined in: [types.ts:95](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L95) Network data diff --git a/docs/sdk/typescript/types/type-aliases/Payout.md b/docs/sdk/typescript/types/type-aliases/Payout.md new file mode 100644 index 0000000000..66df79f1a1 --- /dev/null +++ b/docs/sdk/typescript/types/type-aliases/Payout.md @@ -0,0 +1,45 @@ +[**@human-protocol/sdk**](../../README.md) + +*** + +[@human-protocol/sdk](../../modules.md) / [types](../README.md) / Payout + +# Type Alias: Payout + +> **Payout**: `object` + +Defined in: [types.ts:177](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L177) + +Represents a payout from an escrow. + +## Type declaration + +### amount + +> **amount**: `bigint` + +The amount paid to the recipient. + +### createdAt + +> **createdAt**: `number` + +The timestamp when the payout was created (in UNIX format). + +### escrowAddress + +> **escrowAddress**: `string` + +The address of the escrow associated with the payout. + +### id + +> **id**: `string` + +Unique identifier of the payout. + +### recipient + +> **recipient**: `string` + +The address of the recipient who received the payout. diff --git a/docs/sdk/typescript/types/type-aliases/StorageCredentials.md b/docs/sdk/typescript/types/type-aliases/StorageCredentials.md index adea949f55..ad1b794aec 100644 --- a/docs/sdk/typescript/types/type-aliases/StorageCredentials.md +++ b/docs/sdk/typescript/types/type-aliases/StorageCredentials.md @@ -8,7 +8,7 @@ > `readonly` **StorageCredentials**: `object` -Defined in: [types.ts:40](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L40) +Defined in: [types.ts:40](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L40) AWS/GCP cloud storage access data diff --git a/docs/sdk/typescript/types/type-aliases/StorageParams.md b/docs/sdk/typescript/types/type-aliases/StorageParams.md index c237594091..d86df41951 100644 --- a/docs/sdk/typescript/types/type-aliases/StorageParams.md +++ b/docs/sdk/typescript/types/type-aliases/StorageParams.md @@ -8,7 +8,7 @@ > **StorageParams**: `object` -Defined in: [types.ts:54](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L54) +Defined in: [types.ts:54](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L54) ## Type declaration diff --git a/docs/sdk/typescript/types/type-aliases/TransactionLikeWithNonce.md b/docs/sdk/typescript/types/type-aliases/TransactionLikeWithNonce.md index d329c39cb9..ed7f7a1bd4 100644 --- a/docs/sdk/typescript/types/type-aliases/TransactionLikeWithNonce.md +++ b/docs/sdk/typescript/types/type-aliases/TransactionLikeWithNonce.md @@ -8,7 +8,7 @@ > **TransactionLikeWithNonce**: `TransactionLike` & `object` -Defined in: [types.ts:174](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L174) +Defined in: [types.ts:200](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L200) ## Type declaration diff --git a/docs/sdk/typescript/types/type-aliases/UploadFile.md b/docs/sdk/typescript/types/type-aliases/UploadFile.md index 118d2c8051..29759d4af4 100644 --- a/docs/sdk/typescript/types/type-aliases/UploadFile.md +++ b/docs/sdk/typescript/types/type-aliases/UploadFile.md @@ -8,7 +8,7 @@ > `readonly` **UploadFile**: `object` -Defined in: [types.ts:77](https://github.com/humanprotocol/human-protocol/blob/06afdec15d4185a13ccdd98fd231f6651db0e480/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L77) +Defined in: [types.ts:77](https://github.com/humanprotocol/human-protocol/blob/1fed10bebf38e474662f3001345d050ccf6fda2f/packages/sdk/typescript/human-protocol-sdk/src/types.ts#L77) Upload file data diff --git a/packages/apps/dashboard/server/src/modules/details/details.service.ts b/packages/apps/dashboard/server/src/modules/details/details.service.ts index 24e6de83c3..347bc759bf 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.service.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.service.ts @@ -11,6 +11,8 @@ import { OrderDirection, KVStoreUtils, IOperatorsFilter, + StakingClient, + WorkerUtils, } from '@human-protocol/sdk'; import { WalletDto } from './dto/wallet.dto'; @@ -53,6 +55,14 @@ export class DetailsService { }); return escrowDto; } + const network = this.networkConfig.networks.find( + (network) => network.chainId === chainId, + ); + if (!network) throw new BadRequestException('Invalid chainId provided'); + const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const stakingClient = await StakingClient.build(provider); + const stakingData = await stakingClient.getStakerInfo(address); + const operatorData = await OperatorUtils.getOperator(chainId, address); if (operatorData) { const operatorDto: OperatorDto = plainToInstance( @@ -65,6 +75,11 @@ export class DetailsService { operatorDto.chainId = chainId; operatorDto.balance = await this.getHmtBalance(chainId, address); + operatorDto.amountStaked = ethers.formatEther(stakingData.stakedAmount); + operatorDto.amountLocked = ethers.formatEther(stakingData.lockedAmount); + operatorDto.amountWithdrawable = ethers.formatEther( + stakingData.withdrawableAmount, + ); const { reputation } = await this.fetchOperatorReputation( chainId, @@ -75,10 +90,22 @@ export class DetailsService { return operatorDto; } + + const workerData = await WorkerUtils.getWorker(chainId, address); + const walletDto: WalletDto = plainToInstance(WalletDto, { chainId, address, balance: await this.getHmtBalance(chainId, address), + amountStaked: ethers.formatEther(stakingData.stakedAmount), + amountLocked: ethers.formatEther(stakingData.lockedAmount), + amountWithdrawable: ethers.formatEther(stakingData.withdrawableAmount), + reputation: (await this.fetchOperatorReputation(chainId, address)) + .reputation, + totalAmountReceived: ethers.formatEther( + workerData?.totalAmountReceived || 0, + ), + payoutCount: workerData?.payoutCount || 0, }); return walletDto; diff --git a/packages/apps/dashboard/server/src/modules/details/dto/operator.dto.ts b/packages/apps/dashboard/server/src/modules/details/dto/operator.dto.ts index 7285006e02..2a89e84065 100644 --- a/packages/apps/dashboard/server/src/modules/details/dto/operator.dto.ts +++ b/packages/apps/dashboard/server/src/modules/details/dto/operator.dto.ts @@ -38,6 +38,18 @@ export class OperatorDto { @Expose() public amountStaked: string; + @ApiProperty({ example: '0.07007358932392' }) + @Transform(({ value }) => value?.toString()) + @IsString() + @Expose() + public amountLocked: string; + + @ApiProperty({ example: '0.07007358932392' }) + @Transform(({ value }) => value?.toString()) + @IsString() + @Expose() + public amountWithdrawable: string; + @ApiProperty({ example: 'High' }) @Transform(({ value }) => value?.toString()) @IsString() diff --git a/packages/apps/dashboard/server/src/modules/details/dto/wallet.dto.ts b/packages/apps/dashboard/server/src/modules/details/dto/wallet.dto.ts index 5a49dac3f3..2864a9bc56 100644 --- a/packages/apps/dashboard/server/src/modules/details/dto/wallet.dto.ts +++ b/packages/apps/dashboard/server/src/modules/details/dto/wallet.dto.ts @@ -1,6 +1,7 @@ -import { IsEnum, IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; import { ChainId } from '@human-protocol/sdk'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsEnum, IsNumber, IsString } from 'class-validator'; export class WalletDto { @ApiProperty({ example: ChainId.POLYGON_AMOY }) @@ -14,4 +15,28 @@ export class WalletDto { @ApiProperty({ example: '0.07007358932392' }) @IsString() public balance: string; + + @ApiProperty({ example: '0.07007358932392' }) + @Transform(({ value }) => value?.toString()) + @IsString() + public amountLocked: string; + + @ApiProperty({ example: '0.07007358932392' }) + @Transform(({ value }) => value?.toString()) + @IsString() + public amountWithdrawable: string; + + @ApiProperty({ example: 'High' }) + @Transform(({ value }) => value?.toString()) + @IsString() + public reputation: string; + + @ApiProperty({ example: '2414.07007358932392' }) + @Transform(({ value }) => value?.toString()) + @IsString() + public totalAmountReceived?: string; + + @ApiProperty({ example: 1234 }) + @IsNumber() + public payoutCount?: number; } diff --git a/packages/apps/fortune/exchange-oracle/server/jest.config.ts b/packages/apps/fortune/exchange-oracle/server/jest.config.ts index 43fd9c5a46..77c651c752 100644 --- a/packages/apps/fortune/exchange-oracle/server/jest.config.ts +++ b/packages/apps/fortune/exchange-oracle/server/jest.config.ts @@ -1,13 +1,15 @@ +import { createDefaultPreset } from 'ts-jest'; + +const jestTsPreset = createDefaultPreset({}); + module.exports = { + ...jestTsPreset, coverageDirectory: '../coverage', collectCoverageFrom: ['**/*.(t|j)s'], moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', testEnvironment: 'node', testRegex: '.*\\.spec\\.ts$', - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, moduleNameMapper: { '^uuid$': require.resolve('uuid'), }, diff --git a/packages/apps/fortune/recording-oracle/jest.config.js b/packages/apps/fortune/recording-oracle/jest.config.js deleted file mode 100644 index 446be44e6f..0000000000 --- a/packages/apps/fortune/recording-oracle/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - coverageDirectory: "../coverage", - moduleFileExtensions: ["js", "json", "ts"], - rootDir: "src", - testEnvironment: "node", - testRegex: ".spec.ts$", - transform: { - ".+\\.(t|j)s$": "ts-jest", - }, - moduleNameMapper: { - "^@/(.*)$": "/$1", - "^axios$": require.resolve("axios"), - "^uuid$": require.resolve("uuid"), - }, -}; diff --git a/packages/apps/fortune/recording-oracle/jest.config.ts b/packages/apps/fortune/recording-oracle/jest.config.ts new file mode 100644 index 0000000000..5e965bef58 --- /dev/null +++ b/packages/apps/fortune/recording-oracle/jest.config.ts @@ -0,0 +1,17 @@ +import { createDefaultPreset } from 'ts-jest'; + +const jestTsPreset = createDefaultPreset({}); + +module.exports = { + ...jestTsPreset, + coverageDirectory: '../coverage', + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: 'src', + testEnvironment: 'node', + testRegex: '.spec.ts$', + moduleNameMapper: { + '^@/(.*)$': '/$1', + '^axios$': require.resolve('axios'), + '^uuid$': require.resolve('uuid'), + }, +}; diff --git a/packages/apps/human-app/server/src/integrations/reputation-oracle/reputation-oracle.gateway.ts b/packages/apps/human-app/server/src/integrations/reputation-oracle/reputation-oracle.gateway.ts index 2cd64d719c..bc5b586913 100644 --- a/packages/apps/human-app/server/src/integrations/reputation-oracle/reputation-oracle.gateway.ts +++ b/packages/apps/human-app/server/src/integrations/reputation-oracle/reputation-oracle.gateway.ts @@ -80,6 +80,7 @@ import { SigninOperatorCommand, SigninOperatorData, SigninOperatorResponse, + SignupOperatorResponse, } from '../../modules/user-operator/model/operator-signin.model'; import { GetNDACommand, @@ -149,7 +150,9 @@ export class ReputationOracleGateway { return this.handleRequestToReputationOracle(options); } - async sendOperatorSignup(command: SignupOperatorCommand): Promise { + async sendOperatorSignup( + command: SignupOperatorCommand, + ): Promise { const data = this.mapper.map( command, SignupOperatorCommand, @@ -159,8 +162,11 @@ export class ReputationOracleGateway { ReputationOracleEndpoints.OPERATOR_SIGNUP, data, ); - return this.handleRequestToReputationOracle(options); + return this.handleRequestToReputationOracle( + options, + ); } + async sendOperatorSignin( command: SigninOperatorCommand, ): Promise { diff --git a/packages/apps/human-app/server/src/modules/user-operator/model/operator-signin.model.ts b/packages/apps/human-app/server/src/modules/user-operator/model/operator-signin.model.ts index dbd451bde9..4b4ea113e2 100644 --- a/packages/apps/human-app/server/src/modules/user-operator/model/operator-signin.model.ts +++ b/packages/apps/human-app/server/src/modules/user-operator/model/operator-signin.model.ts @@ -27,3 +27,8 @@ export class SigninOperatorResponse { refresh_token: string; access_token: string; } + +export class SignupOperatorResponse { + refresh_token: string; + access_token: string; +} diff --git a/packages/apps/human-app/server/src/modules/user-operator/operator.controller.ts b/packages/apps/human-app/server/src/modules/user-operator/operator.controller.ts index 563b3842ce..6b3f62c246 100644 --- a/packages/apps/human-app/server/src/modules/user-operator/operator.controller.ts +++ b/packages/apps/human-app/server/src/modules/user-operator/operator.controller.ts @@ -21,6 +21,7 @@ import { SigninOperatorCommand, SigninOperatorDto, SigninOperatorResponse, + SignupOperatorResponse, } from './model/operator-signin.model'; import { DisableOperatorCommand, @@ -44,13 +45,13 @@ export class OperatorController { @UsePipes(new ValidationPipe()) async signupOperator( @Body() signupOperatorDto: SignupOperatorDto, - ): Promise { + ): Promise { const signupOperatorCommand = this.mapper.map( signupOperatorDto, SignupOperatorDto, SignupOperatorCommand, ); - await this.service.signupOperator(signupOperatorCommand); + return this.service.signupOperator(signupOperatorCommand); } @ApiTags('User-Operator') diff --git a/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx b/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx index d6f0cd39ad..bbae6dd16e 100644 --- a/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx +++ b/packages/apps/job-launcher/client/src/pages/Job/JobDetail/index.tsx @@ -137,7 +137,7 @@ export default function JobDetail() { /> @@ -180,7 +180,7 @@ export default function JobDetail() { /> diff --git a/packages/apps/job-launcher/server/.env.example b/packages/apps/job-launcher/server/.env.example index 08b4c7fac6..0bc9ee4047 100644 --- a/packages/apps/job-launcher/server/.env.example +++ b/packages/apps/job-launcher/server/.env.example @@ -20,16 +20,9 @@ GAS_PRICE_MULTIPLIER=1 PGP_PRIVATE_KEY= PGP_ENCRYPT= PGP_PASSPHRASE= -FORTUNE_EXCHANGE_ORACLE_ADDRESS= -FORTUNE_RECORDING_ORACLE_ADDRESS= -CVAT_EXCHANGE_ORACLE_ADDRESS= -CVAT_RECORDING_ORACLE_ADDRESS= -AUDINO_EXCHANGE_ORACLE_ADDRESS= -AUDINO_RECORDING_ORACLE_ADDRESS= REPUTATION_ORACLE_ADDRESS= HCAPTCHA_RECORDING_ORACLE_URI= HCAPTCHA_REPUTATION_ORACLE_URI= -HCAPTCHA_ORACLE_ADDRESS= HCAPTCHA_SITE_KEY= RPC_URL_SEPOLIA= RPC_URL_POLYGON= diff --git a/packages/apps/job-launcher/server/ENV.md b/packages/apps/job-launcher/server/ENV.md index 4553d1a990..bc1a7c60ee 100644 --- a/packages/apps/job-launcher/server/ENV.md +++ b/packages/apps/job-launcher/server/ENV.md @@ -180,24 +180,9 @@ REPUTATION_ORACLE_ADDRESS= ### List of reputation oracle addresses, typically comma-separated. Required REPUTATION_ORACLES= -### Address of the Fortune exchange oracle contract. Required -FORTUNE_EXCHANGE_ORACLE_ADDRESS= - -### Address of the Fortune recording oracle contract. Required -FORTUNE_RECORDING_ORACLE_ADDRESS= - -### Address of the CVAT exchange oracle contract. Required -CVAT_EXCHANGE_ORACLE_ADDRESS= - -### Address of the CVAT recording oracle contract. Required -CVAT_RECORDING_ORACLE_ADDRESS= - ### URI for the hCaptcha recording oracle service. Required HCAPTCHA_RECORDING_ORACLE_URI= ### URI for the hCaptcha reputation oracle service. Required HCAPTCHA_REPUTATION_ORACLE_URI= -### Address of the hCaptcha oracle contract. Required -HCAPTCHA_ORACLE_ADDRESS= - diff --git a/packages/apps/job-launcher/server/jest.config.ts b/packages/apps/job-launcher/server/jest.config.ts index 1a43206abc..f522442f04 100644 --- a/packages/apps/job-launcher/server/jest.config.ts +++ b/packages/apps/job-launcher/server/jest.config.ts @@ -1,13 +1,15 @@ +import { createDefaultPreset } from 'ts-jest'; + +const jestTsPreset = createDefaultPreset({}); + module.exports = { + ...jestTsPreset, coverageDirectory: '../coverage', collectCoverageFrom: ['**/*.(t|j)s'], moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', testEnvironment: 'node', testRegex: '.*\\.spec\\.ts$', - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, moduleNameMapper: { '^uuid$': require.resolve('uuid'), '^typeorm$': require.resolve('typeorm'), diff --git a/packages/apps/job-launcher/server/src/common/config/env-schema.ts b/packages/apps/job-launcher/server/src/common/config/env-schema.ts index 32f15f9d83..3da850105b 100644 --- a/packages/apps/job-launcher/server/src/common/config/env-schema.ts +++ b/packages/apps/job-launcher/server/src/common/config/env-schema.ts @@ -30,15 +30,8 @@ export const envValidator = Joi.object({ GAS_PRICE_MULTIPLIER: Joi.number(), REPUTATION_ORACLE_ADDRESS: Joi.string().required(), REPUTATION_ORACLES: Joi.string().required(), - FORTUNE_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), - FORTUNE_RECORDING_ORACLE_ADDRESS: Joi.string().required(), - CVAT_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(), - CVAT_RECORDING_ORACLE_ADDRESS: Joi.string().required(), - AUDINO_EXCHANGE_ORACLE_ADDRESS: Joi.string(), - AUDINO_RECORDING_ORACLE_ADDRESS: Joi.string(), HCAPTCHA_RECORDING_ORACLE_URI: Joi.string().required(), HCAPTCHA_REPUTATION_ORACLE_URI: Joi.string().required(), - HCAPTCHA_ORACLE_ADDRESS: Joi.string().required(), HCAPTCHA_SITE_KEY: Joi.string().required(), HCAPTCHA_SECRET: Joi.string().required(), HCAPTCHA_PROTECTION_URL: Joi.string().description( diff --git a/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts index 3104f2a006..2a6f6ed955 100644 --- a/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts +++ b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts @@ -45,46 +45,6 @@ export class Web3ConfigService { return this.configService.getOrThrow('REPUTATION_ORACLES'); } - /** - * Address of the Fortune exchange oracle contract. - * Required - */ - get fortuneExchangeOracleAddress(): string { - return this.configService.getOrThrow( - 'FORTUNE_EXCHANGE_ORACLE_ADDRESS', - ); - } - - /** - * Address of the Fortune recording oracle contract. - * Required - */ - get fortuneRecordingOracleAddress(): string { - return this.configService.getOrThrow( - 'FORTUNE_RECORDING_ORACLE_ADDRESS', - ); - } - - /** - * Address of the CVAT exchange oracle contract. - * Required - */ - get cvatExchangeOracleAddress(): string { - return this.configService.getOrThrow( - 'CVAT_EXCHANGE_ORACLE_ADDRESS', - ); - } - - /** - * Address of the CVAT recording oracle contract. - * Required - */ - get cvatRecordingOracleAddress(): string { - return this.configService.getOrThrow( - 'CVAT_RECORDING_ORACLE_ADDRESS', - ); - } - /** * URI for the hCaptcha recording oracle service. * Required @@ -104,30 +64,4 @@ export class Web3ConfigService { 'HCAPTCHA_REPUTATION_ORACLE_URI', ); } - - /** - * Address of the hCaptcha oracle contract. - * Required - */ - get hCaptchaOracleAddress(): string { - return this.configService.getOrThrow('HCAPTCHA_ORACLE_ADDRESS'); - } - - /** - * Address of the Audino exchange oracle. - */ - get audinoExchangeOracleAddress(): string { - return this.configService.getOrThrow( - 'AUDINO_EXCHANGE_ORACLE_ADDRESS', - ); - } - - /** - * Address of the Audino recording oracle. - */ - get audinoRecordingOracleAddress(): string { - return this.configService.getOrThrow( - 'AUDINO_RECORDING_ORACLE_ADDRESS', - ); - } } diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index 03989853ef..3518b69d51 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -1,5 +1,5 @@ import { ChainId } from '@human-protocol/sdk'; -import { JobRequestType, JobStatus } from '../enums/job'; +import { CvatJobType, JobStatus } from '../enums/job'; export const SERVICE_NAME = 'Job Launcher'; export const NS = 'hmt'; @@ -26,11 +26,11 @@ export const SENDGRID_API_KEY_DISABLED = 'sendgrid-disabled'; export const HEADER_SIGNATURE_KEY = 'human-signature'; export const CVAT_JOB_TYPES = [ - JobRequestType.IMAGE_POLYGONS, - JobRequestType.IMAGE_BOXES, - JobRequestType.IMAGE_POINTS, - JobRequestType.IMAGE_BOXES_FROM_POINTS, - JobRequestType.IMAGE_SKELETONS_FROM_BOXES, + CvatJobType.IMAGE_POLYGONS, + CvatJobType.IMAGE_BOXES, + CvatJobType.IMAGE_POINTS, + CvatJobType.IMAGE_BOXES_FROM_POINTS, + CvatJobType.IMAGE_SKELETONS_FROM_BOXES, ]; export const CANCEL_JOB_STATUSES = [ diff --git a/packages/apps/job-launcher/server/src/common/constants/tokens.ts b/packages/apps/job-launcher/server/src/common/constants/tokens.ts index 8a3856f7e8..061662d90e 100644 --- a/packages/apps/job-launcher/server/src/common/constants/tokens.ts +++ b/packages/apps/job-launcher/server/src/common/constants/tokens.ts @@ -59,4 +59,10 @@ export const TOKEN_ADDRESSES: { // decimals: 6, // }, }, + [ChainId.BSC_TESTNET]: { + [EscrowFundToken.HMT]: { + address: NETWORKS[ChainId.BSC_TESTNET]!.hmtAddress, + decimals: 18, + }, + }, }; diff --git a/packages/apps/job-launcher/server/src/common/enums/job.ts b/packages/apps/job-launcher/server/src/common/enums/job.ts index 9f3e0bf9ae..10b9138107 100644 --- a/packages/apps/job-launcher/server/src/common/enums/job.ts +++ b/packages/apps/job-launcher/server/src/common/enums/job.ts @@ -27,17 +27,39 @@ export enum JobSortField { CREATED_AT = 'created_at', } -export enum JobRequestType { +export enum FortuneJobType { + FORTUNE = 'fortune', +} + +export enum HCaptchaJobType { + HCAPTCHA = 'hcaptcha', +} + +export enum CvatJobType { IMAGE_POINTS = 'image_points', IMAGE_POLYGONS = 'image_polygons', IMAGE_BOXES = 'image_boxes', IMAGE_BOXES_FROM_POINTS = 'image_boxes_from_points', IMAGE_SKELETONS_FROM_BOXES = 'image_skeletons_from_boxes', - HCAPTCHA = 'hcaptcha', - FORTUNE = 'fortune', +} + +export enum AudinoJobType { AUDIO_TRANSCRIPTION = 'audio_transcription', } +export const JobType = [ + ...Object.values(CvatJobType), + ...Object.values(FortuneJobType), + ...Object.values(HCaptchaJobType), + ...Object.values(AudinoJobType), +]; + +export type JobRequestType = + | CvatJobType + | FortuneJobType + | AudinoJobType + | HCaptchaJobType; + export enum JobCaptchaMode { BATCH = 'batch', } diff --git a/packages/apps/job-launcher/server/src/common/utils/slack.spec.ts b/packages/apps/job-launcher/server/src/common/utils/slack.spec.ts index e7fb29b6e0..08f8946341 100644 --- a/packages/apps/job-launcher/server/src/common/utils/slack.spec.ts +++ b/packages/apps/job-launcher/server/src/common/utils/slack.spec.ts @@ -1,8 +1,7 @@ import axios from 'axios'; import { sendSlackNotification } from './slack'; -export const MOCK_SLACK_ABUSE_NOTIFICATION_WEBHOOK_URL = - 'https://slack.com/webhook'; +const MOCK_SLACK_ABUSE_NOTIFICATION_WEBHOOK_URL = 'https://slack.com/webhook'; jest.mock('axios'); describe('sendSlackNotification', () => { diff --git a/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts b/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts index d43c9de5f3..ad88430caa 100644 --- a/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts +++ b/packages/apps/job-launcher/server/src/common/utils/storage.spec.ts @@ -1,5 +1,5 @@ import { AWSRegions, StorageProviders } from '../enums/storage'; -import { JobRequestType } from '../enums/job'; +import { CvatJobType } from '../enums/job'; import axios from 'axios'; import { StorageDataDto } from '../../modules/job/job.dto'; import { generateBucketUrl, listObjectsInBucket } from './storage'; @@ -15,7 +15,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); + const url = generateBucketUrl(storageData, CvatJobType.IMAGE_POINTS); const objects = ['object1', 'object2']; const response = { status: 200, @@ -44,7 +44,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); + const url = generateBucketUrl(storageData, CvatJobType.IMAGE_POINTS); const objects = Array.from({ length: 4 }, (_, i) => `object${i + 1}`); const response1 = { status: 200, @@ -90,7 +90,7 @@ describe('Storage utils', () => { bucketName: 'my-bucket', path: 'my-folder', }; - const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); + const url = generateBucketUrl(storageData, CvatJobType.IMAGE_POINTS); const response = { status: 200, data: ` @@ -112,7 +112,7 @@ describe('Storage utils', () => { bucketName: 'non-existent-bucket', path: 'my-folder', }; - const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); + const url = generateBucketUrl(storageData, CvatJobType.IMAGE_POINTS); const response = { status: 404, data: 'Bucket not found', @@ -131,7 +131,7 @@ describe('Storage utils', () => { bucketName: 'private-bucket', path: 'my-folder', }; - const url = generateBucketUrl(storageData, JobRequestType.IMAGE_POINTS); + const url = generateBucketUrl(storageData, CvatJobType.IMAGE_POINTS); const response = { status: 403, data: 'Access denied', diff --git a/packages/apps/job-launcher/server/src/common/utils/storage.ts b/packages/apps/job-launcher/server/src/common/utils/storage.ts index 91b4aef4c3..d51cb003d5 100644 --- a/packages/apps/job-launcher/server/src/common/utils/storage.ts +++ b/packages/apps/job-launcher/server/src/common/utils/storage.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { StorageDataDto } from '../../modules/job/job.dto'; import { AWSRegions, StorageProviders } from '../enums/storage'; import { ErrorBucket } from '../constants/errors'; -import { JobRequestType } from '../enums/job'; +import { AudinoJobType, CvatJobType, JobRequestType } from '../enums/job'; import axios from 'axios'; import { parseString } from 'xml2js'; import { ControlledError } from '../errors/controlled'; @@ -16,14 +16,16 @@ export function generateBucketUrl( jobType: JobRequestType, ): URL { if ( - [ - JobRequestType.IMAGE_POLYGONS, - JobRequestType.IMAGE_BOXES, - JobRequestType.IMAGE_POINTS, - JobRequestType.IMAGE_BOXES_FROM_POINTS, - JobRequestType.IMAGE_SKELETONS_FROM_BOXES, - JobRequestType.AUDIO_TRANSCRIPTION, - ].includes(jobType) && + ( + [ + CvatJobType.IMAGE_POLYGONS, + CvatJobType.IMAGE_BOXES, + CvatJobType.IMAGE_POINTS, + CvatJobType.IMAGE_BOXES_FROM_POINTS, + CvatJobType.IMAGE_SKELETONS_FROM_BOXES, + AudinoJobType.AUDIO_TRANSCRIPTION, + ] as JobRequestType[] + ).includes(jobType) && storageData.provider != StorageProviders.AWS && storageData.provider != StorageProviders.GCS && storageData.provider != StorageProviders.LOCAL diff --git a/packages/apps/job-launcher/server/src/common/validators/token-decimals.ts b/packages/apps/job-launcher/server/src/common/validators/token-decimals.ts new file mode 100644 index 0000000000..44d1340c9f --- /dev/null +++ b/packages/apps/job-launcher/server/src/common/validators/token-decimals.ts @@ -0,0 +1,67 @@ +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import { TOKEN_ADDRESSES } from '../constants/tokens'; +import { ChainId } from '@human-protocol/sdk'; +import { EscrowFundToken } from '../enums/job'; + +@ValidatorConstraint({ async: false }) +export class IsValidTokenDecimalsConstraint + implements ValidatorConstraintInterface +{ + validate(value: number, args: ValidationArguments) { + const [tokenProperty] = args.constraints; + const dto = args.object as Record; + const chainId = dto.chainId as ChainId; + const token = dto[tokenProperty] as EscrowFundToken; + + if (!chainId || !token) { + return false; + } + + const tokenInfo = TOKEN_ADDRESSES[chainId]?.[token]; + + if (!tokenInfo) { + return false; + } + + const maxDecimals = tokenInfo.decimals; + + if (typeof value !== 'number') { + return false; + } + + const [_, decimals] = value.toString().split('.'); + return !decimals || decimals.length <= maxDecimals; + } + + defaultMessage(args: ValidationArguments) { + const [tokenProperty] = args.constraints; + const dto = args.object as Record; + const chainId = dto.chainId as ChainId; + const token = dto[tokenProperty] as EscrowFundToken; + const tokenInfo = TOKEN_ADDRESSES[chainId]?.[token]; + const maxDecimals = tokenInfo?.decimals || 'unknown'; + + return `${args.property} must have at most ${maxDecimals} decimal places for the selected token (${token}) on chainId ${chainId}.`; + } +} + +export function IsValidTokenDecimals( + tokenProperty: string, + validationOptions?: ValidationOptions, +) { + return function (object: object, propertyName: string) { + registerDecorator({ + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [tokenProperty], + validator: IsValidTokenDecimalsConstraint, + }); + }; +} diff --git a/packages/apps/job-launcher/server/src/modules/content-moderation/content-moderation.module.ts b/packages/apps/job-launcher/server/src/modules/content-moderation/content-moderation.module.ts index 9eb83ad901..b7ef50a7af 100644 --- a/packages/apps/job-launcher/server/src/modules/content-moderation/content-moderation.module.ts +++ b/packages/apps/job-launcher/server/src/modules/content-moderation/content-moderation.module.ts @@ -5,9 +5,9 @@ import { JobModule } from '../job/job.module'; import { ContentModerationRequestEntity } from './content-moderation-request.entity'; import { ContentModerationRequestRepository } from './content-moderation-request.repository'; import { GCVContentModerationService } from './gcv-content-moderation.service'; -import { StorageModule } from '../storage/storage.module'; import { JobEntity } from '../job/job.entity'; import { JobRepository } from '../job/job.repository'; +import { ManifestModule } from '../manifest/manifest.module'; @Global() @Module({ @@ -15,7 +15,7 @@ import { JobRepository } from '../job/job.repository'; TypeOrmModule.forFeature([ContentModerationRequestEntity, JobEntity]), ConfigModule, JobModule, - StorageModule, + ManifestModule, ], providers: [ ContentModerationRequestRepository, diff --git a/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.spec.ts b/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.spec.ts index 0206cfdf30..ba784488da 100644 --- a/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.spec.ts @@ -12,12 +12,12 @@ import { JobStatus } from '../../common/enums/job'; import { ControlledError } from '../../common/errors/controlled'; import { JobEntity } from '../job/job.entity'; import { JobRepository } from '../job/job.repository'; -import { StorageService } from '../storage/storage.service'; import { ContentModerationRequestEntity } from './content-moderation-request.entity'; import { ContentModerationRequestRepository } from './content-moderation-request.repository'; import { GCVContentModerationService } from './gcv-content-moderation.service'; import { sendSlackNotification } from '../../common/utils/slack'; import { listObjectsInBucket } from '../../common/utils/storage'; +import { ManifestService } from '../manifest/manifest.service'; jest.mock('@google-cloud/storage'); jest.mock('@google-cloud/vision'); @@ -35,7 +35,7 @@ describe('GCVContentModerationService', () => { let jobRepository: JobRepository; let contentModerationRequestRepository: ContentModerationRequestRepository; let slackConfigService: SlackConfigService; - let storageService: StorageService; + let manifestService: ManifestService; let jobEntity: JobEntity; const mockStorage = { @@ -92,9 +92,9 @@ describe('GCVContentModerationService', () => { }, }, { - provide: StorageService, + provide: ManifestService, useValue: { - downloadJsonLikeData: jest.fn(), + downloadManifest: jest.fn(), }, }, ], @@ -108,7 +108,7 @@ describe('GCVContentModerationService', () => { ContentModerationRequestRepository, ); slackConfigService = module.get(SlackConfigService); - storageService = module.get(StorageService); + manifestService = module.get(ManifestService); jobEntity = { id: faker.number.int(), @@ -167,7 +167,7 @@ describe('GCVContentModerationService', () => { it('should set job to MODERATION_PASSED if data_url is missing or invalid', async () => { jobEntity.status = JobStatus.PAID; - (storageService.downloadJsonLikeData as jest.Mock).mockResolvedValueOnce({ + (manifestService.downloadManifest as jest.Mock).mockResolvedValueOnce({ data: { data_url: null }, }); @@ -178,7 +178,7 @@ describe('GCVContentModerationService', () => { it('should do nothing if no valid files found in GCS', async () => { jobEntity.status = JobStatus.PAID; - (storageService.downloadJsonLikeData as jest.Mock).mockResolvedValueOnce({ + (manifestService.downloadManifest as jest.Mock).mockResolvedValueOnce({ data: { data_url: `gs://${faker.word.sample({ length: { min: 5, max: 10 } })}`, }, @@ -192,7 +192,7 @@ describe('GCVContentModerationService', () => { it('should create new requests in PENDING and set job to UNDER_MODERATION', async () => { jobEntity.status = JobStatus.PAID; - (storageService.downloadJsonLikeData as jest.Mock).mockResolvedValueOnce({ + (manifestService.downloadManifest as jest.Mock).mockResolvedValueOnce({ data: { data_url: `gs://${faker.word.sample({ length: { min: 5, max: 10 } })}`, }, @@ -215,7 +215,7 @@ describe('GCVContentModerationService', () => { it('should throw if an error occurs in creation logic', async () => { jobEntity.status = JobStatus.PAID; - (storageService.downloadJsonLikeData as jest.Mock).mockResolvedValueOnce({ + (manifestService.downloadManifest as jest.Mock).mockResolvedValueOnce({ data: { data_url: `gs://${faker.word.sample({ length: { min: 5, max: 10 } })}`, }, diff --git a/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.ts b/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.ts index f03adf06ba..73b7fbff23 100644 --- a/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.ts +++ b/packages/apps/job-launcher/server/src/modules/content-moderation/gcv-content-moderation.service.ts @@ -25,12 +25,13 @@ import { sendSlackNotification } from '../../common/utils/slack'; import { listObjectsInBucket } from '../../common/utils/storage'; import { JobEntity } from '../job/job.entity'; import { JobRepository } from '../job/job.repository'; -import { StorageService } from '../storage/storage.service'; import { ContentModerationRequestEntity } from './content-moderation-request.entity'; import { ContentModerationRequestRepository } from './content-moderation-request.repository'; import { IContentModeratorService } from './content-moderation.interface'; import { ModerationResultDto } from './content-moderation.dto'; import NodeCache from 'node-cache'; +import { ManifestService } from '../manifest/manifest.service'; +import { CvatManifestDto } from '../manifest/manifest.dto'; @Injectable() export class GCVContentModerationService implements IContentModeratorService { @@ -50,7 +51,7 @@ export class GCVContentModerationService implements IContentModeratorService { private readonly contentModerationRequestRepository: ContentModerationRequestRepository, private readonly visionConfigService: VisionConfigService, private readonly slackConfigService: SlackConfigService, - private readonly storageService: StorageService, + private readonly manifestService: ManifestService, ) { this.visionClient = new ImageAnnotatorClient({ projectId: this.visionConfigService.projectId, @@ -97,9 +98,10 @@ export class GCVContentModerationService implements IContentModeratorService { } try { - const manifest: any = await this.storageService.downloadJsonLikeData( + const manifest = (await this.manifestService.downloadManifest( jobEntity.manifestUrl, - ); + jobEntity.requestType, + )) as CvatManifestDto; const dataUrl = manifest?.data?.data_url; if (!dataUrl || !isGCSBucketUrl(dataUrl)) { diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts index 7f77c883be..3f04992874 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.spec.ts @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { CronJobType } from '../../common/enums/cron-job'; +import { faker } from '@faker-js/faker'; import { createMock } from '@golevelup/ts-jest'; import { ChainId, @@ -27,10 +28,7 @@ import { MOCK_MAX_RETRY_COUNT, MOCK_TRANSACTION_HASH, } from '../../../test/constants'; -import { AuthConfigService } from '../../common/config/auth-config.service'; -import { CvatConfigService } from '../../common/config/cvat-config.service'; import { NetworkConfigService } from '../../common/config/network-config.service'; -import { PGPConfigService } from '../../common/config/pgp-config.service'; import { ServerConfigService } from '../../common/config/server-config.service'; import { SlackConfigService } from '../../common/config/slack-config.service'; import { VisionConfigService } from '../../common/config/vision-config.service'; @@ -39,19 +37,21 @@ import { ErrorContentModeration, ErrorCronJob, } from '../../common/constants/errors'; -import { JobRequestType, JobStatus } from '../../common/enums/job'; +import { CvatJobType, FortuneJobType, JobStatus } from '../../common/enums/job'; import { WebhookStatus } from '../../common/enums/webhook'; import { ControlledError } from '../../common/errors/controlled'; import { ContentModerationRequestRepository } from '../content-moderation/content-moderation-request.repository'; import { GCVContentModerationService } from '../content-moderation/gcv-content-moderation.service'; -import { CvatManifestDto } from '../job/job.dto'; import { JobEntity } from '../job/job.entity'; import { JobRepository } from '../job/job.repository'; import { JobService } from '../job/job.service'; +import { CvatManifestDto } from '../manifest/manifest.dto'; +import { ManifestService } from '../manifest/manifest.service'; import { PaymentRepository } from '../payment/payment.repository'; import { PaymentService } from '../payment/payment.service'; import { QualificationService } from '../qualification/qualification.service'; import { RateService } from '../rate/rate.service'; +import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; import { WebhookEntity } from '../webhook/webhook.entity'; @@ -61,8 +61,6 @@ import { WhitelistService } from '../whitelist/whitelist.service'; import { CronJobEntity } from './cron-job.entity'; import { CronJobRepository } from './cron-job.repository'; import { CronJobService } from './cron-job.service'; -import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; -import { faker } from '@faker-js/faker'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -127,10 +125,7 @@ describe('CronJobService', () => { WebhookService, Encryption, ServerConfigService, - AuthConfigService, Web3ConfigService, - CvatConfigService, - PGPConfigService, NetworkConfigService, { provide: VisionConfigService, @@ -176,6 +171,10 @@ describe('CronJobService', () => { useValue: createMock(), }, { provide: HttpService, useValue: createMock() }, + { + provide: ManifestService, + useValue: createMock(), + }, ], }).compile(); @@ -613,7 +612,7 @@ describe('CronJobService', () => { data_url: MOCK_FILE_URL, }, annotation: { - type: JobRequestType.IMAGE_POINTS, + type: CvatJobType.IMAGE_POINTS, }, }; jest @@ -745,7 +744,7 @@ describe('CronJobService', () => { .mockResolvedValue(MOCK_EXCHANGE_ORACLE_WEBHOOK_URL); const manifestMock = { - requestType: JobRequestType.FORTUNE, + requestType: FortuneJobType.FORTUNE, }; storageService.downloadJsonLikeData = jest .fn() diff --git a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts index b0c6a756ed..2e972b05a2 100644 --- a/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts @@ -457,15 +457,14 @@ export class CronJobService { let eventsBatch; do { - eventsBatch = await EscrowUtils.getStatusEvents( - network.chainId, + eventsBatch = await EscrowUtils.getStatusEvents({ + chainId: network.chainId, statuses, from, - undefined, - this.web3Service.getOperatorAddress(), - 100, + launcher: this.web3Service.getOperatorAddress(), + first: 100, skip, - ); + }); events.push(...eventsBatch); skip += 100; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.controller.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.controller.spec.ts index 936a28c379..3c33b1179f 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.controller.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.controller.spec.ts @@ -12,7 +12,12 @@ import { MutexManagerService } from '../mutex/mutex-manager.service'; import { RequestWithUser } from '../../common/types'; import { JwtAuthGuard } from '../../common/guards'; import { JobCvatDto, JobFortuneDto, JobQuickLaunchDto } from './job.dto'; -import { EscrowFundToken, JobRequestType } from '../../common/enums/job'; +import { + CvatJobType, + EscrowFundToken, + FortuneJobType, + JobRequestType, +} from '../../common/enums/job'; import { MOCK_FILE_HASH, MOCK_FILE_URL, @@ -207,7 +212,7 @@ describe('JobController', () => { ); expect(mockJobService.createJob).toHaveBeenCalledWith( mockRequest.user, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, jobFortuneDto, ); }); @@ -289,7 +294,7 @@ describe('JobController', () => { path: 'path/to/groundtruth', }, userGuide: 'https://example.com/user-guide', - type: JobRequestType.IMAGE_BOXES, + type: CvatJobType.IMAGE_BOXES, paymentCurrency: PaymentCurrency.HMT, paymentAmount: 500, escrowFundToken: EscrowFundToken.HMT, @@ -311,7 +316,7 @@ describe('JobController', () => { ); expect(mockJobService.createJob).toHaveBeenCalledWith( mockRequest.user, - JobRequestType.IMAGE_BOXES, + CvatJobType.IMAGE_BOXES, jobCvatDto, ); }); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts index 78f65ccec6..fb934f4cd5 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts @@ -34,7 +34,7 @@ import { JobAudinoDto, } from './job.dto'; import { JobService } from './job.service'; -import { JobRequestType } from '../../common/enums/job'; +import { FortuneJobType } from '../../common/enums/job'; import { ApiKey } from '../../common/decorators'; import { ChainId } from '@human-protocol/sdk'; import { ControlledError } from '../../common/errors/controlled'; @@ -134,7 +134,7 @@ export class JobController { async () => { return await this.jobService.createJob( req.user, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, data, ); }, diff --git a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts index afdd71d4cd..d46f9db8ae 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts @@ -18,12 +18,10 @@ import { IsDefined, IsNotEmptyObject, ArrayMinSize, - Equals, } from 'class-validator'; import { Type } from 'class-transformer'; import { ChainId } from '@human-protocol/sdk'; import { - JobCaptchaRequestType, JobCaptchaShapeType, EscrowFundToken, JobRequestType, @@ -33,6 +31,9 @@ import { WorkerBrowser, WorkerLanguage, Country, + AudinoJobType, + CvatJobType, + JobType, } from '../../common/enums/job'; import { Transform } from 'class-transformer'; import { AWSRegions, StorageProviders } from '../../common/enums/storage'; @@ -40,6 +41,8 @@ import { PageOptionsDto } from '../../common/pagination/pagination.dto'; import { IsEnumCaseInsensitive } from '../../common/decorators'; import { PaymentCurrency } from '../../common/enums/payment'; import { IsValidToken } from '../../common/validators/tokens'; +import { Label, ManifestDetails } from '../manifest/manifest.dto'; +import { IsValidTokenDecimals } from '../../common/validators/token-decimals'; export class JobDto { @ApiProperty({ enum: ChainId, required: false, name: 'chain_id' }) @@ -83,6 +86,7 @@ export class JobDto { @ApiProperty({ name: 'payment_amount' }) @IsNumber() @IsPositive() + @IsValidTokenDecimals('paymentCurrency') public paymentAmount: number; @ApiProperty({ enum: EscrowFundToken, name: 'escrow_fund_token' }) @@ -94,9 +98,9 @@ export class JobQuickLaunchDto extends JobDto { @ApiProperty({ description: 'Request type', name: 'request_type', - enum: JobRequestType, + enum: JobType, }) - @IsEnumCaseInsensitive(JobRequestType) + @IsEnumCaseInsensitive(JobType) public requestType: JobRequestType; @ApiProperty({ name: 'manifest_url' }) @@ -113,10 +117,12 @@ export class JobQuickLaunchDto extends JobDto { export class JobFortuneDto extends JobDto { @ApiProperty({ name: 'requester_title' }) @IsString() + @IsNotEmpty() public requesterTitle: string; @ApiProperty({ name: 'requester_description' }) @IsString() + @IsNotEmpty() public requesterDescription: string; @ApiProperty({ name: 'submissions_required' }) @@ -136,12 +142,13 @@ export class StorageDataDto { @ApiProperty({ name: 'bucket_name' }) @IsString() + @IsNotEmpty() public bucketName: string; @ApiProperty() @IsOptional() @IsString() - public path: string; + public path?: string; } export class CvatDataDto { @@ -160,25 +167,10 @@ export class CvatDataDto { public boxes?: StorageDataDto; } -export class Label { - @ApiProperty() - @IsString() - public name: string; - - @ApiPropertyOptional() - @IsArray() - @IsOptional() - public nodes?: string[]; - - @ApiPropertyOptional() - @IsArray() - @IsOptional() - public joints?: string[]; -} - export class JobCvatDto extends JobDto { @ApiProperty({ name: 'requester_description' }) @IsString() + @IsNotEmpty() public requesterDescription: string; @ApiProperty() @@ -204,14 +196,15 @@ export class JobCvatDto extends JobDto { @IsUrl() public userGuide: string; - @ApiProperty({ enum: JobRequestType }) - @IsEnumCaseInsensitive(JobRequestType) - public type: JobRequestType; + @ApiProperty({ enum: CvatJobType }) + @IsEnumCaseInsensitive(CvatJobType) + public type: CvatJobType; } class AudinoLabel { @ApiProperty() @IsString() + @IsNotEmpty() public name: string; } @@ -224,6 +217,7 @@ class AudinoDataDto { export class JobAudinoDto extends JobDto { @ApiProperty({ name: 'requester_description' }) @IsString() + @IsNotEmpty() public requesterDescription: string; @ApiProperty() @@ -249,9 +243,9 @@ export class JobAudinoDto extends JobDto { @IsUrl() public userGuide: string; - @ApiProperty({ enum: JobRequestType }) - @IsEnumCaseInsensitive(JobRequestType) - public type: JobRequestType; + @ApiProperty({ enum: AudinoJobType }) + @IsEnumCaseInsensitive(AudinoJobType) + public type: AudinoJobType; @ApiProperty({ name: 'audio_duration' }) @IsNumber() @@ -276,80 +270,7 @@ export class JobIdDto { public id: number; } -export class ManifestDetails { - @ApiProperty({ description: 'Chain ID', name: 'chain_id' }) - @IsNumber() - @Min(1) - public chainId: number; - - @ApiProperty({ description: 'Title (optional)' }) - @IsOptional() - @IsString() - public title?: string; - - @ApiProperty({ description: 'Description' }) - @IsNotEmpty() - @IsString() - public description?: string; - - @ApiProperty({ - description: 'Submissions required', - name: 'submissions_required', - }) - @IsNumber() - public submissionsRequired: number; - - @ApiProperty({ - description: 'Ethereum address of the token', - name: 'token_address', - }) - @IsEthereumAddress() - public tokenAddress: string; - - @ApiProperty({ description: 'Amount of funds', name: 'fund_amount' }) - @IsNumber() - public fundAmount: number; - - @ApiProperty({ - description: 'Ethereum address of the requester', - name: 'requester_address', - }) - @IsEthereumAddress() - public requesterAddress: string; - - @ApiProperty({ description: 'Request type', name: 'request_type' }) - @IsEnumCaseInsensitive(JobRequestType) - public requestType: JobRequestType; - - @ApiProperty({ - description: 'Address of the exchange oracle (optional)', - name: 'exchange_oracle_address', - }) - @IsOptional() - @IsNotEmpty() - @IsString() - public exchangeOracleAddress?: string; - - @ApiProperty({ - description: 'Address of the recording oracle (optional)', - name: 'recording_oracle_address', - }) - @IsOptional() - @IsNotEmpty() - @IsString() - public recordingOracleAddress?: string; - - @ApiProperty({ - description: 'Address of the reputation oracle (optional)', - name: 'reputation_oracle_address', - }) - @IsOptional() - @IsNotEmpty() - @IsString() - public reputationOracleAddress?: string; -} - -export class CommonDetails { +class CommonDetails { @ApiProperty({ description: 'Ethereum address of the escrow', name: 'escrow_address', @@ -419,152 +340,10 @@ export class JobDetailsDto { public manifest: ManifestDetails; } -export class FortuneManifestDto { - @ApiProperty({ name: 'submissions_required' }) - @IsNumber() - @IsPositive() - public submissionsRequired: number; - - @ApiProperty({ name: 'requester_title' }) - @IsString() - public requesterTitle: string; - - @ApiProperty({ name: 'requester_description' }) - @IsString() - public requesterDescription: string; - - @ApiProperty({ name: 'fund_amount' }) - @IsNumber() - @IsPositive() - public fundAmount: number; - - @ApiProperty({ enum: JobRequestType, name: 'request_type' }) - @IsEnumCaseInsensitive(JobRequestType) - public requestType: JobRequestType; - - @IsArray() - @IsOptional() - public qualifications?: string[]; -} - -export class CvatData { - @IsUrl() - public data_url: string; - - @IsUrl() - @IsOptional() - public points_url?: string; - - @IsUrl() - @IsOptional() - public boxes_url?: string; -} - -export class Annotation { - @IsArray() - public labels: Label[]; - - @IsString() - public description: string; - - @IsString() - public user_guide: string; - - @IsEnumCaseInsensitive(JobRequestType) - public type: JobRequestType; - - @IsNumber() - @IsPositive() - public job_size: number; - - @IsArray() - @IsOptional() - public qualifications?: string[]; -} - -export class Validation { - @IsNumber() - @IsPositive() - public min_quality: number; - - @IsNumber() - @IsPositive() - public val_size: number; - - @IsString() - public gt_url: string; -} - -export class CvatManifestDto { - @IsObject() - public data: CvatData; - - @IsObject() - public annotation: Annotation; - - @IsObject() - public validation: Validation; - - @IsString() - public job_bounty: string; -} - -class AudinoData { - @IsUrl() - public data_url: string; -} - -class AudinoAnnotation { - @IsArray() - public labels: Array<{ name: string }>; - - @IsString() - public description: string; - - @IsString() - @IsUrl() - public user_guide: string; - - @Equals(JobRequestType.AUDIO_TRANSCRIPTION) - public type: JobRequestType.AUDIO_TRANSCRIPTION; - - @IsNumber() - @IsPositive() - public segment_duration: number; - - @IsArray() - @IsOptional() - public qualifications?: string[]; -} - -class AudinoValidation { - @IsNumber() - @IsPositive() - public min_quality: number; - - @IsString() - @IsUrl() - public gt_url: string; -} - -export class AudinoManifestDto { - @IsObject() - public data: AudinoData; - - @IsObject() - public annotation: AudinoAnnotation; - - @IsObject() - public validation: AudinoValidation; - - @IsString() - public job_bounty: string; -} - export class FortuneFinalResultDto { @ApiProperty({ name: 'worker_address' }) @IsNotEmpty() - @IsString() + @IsEthereumAddress() public workerAddress: string; @ApiProperty() @@ -666,7 +445,7 @@ export class JobCaptchaAdvancedDto { targetBrowser?: WorkerBrowser; } -export class JobCaptchaAnnotationsDto { +class JobCaptchaAnnotationsDto { @ApiProperty({ enum: JobCaptchaShapeType, name: 'type_of_job', @@ -742,163 +521,9 @@ export class JobCaptchaDto extends JobDto { annotations: JobCaptchaAnnotationsDto; } -export class RestrictedAudience { - @IsObject() - sitekey?: Record[]; - - @IsObject() - lang?: Record[]; - - @IsObject() - browser?: Record[]; - - @IsObject() - country?: Record[]; -} - -class RequesterRestrictedAnswer { - @IsString() - en?: string; - - @IsUrl() - answer_example_uri?: string; -} - -class RequestConfig { - @IsEnumCaseInsensitive(JobCaptchaShapeType) - shape_type?: JobCaptchaShapeType; - - @IsNumber() - @IsPositive() - min_shapes_per_image?: number; - - @IsNumber() - @IsPositive() - max_shapes_per_image?: number; - - @IsNumber() - @IsPositive() - min_points?: number; - - @IsNumber() - @IsPositive() - max_points?: number; - - @IsNumber() - @IsPositive() - minimum_selection_area_per_shape?: number; - - @IsNumber() - @IsPositive() - multiple_choice_max_choices?: number; - - @IsNumber() - @IsPositive() - multiple_choice_min_choices?: number; - - @IsString() - answer_type?: string; - - overlap_threshold?: any; - - @IsNumber() - @IsPositive() - max_length?: number; - - @IsNumber() - @IsPositive() - min_length?: number; -} - -export class HCaptchaManifestDto { - @IsString() - job_mode: string; - - @IsEnumCaseInsensitive(JobCaptchaRequestType) - request_type: JobCaptchaRequestType; - - @IsObject() - @ValidateNested() - request_config: RequestConfig; - - @IsNumber() - requester_accuracy_target: number; - - @IsNumber() - requester_max_repeats: number; - - @IsNumber() - requester_min_repeats: number; - - @IsArray() - @IsUrl({}, { each: true }) - @IsOptional() - requester_question_example?: string[]; - - @IsObject() - requester_question: Record; - - @IsUrl() - taskdata_uri: string; - - @IsNumber() - job_total_tasks: number; - - @IsNumber() - task_bid_price: number; - - @IsUrl() - groundtruth_uri?: string; - - public_results: boolean; - - @IsNumber() - oracle_stake: number; - - @IsString() - repo_uri: string; - - @IsString() - ro_uri: string; - - @IsObject() - @ValidateNested() - restricted_audience: RestrictedAudience; - - @IsObject() - @ValidateNested({ each: true }) - requester_restricted_answer_set: RequesterRestrictedAnswer; - - @IsOptional() - @IsArray() - @ValidateNested({ each: true }) - taskdata?: TaskData[]; - - @IsArray() - @IsOptional() - public qualifications?: string[]; -} - -class DatapointText { - @IsString() - en: string; -} - -class TaskData { - @IsString() - task_key: string; - - @IsOptional() - @IsString() - datapoint_uri?: string; - - @IsString() - datapoint_hash: string; - - @IsObject() - @IsOptional() - datapoint_text?: DatapointText; -} - -export type CreateJob = JobQuickLaunchDto | JobFortuneDto | JobCvatDto; -// | JobCaptchaDto; +export type CreateJob = + | JobQuickLaunchDto + | JobFortuneDto + | JobCvatDto + | JobAudinoDto + | JobCaptchaDto; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.entity.ts b/packages/apps/job-launcher/server/src/modules/job/job.entity.ts index 4207c86d95..df4ff35d7c 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.entity.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.entity.ts @@ -2,7 +2,7 @@ import { Column, Entity, Index, ManyToOne, OneToMany } from 'typeorm'; import { NS } from '../../common/constants'; import { IJob } from '../../common/interfaces'; -import { JobRequestType, JobStatus } from '../../common/enums/job'; +import { JobRequestType, JobStatus, JobType } from '../../common/enums/job'; import { BaseEntity } from '../../database/base.entity'; import { UserEntity } from '../user/user.entity'; import { PaymentEntity } from '../payment/payment.entity'; @@ -46,7 +46,7 @@ export class JobEntity extends BaseEntity implements IJob { @Column({ type: 'enum', - enum: JobRequestType, + enum: JobType, }) public requestType: JobRequestType; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 6dee372cf1..1946bf3a75 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -1,4 +1,4 @@ -import { JobRequestType } from '../../common/enums/job'; +import { CvatJobType, JobRequestType } from '../../common/enums/job'; import { CvatDataDto, JobCaptchaDto, @@ -13,6 +13,7 @@ export interface RequestAction { dto: JobFortuneDto | JobCvatDto | JobCaptchaDto, requestType: JobRequestType, fundAmount: number, + decimals: number, ) => Promise; } @@ -39,8 +40,9 @@ export interface OracleAddresses { } export interface CvatCalculateJobBounty { - requestType: JobRequestType; + requestType: CvatJobType; fundAmount: number; + decimals: number; urls: GenerateUrls; nodesTotal?: number; } diff --git a/packages/apps/job-launcher/server/src/modules/job/job.module.ts b/packages/apps/job-launcher/server/src/modules/job/job.module.ts index ef57461d8d..cdca136425 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.module.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.module.ts @@ -9,9 +9,7 @@ import { HttpModule } from '@nestjs/axios'; import { PaymentModule } from '../payment/payment.module'; import { JobRepository } from './job.repository'; import { Web3Module } from '../web3/web3.module'; -import { EncryptionModule } from '../encryption/encryption.module'; import { StorageModule } from '../storage/storage.module'; -import { AuthModule } from '../auth/auth.module'; import { WebhookEntity } from '../webhook/webhook.entity'; import { WebhookRepository } from '../webhook/webhook.repository'; import { MutexManagerService } from '../mutex/mutex-manager.service'; @@ -19,21 +17,21 @@ import { QualificationModule } from '../qualification/qualification.module'; import { WhitelistModule } from '../whitelist/whitelist.module'; import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.module'; import { RateModule } from '../rate/rate.module'; +import { ManifestModule } from '../manifest/manifest.module'; @Module({ imports: [ TypeOrmModule.forFeature([JobEntity, WebhookEntity]), ConfigModule, HttpModule, - AuthModule, PaymentModule, Web3Module, - EncryptionModule, StorageModule, QualificationModule, WhitelistModule, RoutingProtocolModule, RateModule, + ManifestModule, ], controllers: [JobController], providers: [ diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index a99f024c30..e411c44396 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -1,13 +1,12 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { createMock } from '@golevelup/ts-jest'; -import { Encryption } from '@human-protocol/sdk'; import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { PaymentCurrency } from '../../common/enums/payment'; import { - JobRequestType, JobStatus, EscrowFundToken, + FortuneJobType, } from '../../common/enums/job'; import { MOCK_FILE_HASH, MOCK_FILE_URL } from '../../../test/constants'; import { PaymentService } from '../payment/payment.service'; @@ -21,21 +20,19 @@ import { PaymentRepository } from '../payment/payment.repository'; import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; import { StorageService } from '../storage/storage.service'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { AuthConfigService } from '../../common/config/auth-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { CvatConfigService } from '../../common/config/cvat-config.service'; -import { PGPConfigService } from '../../common/config/pgp-config.service'; import { RateService } from '../rate/rate.service'; import { QualificationService } from '../qualification/qualification.service'; import { WhitelistService } from '../whitelist/whitelist.service'; import { generateRandomEthAddress } from '../../../test/utils/address'; import { mul } from '../../common/utils/decimal'; +import { ManifestService } from '../manifest/manifest.service'; describe('JobService', () => { let jobService: JobService, paymentService: PaymentService, jobRepository: JobRepository, - rateService: RateService; + rateService: RateService, + manifestService: ManifestService; beforeAll(async () => { jest @@ -45,27 +42,10 @@ describe('JobService', () => { const moduleRef = await Test.createTestingModule({ providers: [ JobService, - Encryption, { provide: ServerConfigService, useValue: new ServerConfigService(new ConfigService()), }, - { - provide: AuthConfigService, - useValue: createMock(), - }, - { - provide: Web3ConfigService, - useValue: createMock(), - }, - { - provide: CvatConfigService, - useValue: createMock(), - }, - { - provide: PGPConfigService, - useValue: createMock(), - }, { provide: QualificationService, useValue: createMock(), @@ -99,6 +79,10 @@ describe('JobService', () => { provide: RoutingProtocolService, useValue: createMock(), }, + { + provide: ManifestService, + useValue: createMock(), + }, ], }).compile(); @@ -106,6 +90,7 @@ describe('JobService', () => { paymentService = moduleRef.get(PaymentService); rateService = moduleRef.get(RateService); jobRepository = moduleRef.get(JobRepository); + manifestService = moduleRef.get(ManifestService); }); describe('createJob', () => { @@ -135,7 +120,7 @@ describe('JobService', () => { reputationOracle: generateRandomEthAddress(), }; - jest.spyOn(jobService, 'uploadManifest').mockResolvedValueOnce({ + jest.spyOn(manifestService, 'uploadManifest').mockResolvedValueOnce({ url: MOCK_FILE_URL, hash: MOCK_FILE_HASH, }); @@ -147,7 +132,7 @@ describe('JobService', () => { await jobService.createJob( userMock, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, fortuneJobDto, ); @@ -165,10 +150,10 @@ describe('JobService', () => { userId: userMock.id, manifestUrl: MOCK_FILE_URL, manifestHash: MOCK_FILE_HASH, - requestType: JobRequestType.FORTUNE, + requestType: FortuneJobType.FORTUNE, fee: expect.any(Number), fundAmount: fortuneJobDto.paymentAmount, - status: JobStatus.PAID, + status: JobStatus.MODERATION_PASSED, waitUntil: expect.any(Date), token: fortuneJobDto.escrowFundToken, exchangeOracle: fortuneJobDto.exchangeOracle, @@ -192,7 +177,7 @@ describe('JobService', () => { reputationOracle: generateRandomEthAddress(), }; - jest.spyOn(jobService, 'uploadManifest').mockResolvedValueOnce({ + jest.spyOn(manifestService, 'uploadManifest').mockResolvedValueOnce({ url: MOCK_FILE_URL, hash: MOCK_FILE_HASH, }); @@ -214,7 +199,7 @@ describe('JobService', () => { await jobService.createJob( userMock, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, fortuneJobDto, ); @@ -229,7 +214,7 @@ describe('JobService', () => { userId: userMock.id, manifestUrl: MOCK_FILE_URL, manifestHash: MOCK_FILE_HASH, - requestType: JobRequestType.FORTUNE, + requestType: FortuneJobType.FORTUNE, fee: expect.any(Number), fundAmount: Number( mul( @@ -237,7 +222,7 @@ describe('JobService', () => { usdToTokenRate, ).toFixed(6), ), - status: JobStatus.PAID, + status: JobStatus.MODERATION_PASSED, waitUntil: expect.any(Date), token: fortuneJobDto.escrowFundToken, exchangeOracle: fortuneJobDto.exchangeOracle, diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 3b06d45caa..08ba57620e 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -4,13 +4,11 @@ import { EscrowClient, EscrowStatus, EscrowUtils, - NETWORKS, - StorageParams, - Encryption, KVStoreKeys, KVStoreUtils, + NETWORKS, + StorageParams, } from '@human-protocol/sdk'; -import { v4 as uuidv4 } from 'uuid'; import { HttpStatus, Inject, @@ -18,109 +16,64 @@ import { Logger, ValidationError, } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; import { validate } from 'class-validator'; import { ethers } from 'ethers'; -import { AuthConfigService } from '../../common/config/auth-config.service'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { CvatConfigService } from '../../common/config/cvat-config.service'; -import { PGPConfigService } from '../../common/config/pgp-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { CANCEL_JOB_STATUSES } from '../../common/constants'; import { - ErrorBucket, ErrorEscrow, ErrorJob, ErrorQualification, } from '../../common/constants/errors'; +import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; +import { CronJobType } from '../../common/enums/cron-job'; import { + AudinoJobType, + CvatJobType, + EscrowFundToken, + FortuneJobType, + HCaptchaJobType, JobRequestType, JobStatus, - JobCaptchaMode, - JobCaptchaRequestType, - JobCaptchaShapeType, - EscrowFundToken, } from '../../common/enums/job'; import { FiatCurrency } from '../../common/enums/payment'; +import { EventType, OracleType } from '../../common/enums/webhook'; +import { ControlledError } from '../../common/errors/controlled'; +import { PageDto } from '../../common/pagination/pagination.dto'; import { parseUrl } from '../../common/utils'; -import { add, div, mul, max } from '../../common/utils/decimal'; +import { add, div, max, mul } from '../../common/utils/decimal'; +import { getTokenDecimals } from '../../common/utils/tokens'; +import { CronJobRepository } from '../cron-job/cron-job.repository'; +import { + CvatManifestDto, + FortuneManifestDto, + HCaptchaManifestDto, +} from '../manifest/manifest.dto'; +import { ManifestService } from '../manifest/manifest.service'; import { PaymentService } from '../payment/payment.service'; +import { QualificationService } from '../qualification/qualification.service'; +import { RateService } from '../rate/rate.service'; +import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; +import { StorageService } from '../storage/storage.service'; +import { UserEntity } from '../user/user.entity'; import { Web3Service } from '../web3/web3.service'; +import { WebhookDataDto } from '../webhook/webhook.dto'; +import { WebhookEntity } from '../webhook/webhook.entity'; +import { WebhookRepository } from '../webhook/webhook.repository'; +import { WhitelistService } from '../whitelist/whitelist.service'; import { - CvatManifestDto, + CreateJob, EscrowCancelDto, FortuneFinalResultDto, - FortuneManifestDto, - JobCvatDto, + GetJobsDto, JobDetailsDto, - JobFortuneDto, JobListDto, - HCaptchaManifestDto, - JobCaptchaAdvancedDto, - JobCaptchaDto, - RestrictedAudience, - CreateJob, JobQuickLaunchDto, - CvatDataDto, - StorageDataDto, - GetJobsDto, - JobAudinoDto, - AudinoManifestDto, } from './job.dto'; import { JobEntity } from './job.entity'; +import { EscrowAction } from './job.interface'; import { JobRepository } from './job.repository'; -import { - CANCEL_JOB_STATUSES, - HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - HCAPTCHA_IMMO_MAX_LENGTH, - HCAPTCHA_IMMO_MIN_LENGTH, - HCAPTCHA_LANDMARK_MAX_POINTS, - HCAPTCHA_LANDMARK_MIN_POINTS, - HCAPTCHA_MAX_SHAPES_PER_IMAGE, - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - HCAPTCHA_MIN_SHAPES_PER_IMAGE, - HCAPTCHA_NOT_PRESENTED_LABEL, - HCAPTCHA_ORACLE_STAKE, - HCAPTCHA_POLYGON_MAX_POINTS, - HCAPTCHA_POLYGON_MIN_POINTS, -} from '../../common/constants'; -import { EventType, OracleType } from '../../common/enums/webhook'; -import { - HMToken, - HMToken__factory, -} from '@human-protocol/core/typechain-types'; -import Decimal from 'decimal.js'; -import { StorageService } from '../storage/storage.service'; -import { - generateBucketUrl, - listObjectsInBucket, -} from '../../common/utils/storage'; -import { WebhookDataDto } from '../webhook/webhook.dto'; -import { - ManifestAction, - EscrowAction, - OracleAction, - OracleAddresses, - RequestAction, - CvatCalculateJobBounty, - CvatImageData, - CvatAnnotationData, - GenerateUrls, -} from './job.interface'; -import { WebhookEntity } from '../webhook/webhook.entity'; -import { WebhookRepository } from '../webhook/webhook.repository'; -import { ControlledError } from '../../common/errors/controlled'; -import { RateService } from '../rate/rate.service'; -import { PageDto } from '../../common/pagination/pagination.dto'; -import { CronJobType } from '../../common/enums/cron-job'; -import { CronJobRepository } from '../cron-job/cron-job.repository'; -import { ModuleRef } from '@nestjs/core'; -import { QualificationService } from '../qualification/qualification.service'; -import { WhitelistService } from '../whitelist/whitelist.service'; -import { UserEntity } from '../user/user.entity'; -import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service'; -import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; -import { getTokenDecimals } from '../../common/utils/tokens'; - @Injectable() export class JobService { public readonly logger = new Logger(JobService.name); @@ -134,17 +87,13 @@ export class JobService { private readonly webhookRepository: WebhookRepository, private readonly paymentService: PaymentService, private readonly serverConfigService: ServerConfigService, - private readonly authConfigService: AuthConfigService, - private readonly web3ConfigService: Web3ConfigService, - private readonly cvatConfigService: CvatConfigService, - private readonly pgpConfigService: PGPConfigService, private readonly routingProtocolService: RoutingProtocolService, private readonly storageService: StorageService, private readonly rateService: RateService, private readonly whitelistService: WhitelistService, private moduleRef: ModuleRef, - @Inject(Encryption) private readonly encryption: Encryption, private readonly qualificationService: QualificationService, + private readonly manifestService: ManifestService, ) {} onModuleInit() { @@ -153,691 +102,39 @@ export class JobService { }); } - public async createCvatManifest( - dto: JobCvatDto, - requestType: JobRequestType, - tokenFundAmount: number, - ): Promise { - const { generateUrls } = this.createManifestActions[requestType]; - - const urls = generateUrls(dto.data, dto.groundTruth); - - const jobBounty = await this.calculateJobBounty({ - requestType, - fundAmount: tokenFundAmount, - urls, - nodesTotal: dto.labels[0]?.nodes?.length, - }); - - return { - data: { - data_url: urls.dataUrl.href, - ...(urls.pointsUrl && { - points_url: urls.pointsUrl?.href, - }), - ...(urls.boxesUrl && { - boxes_url: urls.boxesUrl?.href, - }), - }, - annotation: { - labels: dto.labels, - description: dto.requesterDescription, - user_guide: dto.userGuide, - type: requestType, - job_size: this.cvatConfigService.jobSize, - ...(dto.qualifications && { - qualifications: dto.qualifications, - }), - }, - validation: { - min_quality: dto.minQuality, - val_size: this.cvatConfigService.valSize, - gt_url: urls.gtUrl.href, - }, - job_bounty: jobBounty, - }; - } - - public async createHCaptchaManifest( - jobDto: JobCaptchaDto, - ): Promise { - const jobType = jobDto.annotations.typeOfJob; - const dataUrl = generateBucketUrl(jobDto.data, JobRequestType.HCAPTCHA); - const objectsInBucket = await listObjectsInBucket(dataUrl); - - const commonManifestProperties = { - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: {}, - restricted_audience: this.buildHCaptchaRestrictedAudience( - jobDto.advanced, - ), - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - job_total_tasks: objectsInBucket.length, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: await this.generateAndUploadTaskData( - dataUrl.href, - objectsInBucket, - ), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: this.web3ConfigService.hCaptchaReputationOracleURI, - ro_uri: this.web3ConfigService.hCaptchaRecordingOracleURI, - ...(jobDto.qualifications && { - qualifications: jobDto.qualifications, - }), - }; - - let groundTruthsData; - if (jobDto.annotations.groundTruths) { - groundTruthsData = await this.storageService.downloadJsonLikeData( - jobDto.annotations.groundTruths, - ); - } - - switch (jobType) { - case JobCaptchaShapeType.COMPARISON: - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_BINARY, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: {}, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - case JobCaptchaShapeType.CATEGORIZATION: - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_MULTIPLE_CHOICE, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: - this.buildHCaptchaRestrictedAnswerSet(groundTruthsData), - }; - - case JobCaptchaShapeType.POLYGON: - if (!jobDto.annotations.label) { - throw new ControlledError( - ErrorJob.JobParamsValidationFailed, - HttpStatus.BAD_REQUEST, - ); - } - - const polygonManifest = { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: JobCaptchaShapeType.POLYGON, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_POLYGON_MIN_POINTS, - max_points: HCAPTCHA_POLYGON_MAX_POINTS, - minimum_selection_area_per_shape: - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - return polygonManifest; - - case JobCaptchaShapeType.POINT: - if (!jobDto.annotations.label) { - throw new ControlledError( - ErrorJob.JobParamsValidationFailed, - HttpStatus.BAD_REQUEST, - ); - } - - const pointManifest = { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: jobType, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_LANDMARK_MIN_POINTS, - max_points: HCAPTCHA_LANDMARK_MAX_POINTS, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - return pointManifest; - case JobCaptchaShapeType.BOUNDING_BOX: - if (!jobDto.annotations.label) { - throw new ControlledError( - ErrorJob.JobParamsValidationFailed, - HttpStatus.BAD_REQUEST, - ); - } - - const boundingBoxManifest = { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: jobType, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - max_points: HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - return boundingBoxManifest; - case JobCaptchaShapeType.IMMO: - if (!jobDto.annotations.label) { - throw new ControlledError( - ErrorJob.JobParamsValidationFailed, - HttpStatus.BAD_REQUEST, - ); - } - - const immoManifest = { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.TEXT_FREEE_NTRY, - request_config: { - multiple_choice_max_choices: 1, - multiple_choice_min_choices: 1, - overlap_threshold: null, - answer_type: 'str', - max_length: HCAPTCHA_IMMO_MAX_LENGTH, - min_length: HCAPTCHA_IMMO_MIN_LENGTH, - }, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - taskdata: [], - }; - - return immoManifest; - - default: - throw new ControlledError( - ErrorJob.HCaptchaInvalidJobType, - HttpStatus.CONFLICT, - ); - } - } - - public async createAudinoManifest( - dto: JobAudinoDto, - requestType: JobRequestType, - tokenFundAmount: number, - ): Promise { - const { generateUrls } = this.createManifestActions[requestType]; - const urls = generateUrls(dto.data, dto.groundTruth); - const totalSegments = Math.ceil( - (dto.audioDuration * 1000) / dto.segmentDuration, - ); - - const jobBounty = - ethers.parseUnits(tokenFundAmount.toString(), 'ether') / - BigInt(totalSegments); - - return { - annotation: { - description: dto.requesterDescription, - labels: dto.labels, - qualifications: dto.qualifications || [], - type: requestType, - user_guide: dto.userGuide, - segment_duration: dto.segmentDuration, - }, - data: { - data_url: urls.dataUrl.href, - }, - job_bounty: ethers.formatEther(jobBounty), - validation: { - gt_url: urls.gtUrl.href, - min_quality: dto.minQuality, - }, - }; - } - - private buildHCaptchaRestrictedAudience(advanced: JobCaptchaAdvancedDto) { - const restrictedAudience: RestrictedAudience = {}; - - restrictedAudience.sitekey = [ - { - [this.authConfigService.hCaptchaSiteKey]: { - score: 1, - }, - }, - ]; - - if (advanced.workerLanguage) { - restrictedAudience.lang = [{ [advanced.workerLanguage]: { score: 1 } }]; - } - - if (advanced.workerLocation) { - restrictedAudience.country = [ - { [advanced.workerLocation]: { score: 1 } }, - ]; - } - - if (advanced.targetBrowser) { - restrictedAudience.browser = [{ [advanced.targetBrowser]: { score: 1 } }]; - } - - return restrictedAudience; - } - - private buildHCaptchaRestrictedAnswerSet(groundTruthsData: any) { - const maxElements = 3; - const outputObject: any = {}; - - let elementCount = 0; - - for (const key of Object.keys(groundTruthsData)) { - if (elementCount >= maxElements) { - break; - } - - const value = groundTruthsData[key][0][0]; - outputObject[value] = { en: value, answer_example_uri: key }; - elementCount++; - } - - // Default case - outputObject['0'] = { en: HCAPTCHA_NOT_PRESENTED_LABEL }; - - return outputObject; - } - - public async generateAndUploadTaskData( - dataUrl: string, - objectNames: string[], - ) { - const data = objectNames.map((objectName) => { - return { - datapoint_uri: `${dataUrl}/${objectName}`, - datapoint_hash: 'undefined-hash', - task_key: uuidv4(), - }; - }); - - const { url } = await this.storageService.uploadJsonLikeData(data); - return url; - } - - private createJobSpecificActions: Record = { - [JobRequestType.HCAPTCHA]: { - createManifest: (dto: JobCaptchaDto) => this.createHCaptchaManifest(dto), - }, - [JobRequestType.FORTUNE]: { - createManifest: async ( - dto: JobFortuneDto, - requestType: JobRequestType, - fundAmount: number, - ) => ({ - ...dto, - requestType, - fundAmount, - }), - }, - [JobRequestType.IMAGE_POLYGONS]: { - createManifest: ( - dto: JobCvatDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createCvatManifest(dto, requestType, fundAmount), - }, - [JobRequestType.IMAGE_BOXES]: { - createManifest: ( - dto: JobCvatDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createCvatManifest(dto, requestType, fundAmount), - }, - [JobRequestType.IMAGE_POINTS]: { - createManifest: ( - dto: JobCvatDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createCvatManifest(dto, requestType, fundAmount), - }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - createManifest: ( - dto: JobCvatDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createCvatManifest(dto, requestType, fundAmount), - }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - createManifest: ( - dto: JobCvatDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createCvatManifest(dto, requestType, fundAmount), - }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { - createManifest: ( - dto: JobAudinoDto, - requestType: JobRequestType, - fundAmount: number, - ) => this.createAudinoManifest(dto, requestType, fundAmount), - }, - }; - private createEscrowSpecificActions: Record = { - [JobRequestType.HCAPTCHA]: { + [HCaptchaJobType.HCAPTCHA]: { getTrustedHandlers: () => [], }, - [JobRequestType.FORTUNE]: { + [FortuneJobType.FORTUNE]: { getTrustedHandlers: () => [], }, - [JobRequestType.IMAGE_POLYGONS]: { + [CvatJobType.IMAGE_POLYGONS]: { getTrustedHandlers: () => [], }, - [JobRequestType.IMAGE_BOXES]: { + [CvatJobType.IMAGE_BOXES]: { getTrustedHandlers: () => [], }, - [JobRequestType.IMAGE_POINTS]: { + [CvatJobType.IMAGE_POINTS]: { getTrustedHandlers: () => [], }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { + [CvatJobType.IMAGE_BOXES_FROM_POINTS]: { getTrustedHandlers: () => [], }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { + [CvatJobType.IMAGE_SKELETONS_FROM_BOXES]: { getTrustedHandlers: () => [], }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { + [AudinoJobType.AUDIO_TRANSCRIPTION]: { getTrustedHandlers: () => [], }, }; - private createManifestActions: Record = { - [JobRequestType.HCAPTCHA]: { - getElementsCount: async () => 0, - generateUrls: () => ({ dataUrl: new URL(''), gtUrl: new URL('') }), - }, - [JobRequestType.FORTUNE]: { - getElementsCount: async () => 0, - generateUrls: () => ({ dataUrl: new URL(''), gtUrl: new URL('') }), - }, - [JobRequestType.IMAGE_POLYGONS]: { - getElementsCount: async (urls: GenerateUrls) => { - const gt = (await this.storageService.downloadJsonLikeData( - `${urls.gtUrl.protocol}//${urls.gtUrl.host}${urls.gtUrl.pathname}`, - )) as any; - if (!gt || !gt.images || gt.images.length === 0) - throw new ControlledError( - ErrorJob.GroundThuthValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - const data = await listObjectsInBucket(urls.dataUrl); - if (!data || data.length === 0 || !data[0]) - throw new ControlledError( - ErrorJob.DatasetValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - await this.checkImageConsistency(gt.images, data); - - return data.length - gt.images.length; - }, - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - const requestType = JobRequestType.IMAGE_POLYGONS; - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - }; - }, - }, - [JobRequestType.IMAGE_BOXES]: { - getElementsCount: async (urls: GenerateUrls) => { - const gt = (await this.storageService.downloadJsonLikeData( - `${urls.gtUrl.protocol}//${urls.gtUrl.host}${urls.gtUrl.pathname}`, - )) as any; - if (!gt || !gt.images || gt.images.length === 0) - throw new ControlledError( - ErrorJob.GroundThuthValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - const data = await listObjectsInBucket(urls.dataUrl); - if (!data || data.length === 0 || !data[0]) - throw new ControlledError( - ErrorJob.DatasetValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - await this.checkImageConsistency(gt.images, data); - - return data.length - gt.images.length; - }, - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - const requestType = JobRequestType.IMAGE_BOXES; - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - }; - }, - }, - [JobRequestType.IMAGE_POINTS]: { - getElementsCount: async (urls: GenerateUrls) => { - const gt = (await this.storageService.downloadJsonLikeData( - `${urls.gtUrl.protocol}//${urls.gtUrl.host}${urls.gtUrl.pathname}`, - )) as any; - if (!gt || !gt.images || gt.images.length === 0) - throw new ControlledError( - ErrorJob.GroundThuthValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - const data = await listObjectsInBucket(urls.dataUrl); - if (!data || data.length === 0 || !data[0]) - throw new ControlledError( - ErrorJob.DatasetValidationFailed, - HttpStatus.BAD_REQUEST, - ); - - await this.checkImageConsistency(gt.images, data); - - return data.length - gt.images.length; - }, - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - const requestType = JobRequestType.IMAGE_POINTS; - - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - }; - }, - }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - getElementsCount: async (urls: GenerateUrls) => - this.getCvatElementsCount(urls.gtUrl, urls.pointsUrl!), - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - if (!data.points) { - throw new ControlledError(ErrorJob.DataNotExist, HttpStatus.CONFLICT); - } - - const requestType = JobRequestType.IMAGE_BOXES_FROM_POINTS; - - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - pointsUrl: generateBucketUrl(data.points, requestType), - }; - }, - }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - getElementsCount: async (urls: GenerateUrls) => - this.getCvatElementsCount(urls.gtUrl, urls.boxesUrl!), - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - if (!data.boxes) { - throw new ControlledError(ErrorJob.DataNotExist, HttpStatus.CONFLICT); - } - - const requestType = JobRequestType.IMAGE_SKELETONS_FROM_BOXES; - - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - boxesUrl: generateBucketUrl(data.boxes, requestType), - }; - }, - }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { - getElementsCount: async () => 0, - generateUrls: ( - data: CvatDataDto, - groundTruth: StorageDataDto, - ): GenerateUrls => { - const requestType = JobRequestType.AUDIO_TRANSCRIPTION; - - return { - dataUrl: generateBucketUrl(data.dataset, requestType), - gtUrl: generateBucketUrl(groundTruth, requestType), - }; - }, - }, - }; - - private getOraclesSpecificActions: Record = { - [JobRequestType.HCAPTCHA]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.hCaptchaOracleAddress; - const recordingOracle = this.web3ConfigService.hCaptchaOracleAddress; - const reputationOracle = this.web3ConfigService.hCaptchaOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.FORTUNE]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = - this.web3ConfigService.fortuneExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.fortuneRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.IMAGE_POLYGONS]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.cvatRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.IMAGE_BOXES]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.cvatRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.IMAGE_POINTS]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.cvatRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.cvatRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - getOracleAddresses: (): OracleAddresses => { - const exchangeOracle = this.web3ConfigService.cvatExchangeOracleAddress; - const recordingOracle = - this.web3ConfigService.cvatRecordingOracleAddress; - const reputationOracle = this.web3ConfigService.reputationOracleAddress; - - return { exchangeOracle, recordingOracle, reputationOracle }; - }, - }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { - getOracleAddresses: (): OracleAddresses => { - return { - exchangeOracle: this.web3ConfigService.audinoExchangeOracleAddress, - recordingOracle: this.web3ConfigService.audinoRecordingOracleAddress, - reputationOracle: this.web3ConfigService.reputationOracleAddress, - }; - }, - }, - }; - - private async checkImageConsistency( - gtImages: any[], - dataFiles: string[], - ): Promise { - const gtFileNames = gtImages.map((image: any) => image.file_name); - const baseFileNames = dataFiles.map((fileName) => - fileName.split('/').pop(), - ); - const missingFileNames = gtFileNames.filter( - (fileName: any) => !baseFileNames.includes(fileName), - ); - - if (missingFileNames.length !== 0) { - throw new ControlledError( - ErrorJob.ImageConsistency, - HttpStatus.BAD_REQUEST, - ); - } - } - public async createJob( user: UserEntity, requestType: JobRequestType, dto: CreateJob, ): Promise { let { chainId, reputationOracle, exchangeOracle, recordingOracle } = dto; - if (!Object.values(JobRequestType).includes(requestType)) { - throw new ControlledError( - ErrorJob.InvalidRequestType, - HttpStatus.BAD_REQUEST, - ); - } // Select network chainId = chainId || this.routingProtocolService.selectNetwork(); @@ -948,8 +245,6 @@ export class JobService { }); } - const { createManifest } = this.createJobSpecificActions[requestType]; - let jobEntity = new JobEntity(); if (dto instanceof JobQuickLaunchDto) { @@ -970,16 +265,17 @@ export class JobService { jobEntity.manifestUrl = dto.manifestUrl; } else { - const manifestOrigin = await createManifest( + const manifestOrigin = await this.manifestService.createManifest( dto, requestType, fundTokenAmount, + fundTokenDecimals, ); - const { url, hash } = await this.uploadManifest( - requestType, + const { url, hash } = await this.manifestService.uploadManifest( chainId, manifestOrigin, + [exchangeOracle, reputationOracle, recordingOracle], ); jobEntity.manifestUrl = url; @@ -1007,7 +303,13 @@ export class JobService { if ( user.whitelist || - [JobRequestType.AUDIO_TRANSCRIPTION].includes(requestType) + ( + [ + AudinoJobType.AUDIO_TRANSCRIPTION, + FortuneJobType.FORTUNE, + HCaptchaJobType.HCAPTCHA, + ] as JobRequestType[] + ).includes(requestType) ) { jobEntity.status = JobStatus.MODERATION_PASSED; } else { @@ -1019,73 +321,6 @@ export class JobService { return jobEntity.id; } - public async getCvatElementsCount(gtUrl: URL, dataUrl: URL): Promise { - const data = (await this.storageService.downloadJsonLikeData( - dataUrl.href, - )) as any; - const gt = (await this.storageService.downloadJsonLikeData( - gtUrl.href, - )) as any; - - if (!gt || !gt.images || gt.images.length === 0) { - throw new ControlledError( - ErrorJob.GroundThuthValidationFailed, - HttpStatus.BAD_REQUEST, - ); - } - - let gtEntries = 0; - - gt.images.forEach((gtImage: CvatImageData) => { - const { id } = data.images.find( - (dataImage: CvatImageData) => dataImage.file_name === gtImage.file_name, - ); - - if (id) { - const matchingAnnotations = data.annotations.filter( - (dataAnnotation: CvatAnnotationData) => - dataAnnotation.image_id === id, - ); - gtEntries += matchingAnnotations.length; - } - }); - - return data.annotations.length - gtEntries; - } - - public async calculateJobBounty( - params: CvatCalculateJobBounty, - ): Promise { - const { requestType, fundAmount, urls, nodesTotal } = params; - - const { getElementsCount } = this.createManifestActions[requestType]; - const elementsCount = await getElementsCount(urls); - - let jobSize = Number(this.cvatConfigService.jobSize); - - if (requestType === JobRequestType.IMAGE_SKELETONS_FROM_BOXES) { - const jobSizeMultiplier = Number( - this.cvatConfigService.skeletonsJobSizeMultiplier, - ); - jobSize *= jobSizeMultiplier; - } - - let totalJobs: number; - - // For each skeleton node CVAT creates a separate project thus increasing amount of jobs - if ( - requestType === JobRequestType.IMAGE_SKELETONS_FROM_BOXES && - nodesTotal - ) { - totalJobs = Math.ceil(elementsCount / jobSize) * nodesTotal; - } else { - totalJobs = Math.ceil(elementsCount / jobSize); - } - const jobBounty = - ethers.parseUnits(fundAmount.toString(), 'ether') / BigInt(totalJobs); - return ethers.formatEther(jobBounty); - } - public async createEscrow(jobEntity: JobEntity): Promise { const { getTrustedHandlers } = this.createEscrowSpecificActions[jobEntity.requestType]; @@ -1117,33 +352,24 @@ export class JobService { } public async setupEscrow(jobEntity: JobEntity): Promise { - const { getOracleAddresses } = - this.getOraclesSpecificActions[jobEntity.requestType]; - const signer = this.web3Service.getSigner(jobEntity.chainId); const escrowClient = await EscrowClient.build(signer); - const manifest = (await this.storageService.downloadJsonLikeData( - jobEntity.manifestUrl, - )) as any; - - await this.validateManifest(jobEntity.requestType, manifest); - - const oracleAddresses = getOracleAddresses(); - const escrowConfig = { - ...oracleAddresses, + recordingOracle: jobEntity.recordingOracle, recordingOracleFee: await this.getOracleFee( - oracleAddresses.recordingOracle, + jobEntity.recordingOracle, jobEntity.chainId, ), + reputationOracle: jobEntity.reputationOracle, reputationOracleFee: await this.getOracleFee( - oracleAddresses.reputationOracle, + jobEntity.reputationOracle, jobEntity.chainId, ), + exchangeOracle: jobEntity.exchangeOracle, exchangeOracleFee: await this.getOracleFee( - oracleAddresses.exchangeOracle, + jobEntity.exchangeOracle, jobEntity.chainId, ), manifestUrl: jobEntity.manifestUrl, @@ -1291,81 +517,6 @@ export class JobService { await this.jobRepository.updateOne(jobEntity); } - public async uploadManifest( - requestType: JobRequestType, - chainId: ChainId, - data: any, - ): Promise { - let manifestFile = data; - - if (this.pgpConfigService.encrypt) { - const { getOracleAddresses } = - this.getOraclesSpecificActions[requestType]; - - const signer = this.web3Service.getSigner(chainId); - const publicKeys: string[] = [ - await KVStoreUtils.getPublicKey(chainId, signer.address), - ]; - const oracleAddresses = getOracleAddresses(); - for (const address of Object.values(oracleAddresses)) { - const publicKey = await KVStoreUtils.getPublicKey(chainId, address); - if (publicKey) publicKeys.push(publicKey); - } - const encryptedManifest = await this.encryption.signAndEncrypt( - JSON.stringify(data), - publicKeys, - ); - manifestFile = encryptedManifest; - } - - const uploadedFile = - await this.storageService.uploadJsonLikeData(manifestFile); - - if (!uploadedFile) { - throw new ControlledError( - ErrorBucket.UnableSaveFile, - HttpStatus.BAD_REQUEST, - ); - } - - return uploadedFile; - } - - private async validateManifest( - requestType: JobRequestType, - manifest: FortuneManifestDto | CvatManifestDto | HCaptchaManifestDto, - ): Promise { - let dtoCheck; - - if (requestType === JobRequestType.FORTUNE) { - dtoCheck = new FortuneManifestDto(); - } else if (requestType === JobRequestType.HCAPTCHA) { - return true; - dtoCheck = new HCaptchaManifestDto(); - } else if (requestType === JobRequestType.AUDIO_TRANSCRIPTION) { - dtoCheck = new AudinoManifestDto(); - } else { - dtoCheck = new CvatManifestDto(); - } - - Object.assign(dtoCheck, manifest); - - const validationErrors: ValidationError[] = await validate(dtoCheck); - if (validationErrors.length > 0) { - this.logger.log( - ErrorJob.ManifestValidationFailed, - JobService.name, - validationErrors, - ); - throw new ControlledError( - ErrorJob.ManifestValidationFailed, - HttpStatus.NOT_FOUND, - ); - } - - return true; - } - public async getJobsByStatus( data: GetJobsDto, userId: number, @@ -1430,7 +581,7 @@ export class JobService { throw new ControlledError(ErrorJob.ResultNotFound, HttpStatus.NOT_FOUND); } - if (jobEntity.requestType === JobRequestType.FORTUNE) { + if (jobEntity.requestType === FortuneJobType.FORTUNE) { const data = (await this.storageService.downloadJsonLikeData( finalResultUrl, )) as Array; @@ -1488,7 +639,7 @@ export class JobService { throw new ControlledError(ErrorJob.ResultNotFound, HttpStatus.NOT_FOUND); } - if (jobEntity.requestType === JobRequestType.FORTUNE) { + if (jobEntity.requestType === FortuneJobType.FORTUNE) { throw new ControlledError( ErrorJob.InvalidRequestType, HttpStatus.BAD_REQUEST, @@ -1529,11 +680,11 @@ export class JobService { }; public getOracleType(requestType: JobRequestType): OracleType { - if (requestType === JobRequestType.FORTUNE) { + if (requestType === FortuneJobType.FORTUNE) { return OracleType.FORTUNE; - } else if (requestType === JobRequestType.HCAPTCHA) { + } else if (requestType === HCaptchaJobType.HCAPTCHA) { return OracleType.HCAPTCHA; - } else if (requestType === JobRequestType.AUDIO_TRANSCRIPTION) { + } else if (requestType === AudinoJobType.AUDIO_TRANSCRIPTION) { return OracleType.AUDINO; } else { return OracleType.CVAT; @@ -1636,23 +787,24 @@ export class JobService { escrow = await EscrowUtils.getEscrow(chainId, escrowAddress); } - const manifestData = (await this.storageService.downloadJsonLikeData( + const manifestData = await this.manifestService.downloadManifest( manifestUrl, - )) as any; + jobEntity.requestType, + ); - if (!manifestData) { - throw new ControlledError( - ErrorJob.ManifestNotFound, - HttpStatus.NOT_FOUND, - ); - } + const fundTokenDecimals = getTokenDecimals( + chainId, + jobEntity.token as EscrowFundToken, + ); const baseManifestDetails = { chainId, tokenAddress: escrow ? escrow.token : ethers.ZeroAddress, requesterAddress: signer.address, fundAmount: escrow - ? Number(ethers.formatEther(escrow.totalFundedAmount)) + ? Number( + ethers.formatUnits(escrow.totalFundedAmount, fundTokenDecimals), + ) : 0, exchangeOracleAddress: escrow?.exchangeOracle || ethers.ZeroAddress, recordingOracleAddress: escrow?.recordingOracle || ethers.ZeroAddress, @@ -1660,22 +812,22 @@ export class JobService { }; let specificManifestDetails; - if (jobEntity.requestType === JobRequestType.FORTUNE) { + if (jobEntity.requestType === FortuneJobType.FORTUNE) { const manifest = manifestData as FortuneManifestDto; specificManifestDetails = { title: manifest.requesterTitle, description: manifest.requesterDescription, - requestType: JobRequestType.FORTUNE, + requestType: FortuneJobType.FORTUNE, submissionsRequired: manifest.submissionsRequired, ...(manifest.qualifications && manifest.qualifications?.length > 0 && { qualifications: manifest.qualifications, }), }; - } else if (jobEntity.requestType === JobRequestType.HCAPTCHA) { + } else if (jobEntity.requestType === HCaptchaJobType.HCAPTCHA) { const manifest = manifestData as HCaptchaManifestDto; specificManifestDetails = { - requestType: JobRequestType.HCAPTCHA, + requestType: HCaptchaJobType.HCAPTCHA, submissionsRequired: manifest.job_total_tasks, ...(manifest.qualifications && manifest.qualifications?.length > 0 && { @@ -1720,8 +872,12 @@ export class JobService { escrowAddress, manifestUrl, manifestHash, - balance: Number(ethers.formatEther(escrow?.balance || 0)), - paidOut: Number(ethers.formatEther(escrow?.amountPaid || 0)), + balance: Number( + ethers.formatUnits(escrow?.balance || 0, fundTokenDecimals), + ), + paidOut: Number( + ethers.formatUnits(escrow?.amountPaid || 0, fundTokenDecimals), + ), currency: jobEntity.token as EscrowFundToken, status: jobEntity.status, failedReason: jobEntity.failedReason, @@ -1730,53 +886,6 @@ export class JobService { }; } - public async getTransferLogs( - chainId: ChainId, - tokenAddress: string, - fromBlock: number, - toBlock: string | number, - ) { - const signer = this.web3Service.getSigner(chainId); - const filter = { - address: tokenAddress, - topics: [ethers.id('Transfer(address,address,uint256)')], - fromBlock: fromBlock, - toBlock: toBlock, - }; - - return signer.provider?.getLogs(filter); - } - - public async getPaidOutAmount( - chainId: ChainId, - tokenAddress: string, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - const tokenContract: HMToken = HMToken__factory.connect( - tokenAddress, - signer, - ); - - const logs = await this.getTransferLogs(chainId, tokenAddress, 0, 'latest'); - let paidOutAmount = new Decimal(0); - - logs?.forEach((log) => { - const parsedLog = tokenContract.interface.parseLog({ - topics: log.topics as string[], - data: log.data, - }); - const from = parsedLog?.args[0]; - const amount = parsedLog?.args[2]; - - if (from === escrowAddress) { - paidOutAmount = paidOutAmount.add(ethers.formatEther(amount)); - } - }); - - return Number(paidOutAmount); - } - private async getOracleFee( oracleAddress: string, chainId: ChainId, diff --git a/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts new file mode 100644 index 0000000000..8ff0012edf --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts @@ -0,0 +1,142 @@ +import { faker } from '@faker-js/faker'; +import { JobCvatDto, JobAudinoDto, JobCaptchaDto } from '../job/job.dto'; +import { AWSRegions, StorageProviders } from '../../common/enums/storage'; +import { ChainId } from '@human-protocol/sdk'; +import { PaymentCurrency } from '../../common/enums/payment'; +import { + AudinoJobType, + CvatJobType, + EscrowFundToken, + JobCaptchaShapeType, +} from '../../common/enums/job'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; + +export const mockCvatConfigService: Omit = { + jobSize: faker.number.int({ min: 1, max: 1000 }), + maxTime: faker.number.int({ min: 1, max: 1000 }), + valSize: faker.number.int({ min: 1, max: 1000 }), + skeletonsJobSizeMultiplier: faker.number.int({ min: 1, max: 1000 }), +}; + +export const mockAuthConfigService: Omit< + Partial, + 'configService' +> = { + hCaptchaSiteKey: faker.string.uuid(), +}; + +export const mockWeb3ConfigService: Omit< + Partial, + 'configService' +> = { + hCaptchaReputationOracleURI: faker.internet.url(), + hCaptchaRecordingOracleURI: faker.internet.url(), +}; + +export function getMockedProvider(): StorageProviders { + return faker.helpers.arrayElement( + Object.values(StorageProviders).filter( + (provider) => provider !== StorageProviders.LOCAL, + ), + ); +} + +export function getMockedRegion(): AWSRegions { + return faker.helpers.arrayElement(Object.values(AWSRegions)); +} + +export function createJobCvatDto( + overrides: Partial = {}, +): JobCvatDto { + return { + data: { + dataset: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + }, + labels: [{ name: faker.lorem.word(), nodes: [faker.string.uuid()] }], + requesterDescription: faker.lorem.sentence(), + userGuide: faker.internet.url(), + minQuality: faker.number.float({ min: 0.1, max: 1 }), + groundTruth: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + type: CvatJobType.IMAGE_BOXES, + chainId: faker.helpers.arrayElement(Object.values(ChainId)) as ChainId, + paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)), + paymentAmount: faker.number.int({ min: 1, max: 1000 }), + escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)), + ...overrides, + }; +} + +export function createJobAudinoDto( + overrides: Partial = {}, +): JobAudinoDto { + return { + data: { + dataset: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + }, + groundTruth: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + labels: [{ name: faker.lorem.word() }], + audioDuration: faker.number.int({ min: 100, max: 1000 }), + segmentDuration: faker.number.int({ min: 10, max: 100 }), + requesterDescription: faker.lorem.sentence(), + userGuide: faker.internet.url(), + qualifications: [faker.lorem.word()], + minQuality: faker.number.int({ min: 1, max: 100 }), + type: AudinoJobType.AUDIO_TRANSCRIPTION, + paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)), + paymentAmount: faker.number.int({ min: 1, max: 1000 }), + escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)), + ...overrides, + }; +} + +export function createJobCaptchaDto( + overrides: Partial = {}, +): JobCaptchaDto { + return { + data: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + accuracyTarget: faker.number.float({ min: 0.1, max: 1 }), + minRequests: faker.number.int({ min: 1, max: 5 }), + maxRequests: faker.number.int({ min: 6, max: 10 }), + annotations: { + typeOfJob: faker.helpers.arrayElement(Object.values(JobCaptchaShapeType)), + labelingPrompt: faker.lorem.sentence(), + groundTruths: faker.internet.url(), + exampleImages: [faker.internet.url(), faker.internet.url()], + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + label: faker.lorem.word(), + }, + completionDate: faker.date.future(), + paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)), + paymentAmount: faker.number.int({ min: 1, max: 1000 }), + escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)), + advanced: {}, + ...overrides, + }; +} diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.dto.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.dto.ts new file mode 100644 index 0000000000..c4ab89f9a1 --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.dto.ts @@ -0,0 +1,466 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + Equals, + IsArray, + IsEthereumAddress, + IsNotEmpty, + IsNumber, + IsObject, + IsOptional, + IsPositive, + IsString, + IsUrl, + Min, + ValidateNested, +} from 'class-validator'; +import { IsEnumCaseInsensitive } from '../../common/decorators'; +import { + JobCaptchaRequestType, + JobCaptchaShapeType, + JobRequestType, +} from '../../common/enums/job'; +import { + FortuneJobType, + CvatJobType, + AudinoJobType, +} from '../../common/enums/job'; +import { Type } from 'class-transformer'; + +export class FortuneManifestDto { + @ApiProperty({ name: 'submissions_required' }) + @IsNumber() + @IsPositive() + public submissionsRequired: number; + + @ApiProperty({ name: 'requester_title' }) + @IsString() + public requesterTitle: string; + + @ApiProperty({ name: 'requester_description' }) + @IsString() + public requesterDescription: string; + + @ApiProperty({ name: 'fund_amount' }) + @IsNumber() + @IsPositive() + public fundAmount: number; + + @ApiProperty({ enum: FortuneJobType, name: 'request_type' }) + @IsEnumCaseInsensitive(FortuneJobType) + public requestType: FortuneJobType; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + public qualifications?: string[]; +} + +class CvatData { + @IsUrl() + public data_url: string; + + @IsUrl() + @IsOptional() + public points_url?: string; + + @IsUrl() + @IsOptional() + public boxes_url?: string; +} + +export class Label { + @ApiProperty() + @IsString() + public name: string; + + @ApiPropertyOptional() + @IsString({ each: true }) + @IsArray() + @IsOptional() + public nodes?: string[]; + + @ApiPropertyOptional() + @IsString({ each: true }) + @IsArray() + @IsOptional() + public joints?: string[]; +} + +class Annotation { + @IsArray() + @ValidateNested() + @Type(() => Label) + public labels: Label[]; + + @IsString() + public description: string; + + @IsString() + public user_guide: string; + + @IsEnumCaseInsensitive(CvatJobType) + public type: CvatJobType; + + @IsNumber() + @IsPositive() + public job_size: number; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + public qualifications?: string[]; +} + +class Validation { + @IsNumber() + @IsPositive() + public min_quality: number; + + @IsNumber() + @IsPositive() + public val_size: number; + + @IsString() + public gt_url: string; +} + +export class CvatManifestDto { + @IsObject() + @ValidateNested() + @Type(() => CvatData) + public data: CvatData; + + @IsObject() + @ValidateNested() + @Type(() => Annotation) + public annotation: Annotation; + + @IsObject() + @ValidateNested() + @Type(() => Validation) + public validation: Validation; + + @IsString() + public job_bounty: string; +} + +class AudinoData { + @IsUrl() + public data_url: string; +} + +class AudinoAnnotation { + @IsArray() + public labels: Array<{ name: string }>; + + @IsString() + public description: string; + + @IsString() + @IsUrl() + public user_guide: string; + + @Equals(AudinoJobType.AUDIO_TRANSCRIPTION) + public type: AudinoJobType.AUDIO_TRANSCRIPTION; + + @IsNumber() + @IsPositive() + public segment_duration: number; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + public qualifications?: string[]; +} + +class AudinoValidation { + @IsNumber() + @IsPositive() + public min_quality: number; + + @IsString() + @IsUrl() + public gt_url: string; +} + +export class AudinoManifestDto { + @IsObject() + @ValidateNested() + @Type(() => AudinoData) + public data: AudinoData; + + @IsObject() + @ValidateNested() + @Type(() => AudinoAnnotation) + public annotation: AudinoAnnotation; + + @IsObject() + @ValidateNested() + @Type(() => AudinoValidation) + public validation: AudinoValidation; + + @IsString() + public job_bounty: string; +} + +export class RestrictedAudience { + @IsObject() + @IsOptional() + sitekey?: Record[]; + + @IsObject() + @IsOptional() + lang?: Record[]; + + @IsObject() + @IsOptional() + browser?: Record[]; + + @IsObject() + @IsOptional() + country?: Record[]; +} + +class RequesterRestrictedAnswer { + @IsString() + @IsOptional() + en?: string; + + @IsUrl() + @IsOptional() + answer_example_uri?: string; +} + +class RequestConfig { + @IsEnumCaseInsensitive(JobCaptchaShapeType) + @IsOptional() + shape_type?: JobCaptchaShapeType; + + @IsNumber() + @IsPositive() + @IsOptional() + min_shapes_per_image?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + max_shapes_per_image?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + min_points?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + max_points?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + minimum_selection_area_per_shape?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + multiple_choice_max_choices?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + multiple_choice_min_choices?: number; + + @IsString() + @IsOptional() + answer_type?: string; + + @IsOptional() + overlap_threshold?: any; + + @IsNumber() + @IsPositive() + @IsOptional() + max_length?: number; + + @IsNumber() + @IsPositive() + @IsOptional() + min_length?: number; +} + +export class HCaptchaManifestDto { + @IsString() + job_mode: string; + + @IsEnumCaseInsensitive(JobCaptchaRequestType) + request_type: JobCaptchaRequestType; + + @IsObject() + @ValidateNested() + @Type(() => RequestConfig) + request_config: RequestConfig; + + @IsNumber() + requester_accuracy_target: number; + + @IsNumber() + requester_max_repeats: number; + + @IsNumber() + requester_min_repeats: number; + + @IsArray() + @IsUrl({}, { each: true }) + @IsOptional() + requester_question_example?: string[]; + + @IsObject() + requester_question: Record; + + @IsUrl() + taskdata_uri: string; + + @IsNumber() + job_total_tasks: number; + + @IsNumber() + task_bid_price: number; + + @IsUrl() + @IsOptional() + groundtruth_uri?: string; + + public_results: boolean; + + @IsNumber() + oracle_stake: number; + + @IsString() + repo_uri: string; + + @IsString() + ro_uri: string; + + @IsObject() + @ValidateNested() + @Type(() => RestrictedAudience) + restricted_audience: RestrictedAudience; + + @IsObject() + @ValidateNested() + @Type(() => RequesterRestrictedAnswer) + requester_restricted_answer_set: RequesterRestrictedAnswer; + + @IsOptional() + @IsArray() + @ValidateNested() + @Type(() => TaskData) + taskdata?: TaskData[]; + + @IsArray() + @IsString({ each: true }) + @IsOptional() + public qualifications?: string[]; +} + +class DatapointText { + @IsString() + en: string; +} + +class TaskData { + @IsString() + task_key: string; + + @IsOptional() + @IsString() + datapoint_uri?: string; + + @IsString() + datapoint_hash: string; + + @IsObject() + @IsOptional() + datapoint_text?: DatapointText; +} + +export class ManifestDetails { + @ApiProperty({ description: 'Chain ID', name: 'chain_id' }) + @IsNumber() + @Min(1) + public chainId: number; + + @ApiProperty({ description: 'Title (optional)' }) + @IsOptional() + @IsString() + public title?: string; + + @ApiProperty({ description: 'Description' }) + @IsNotEmpty() + @IsString() + @IsOptional() + public description?: string; + + @ApiProperty({ + description: 'Submissions required', + name: 'submissions_required', + }) + @IsNumber() + public submissionsRequired: number; + + @ApiProperty({ + description: 'Ethereum address of the token', + name: 'token_address', + }) + @IsEthereumAddress() + public tokenAddress: string; + + @ApiProperty({ description: 'Amount of funds', name: 'fund_amount' }) + @IsNumber() + public fundAmount: number; + + @ApiProperty({ + description: 'Ethereum address of the requester', + name: 'requester_address', + }) + @IsEthereumAddress() + public requesterAddress: string; + + @ApiProperty({ description: 'Request type', name: 'request_type' }) + @IsString() + public requestType: JobRequestType; + + @ApiProperty({ + description: 'Address of the exchange oracle (optional)', + name: 'exchange_oracle_address', + }) + @IsOptional() + @IsNotEmpty() + @IsEthereumAddress() + public exchangeOracleAddress?: string; + + @ApiProperty({ + description: 'Address of the recording oracle (optional)', + name: 'recording_oracle_address', + }) + @IsOptional() + @IsNotEmpty() + @IsEthereumAddress() + public recordingOracleAddress?: string; + + @ApiProperty({ + description: 'Address of the reputation oracle (optional)', + name: 'reputation_oracle_address', + }) + @IsOptional() + @IsNotEmpty() + @IsEthereumAddress() + public reputationOracleAddress?: string; +} + +export type ManifestDto = + | FortuneManifestDto + | CvatManifestDto + | HCaptchaManifestDto + | AudinoManifestDto; diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.module.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.module.ts new file mode 100644 index 0000000000..ab52fd4050 --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { ManifestService } from './manifest.service'; +import { StorageModule } from '../storage/storage.module'; +import { Web3Module } from '../web3/web3.module'; +import { EncryptionModule } from '../encryption/encryption.module'; +import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.module'; +import { RateModule } from '../rate/rate.module'; +import { QualificationModule } from '../qualification/qualification.module'; + +@Module({ + imports: [ + StorageModule, + Web3Module, + EncryptionModule, + RoutingProtocolModule, + RateModule, + QualificationModule, + ], + providers: [ManifestService], + exports: [ManifestService], +}) +export class ManifestModule {} diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts new file mode 100644 index 0000000000..2d509f00ad --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts @@ -0,0 +1,875 @@ +jest.mock('../../common/utils/storage', () => ({ + ...jest.requireActual('../../common/utils/storage'), + listObjectsInBucket: jest.fn(), +})); + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { faker } from '@faker-js/faker'; +import { createMock } from '@golevelup/ts-jest'; +import { Encryption } from '@human-protocol/sdk'; +import { HttpStatus } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { ethers } from 'ethers'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { + HCAPTCHA_BOUNDING_BOX_MAX_POINTS, + HCAPTCHA_BOUNDING_BOX_MIN_POINTS, + HCAPTCHA_IMMO_MAX_LENGTH, + HCAPTCHA_IMMO_MIN_LENGTH, + HCAPTCHA_LANDMARK_MAX_POINTS, + HCAPTCHA_LANDMARK_MIN_POINTS, + HCAPTCHA_MAX_SHAPES_PER_IMAGE, + HCAPTCHA_MIN_SHAPES_PER_IMAGE, + HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, + HCAPTCHA_ORACLE_STAKE, + HCAPTCHA_POLYGON_MAX_POINTS, + HCAPTCHA_POLYGON_MIN_POINTS, +} from '../../common/constants'; +import { ErrorJob } from '../../common/constants/errors'; +import { + AudinoJobType, + CvatJobType, + FortuneJobType, + HCaptchaJobType, + JobCaptchaMode, + JobCaptchaRequestType, + JobCaptchaShapeType, +} from '../../common/enums/job'; +import { ControlledError } from '../../common/errors/controlled'; +import { + generateBucketUrl, + listObjectsInBucket, +} from '../../common/utils/storage'; +import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { + createJobAudinoDto, + createJobCaptchaDto, + createJobCvatDto, + getMockedProvider, + getMockedRegion, + mockAuthConfigService, + mockCvatConfigService, + mockWeb3ConfigService, +} from './fixtures'; +import { FortuneManifestDto } from './manifest.dto'; +import { ManifestService } from './manifest.service'; + +describe('ManifestService', () => { + let manifestService: ManifestService; + const mockStorageService = { + uploadJsonLikeData: jest.fn(), + downloadJsonLikeData: jest.fn(), + }; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + ManifestService, + { provide: Web3Service, useValue: createMock() }, + { provide: StorageService, useValue: mockStorageService }, + { + provide: AuthConfigService, + useValue: mockAuthConfigService, + }, + { + provide: CvatConfigService, + useValue: mockCvatConfigService, + }, + { provide: PGPConfigService, useValue: { encrypt: false } }, + { + provide: Web3ConfigService, + useValue: mockWeb3ConfigService, + }, + { provide: Encryption, useValue: createMock() }, + ], + }).compile(); + + manifestService = moduleRef.get(ManifestService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('createManifest', () => { + describe('createCvatManifest', () => { + const tokenFundAmount = faker.number.int({ min: 1, max: 1000 }); + const tokenFundDecimals = faker.number.int({ min: 1, max: 18 }); + let jobBounty: string; + + beforeAll(() => { + jobBounty = faker.number.int({ min: 1, max: 1000 }).toString(); + manifestService['calculateCvatJobBounty'] = jest + .fn() + .mockResolvedValue(jobBounty); + }); + + it('should create a valid CVAT manifest for image boxes job type', async () => { + const dto = createJobCvatDto({ type: CvatJobType.IMAGE_BOXES }); + const requestType = CvatJobType.IMAGE_BOXES; + + const result = await manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + data: { + data_url: generateBucketUrl(dto.data.dataset, requestType).href, + }, + annotation: { + labels: dto.labels, + description: dto.requesterDescription, + user_guide: dto.userGuide, + type: requestType, + job_size: mockCvatConfigService.jobSize, + }, + validation: { + min_quality: dto.minQuality, + val_size: mockCvatConfigService.valSize, + gt_url: generateBucketUrl(dto.groundTruth, requestType).href, + }, + job_bounty: jobBounty, + }); + }); + + it('should create a valid CVAT manifest for image polygons job type', async () => { + const dto = createJobCvatDto({ type: CvatJobType.IMAGE_POLYGONS }); + const requestType = CvatJobType.IMAGE_POLYGONS; + + const result = await manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + data: { + data_url: generateBucketUrl(dto.data.dataset, requestType).href, + }, + annotation: { + labels: dto.labels, + description: dto.requesterDescription, + user_guide: dto.userGuide, + type: requestType, + job_size: mockCvatConfigService.jobSize, + }, + validation: { + min_quality: dto.minQuality, + val_size: mockCvatConfigService.valSize, + gt_url: generateBucketUrl(dto.groundTruth, requestType).href, + }, + job_bounty: jobBounty, + }); + }); + + it('should create a valid CVAT manifest for image boxes from points job type', async () => { + const dto = createJobCvatDto({ + data: { + dataset: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + points: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + }, + type: CvatJobType.IMAGE_BOXES_FROM_POINTS, + }); + const requestType = CvatJobType.IMAGE_BOXES_FROM_POINTS; + + const result = await manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + data: { + data_url: generateBucketUrl(dto.data.dataset, requestType).href, + points_url: generateBucketUrl(dto.data.points!, requestType).href, + }, + annotation: { + labels: dto.labels, + description: dto.requesterDescription, + user_guide: dto.userGuide, + type: requestType, + job_size: mockCvatConfigService.jobSize, + }, + validation: { + min_quality: dto.minQuality, + val_size: mockCvatConfigService.valSize, + gt_url: generateBucketUrl(dto.groundTruth, requestType).href, + }, + job_bounty: jobBounty, + }); + }); + + it('should create a valid CVAT manifest for image skeletons from boxes job type', async () => { + const dto = createJobCvatDto({ + data: { + dataset: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + boxes: { + provider: getMockedProvider(), + region: getMockedRegion(), + bucketName: faker.lorem.word(), + path: faker.system.filePath(), + }, + }, + type: CvatJobType.IMAGE_SKELETONS_FROM_BOXES, + }); + const requestType = CvatJobType.IMAGE_SKELETONS_FROM_BOXES; + + const result = await manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + data: { + data_url: generateBucketUrl(dto.data.dataset, requestType).href, + boxes_url: generateBucketUrl(dto.data.boxes!, requestType).href, + }, + annotation: { + labels: dto.labels, + description: dto.requesterDescription, + user_guide: dto.userGuide, + type: requestType, + job_size: mockCvatConfigService.jobSize, + }, + validation: { + min_quality: dto.minQuality, + val_size: mockCvatConfigService.valSize, + gt_url: generateBucketUrl(dto.groundTruth, requestType).href, + }, + job_bounty: jobBounty, + }); + }); + + it('should throw an error if data does not exist for image boxes from points job type', async () => { + const requestType = CvatJobType.IMAGE_BOXES_FROM_POINTS; + + const dto = createJobCvatDto({ type: requestType }); + + await expect( + manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError(ErrorJob.DataNotExist, HttpStatus.CONFLICT), + ); + }); + + it('should throw an error if data does not exist for image skeletons from boxes job type', async () => { + const requestType = CvatJobType.IMAGE_SKELETONS_FROM_BOXES; + + const dto = createJobCvatDto({ type: requestType }); + + await expect( + manifestService.createManifest( + dto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError(ErrorJob.DataNotExist, HttpStatus.CONFLICT), + ); + }); + }); + + describe('createAudinoManifest', () => { + it('should create an Audino manifest successfully', async () => { + const mockDto = createJobAudinoDto(); // Use the helper function + const mockRequestType = AudinoJobType.AUDIO_TRANSCRIPTION; + const mockTokenFundAmount = faker.number.int({ min: 1, max: 1000 }); + const mockTokenFundDecimals = faker.number.int({ min: 1, max: 18 }); + + const result = await manifestService.createManifest( + mockDto as any, + mockRequestType, + mockTokenFundAmount, + mockTokenFundDecimals, + ); + + const totalSegments = Math.ceil( + (mockDto.audioDuration * 1000) / mockDto.segmentDuration, + ); + const jobBounty = + ethers.parseUnits(mockTokenFundAmount.toString(), 'ether') / + BigInt(totalSegments); + + expect(result).toEqual({ + annotation: { + description: mockDto.requesterDescription, + labels: mockDto.labels, + qualifications: mockDto.qualifications || [], + type: mockRequestType, + user_guide: mockDto.userGuide, + segment_duration: mockDto.segmentDuration, + }, + data: { + data_url: generateBucketUrl(mockDto.data.dataset, mockRequestType) + .href, + }, + job_bounty: ethers.formatEther(jobBounty), + validation: { + gt_url: generateBucketUrl(mockDto.groundTruth, mockRequestType) + .href, + min_quality: mockDto.minQuality, + }, + }); + }); + }); + + describe('createHCaptchaManifest', () => { + const requestType = HCaptchaJobType.HCAPTCHA; + const tokenFundAmount = faker.number.int({ min: 1, max: 1000 }); + const tokenFundDecimals = faker.number.int({ min: 1, max: 18 }); + + beforeEach(() => { + const fileContent = JSON.stringify({ + [faker.internet.url()]: [true, true, true], + }); + (listObjectsInBucket as jest.Mock).mockResolvedValueOnce([ + `${faker.word.sample()}.jpg`, + `${faker.word.sample()}.jpg`, + `${faker.word.sample()}.jpg`, + ]); + mockStorageService.uploadJsonLikeData.mockResolvedValueOnce({ + url: faker.internet.url(), + hash: faker.string.uuid(), + }); + mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( + fileContent, + ); + }); + + it('should create a valid HCaptcha manifest for COMPARISON job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.COMPARISON, + labelingPrompt: faker.lorem.sentence(), + groundTruths: faker.internet.url(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + exampleImages: [faker.internet.url(), faker.internet.url()], + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: {}, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.IMAGE_LABEL_BINARY, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: {}, + requester_question_example: jobDto.annotations.exampleImages, + }); + }); + + it('should create a valid HCaptcha manifest for CATEGORIZATION job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.CATEGORIZATION, + labelingPrompt: faker.lorem.sentence(), + groundTruths: faker.internet.url(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: {}, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.IMAGE_LABEL_MULTIPLE_CHOICE, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: expect.any(Object), + }); + }); + + it('should create a valid HCaptcha manifest for POLYGON job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.POLYGON, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + label: faker.lorem.word(), + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: { + shape_type: JobCaptchaShapeType.POLYGON, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_POLYGON_MIN_POINTS, + max_points: HCAPTCHA_POLYGON_MAX_POINTS, + minimum_selection_area_per_shape: + HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, + }, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + requester_question_example: [], + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + }); + }); + + it('should throw ControlledError for invalid POLYGON job type without label', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.POLYGON, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + }, + }); + + await expect( + manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ), + ); + }); + + it('should create a valid HCaptcha manifest for POINT job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.POINT, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + label: faker.lorem.word(), + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: { + shape_type: JobCaptchaShapeType.POINT, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_LANDMARK_MIN_POINTS, + max_points: HCAPTCHA_LANDMARK_MAX_POINTS, + }, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + requester_question_example: jobDto.annotations.exampleImages || [], + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + }); + }); + + it('should throw ControlledError for invalid POINT job type without label', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.POINT, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + }, + }); + + await expect( + manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ), + ); + }); + + it('should create a valid HCaptcha manifest for BOUNDING_BOX job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.BOUNDING_BOX, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + label: faker.lorem.word(), + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: { + shape_type: JobCaptchaShapeType.BOUNDING_BOX, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_BOUNDING_BOX_MIN_POINTS, + max_points: HCAPTCHA_BOUNDING_BOX_MAX_POINTS, + }, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + requester_question_example: jobDto.annotations.exampleImages || [], + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + }); + }); + + it('should throw ControlledError for invalid BOUNDING_BOX job type without label', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.BOUNDING_BOX, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + }, + }); + + await expect( + manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ), + ); + }); + + it('should create a valid HCaptcha manifest for IMMO job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.IMMO, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + label: faker.lorem.word(), + }, + }); + + const result = await manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ); + + expect(result).toEqual({ + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: { + multiple_choice_max_choices: 1, + multiple_choice_min_choices: 1, + overlap_threshold: null, + answer_type: 'str', + max_length: HCAPTCHA_IMMO_MAX_LENGTH, + min_length: HCAPTCHA_IMMO_MIN_LENGTH, + }, + restricted_audience: { + sitekey: [expect.any(Object)], + }, + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + job_total_tasks: 3, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata: [], + taskdata_uri: expect.any(String), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, + ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, + request_type: JobCaptchaRequestType.TEXT_FREEE_NTRY, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + }); + }); + + it('should throw ControlledError for invalid IMMO job type without label', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: JobCaptchaShapeType.IMMO, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + }, + }); + + await expect( + manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ), + ); + }); + + it('should throw ControlledError for invalid job type', async () => { + const jobDto = createJobCaptchaDto({ + annotations: { + typeOfJob: 'INVALID_JOB_TYPE' as JobCaptchaShapeType, + labelingPrompt: faker.lorem.sentence(), + taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), + groundTruths: faker.internet.url(), + }, + }); + + await expect( + manifestService.createManifest( + jobDto, + requestType, + tokenFundAmount, + tokenFundDecimals, + ), + ).rejects.toThrow( + new ControlledError( + ErrorJob.HCaptchaInvalidJobType, + HttpStatus.CONFLICT, + ), + ); + }); + }); + }); + + describe('uploadManifest', () => { + it('should upload a manifest successfully', async () => { + const mockChainId = faker.number.int(); + const mockData = { key: faker.lorem.word() }; + const mockOracleAddresses: string[] = []; + const mockManifestData = { + url: faker.internet.url(), + hash: faker.string.uuid(), + }; + + mockStorageService.uploadJsonLikeData.mockResolvedValueOnce( + mockManifestData, + ); + + const result = await manifestService.uploadManifest( + mockChainId, + mockData, + mockOracleAddresses, + ); + + expect(result).toEqual( + expect.objectContaining({ + url: mockManifestData.url, + hash: mockManifestData.hash, + }), + ); + }); + + it('should throw an error if upload fails', async () => { + const mockChainId = faker.number.int(); + const mockData = { key: faker.lorem.word() }; + const mockOracleAddresses: string[] = []; + + mockStorageService.uploadJsonLikeData.mockRejectedValue( + new ControlledError('File not uploaded', HttpStatus.BAD_REQUEST), + ); + + await expect( + manifestService.uploadManifest( + mockChainId, + mockData, + mockOracleAddresses, + ), + ).rejects.toThrow(ControlledError); + }); + }); + + describe('downloadManifest', () => { + it('should download and validate a manifest successfully', async () => { + const mockManifestUrl = faker.internet.url(); + const mockRequestType = FortuneJobType.FORTUNE; + const mockManifest: FortuneManifestDto = { + submissionsRequired: faker.number.int({ min: 1, max: 100 }), + requesterTitle: faker.lorem.words(3), + requesterDescription: faker.lorem.sentence(), + fundAmount: faker.number.float({ min: 1, max: 1000 }), + requestType: FortuneJobType.FORTUNE, + qualifications: [faker.lorem.word(), faker.lorem.word()], + }; + mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( + mockManifest, + ); + const result = await manifestService.downloadManifest( + mockManifestUrl, + mockRequestType, + ); + expect(result).toEqual(mockManifest); + }); + + it('should throw an error if validation fails', async () => { + const mockManifestUrl = faker.internet.url(); + const mockRequestType = CvatJobType.IMAGE_BOXES; + const mockManifest: FortuneManifestDto = { + submissionsRequired: faker.number.int({ min: 1, max: 100 }), + requesterTitle: faker.lorem.words(3), + requesterDescription: faker.lorem.sentence(), + fundAmount: faker.number.float({ min: 1, max: 1000 }), + requestType: FortuneJobType.FORTUNE, + qualifications: [faker.lorem.word(), faker.lorem.word()], + }; + mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( + mockManifest, + ); + await expect( + manifestService.downloadManifest(mockManifestUrl, mockRequestType), + ).rejects.toThrow( + new ControlledError( + ErrorJob.ManifestValidationFailed, + HttpStatus.NOT_FOUND, + ), + ); + }); + }); +}); diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts new file mode 100644 index 0000000000..0a91fa38d2 --- /dev/null +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts @@ -0,0 +1,702 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + ChainId, + Encryption, + KVStoreUtils, + StorageParams, +} from '@human-protocol/sdk'; +import { + HttpStatus, + Injectable, + Logger, + ValidationError, +} from '@nestjs/common'; +import { validate } from 'class-validator'; +import { ethers } from 'ethers'; +import { v4 as uuidv4 } from 'uuid'; +import { AuthConfigService } from '../../common/config/auth-config.service'; +import { CvatConfigService } from '../../common/config/cvat-config.service'; +import { PGPConfigService } from '../../common/config/pgp-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { + HCAPTCHA_BOUNDING_BOX_MAX_POINTS, + HCAPTCHA_BOUNDING_BOX_MIN_POINTS, + HCAPTCHA_IMMO_MAX_LENGTH, + HCAPTCHA_IMMO_MIN_LENGTH, + HCAPTCHA_LANDMARK_MAX_POINTS, + HCAPTCHA_LANDMARK_MIN_POINTS, + HCAPTCHA_MAX_SHAPES_PER_IMAGE, + HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, + HCAPTCHA_MIN_SHAPES_PER_IMAGE, + HCAPTCHA_NOT_PRESENTED_LABEL, + HCAPTCHA_ORACLE_STAKE, + HCAPTCHA_POLYGON_MAX_POINTS, + HCAPTCHA_POLYGON_MIN_POINTS, +} from '../../common/constants'; +import { ErrorJob } from '../../common/constants/errors'; +import { + AudinoJobType, + CvatJobType, + FortuneJobType, + HCaptchaJobType, + JobCaptchaMode, + JobCaptchaRequestType, + JobCaptchaShapeType, + JobRequestType, +} from '../../common/enums/job'; +import { ControlledError } from '../../common/errors/controlled'; +import { + generateBucketUrl, + listObjectsInBucket, +} from '../../common/utils/storage'; +import { + CreateJob, + JobAudinoDto, + JobCaptchaAdvancedDto, + JobCaptchaDto, + JobCvatDto, +} from '../job/job.dto'; +import { + CvatAnnotationData, + CvatCalculateJobBounty, + CvatImageData, + GenerateUrls, +} from '../job/job.interface'; +import { StorageService } from '../storage/storage.service'; +import { Web3Service } from '../web3/web3.service'; +import { + AudinoManifestDto, + CvatManifestDto, + HCaptchaManifestDto, + FortuneManifestDto, + RestrictedAudience, + ManifestDto, +} from './manifest.dto'; + +@Injectable() +export class ManifestService { + public readonly logger = new Logger(ManifestService.name); + public readonly storageParams: StorageParams; + public readonly bucket: string; + + constructor( + private readonly web3Service: Web3Service, + private readonly authConfigService: AuthConfigService, + private readonly web3ConfigService: Web3ConfigService, + private readonly cvatConfigService: CvatConfigService, + private readonly pgpConfigService: PGPConfigService, + private readonly storageService: StorageService, + private readonly encryption: Encryption, + ) {} + + async createManifest( + dto: CreateJob, + requestType: JobRequestType, + fundAmount: number, + decimals: number, + ): Promise { + switch (requestType) { + case HCaptchaJobType.HCAPTCHA: + return this.createHCaptchaManifest(dto as JobCaptchaDto); + + case FortuneJobType.FORTUNE: + return { + ...dto, + requestType, + fundAmount, + }; + + case CvatJobType.IMAGE_POLYGONS: + case CvatJobType.IMAGE_BOXES: + case CvatJobType.IMAGE_POINTS: + case CvatJobType.IMAGE_BOXES_FROM_POINTS: + case CvatJobType.IMAGE_SKELETONS_FROM_BOXES: + return this.createCvatManifest( + dto as JobCvatDto, + requestType, + fundAmount, + decimals, + ); + + case AudinoJobType.AUDIO_TRANSCRIPTION: + return this.createAudinoManifest( + dto as JobAudinoDto, + requestType, + fundAmount, + ); + + default: + throw new ControlledError( + ErrorJob.InvalidRequestType, + HttpStatus.BAD_REQUEST, + ); + } + } + + private async getCvatElementsCount( + urls: GenerateUrls, + requestType: CvatJobType, + ): Promise { + let gt: any, gtEntries: number; + switch (requestType) { + case CvatJobType.IMAGE_POLYGONS: + case CvatJobType.IMAGE_BOXES: + case CvatJobType.IMAGE_POINTS: + const data = await listObjectsInBucket(urls.dataUrl); + if (!data || data.length === 0 || !data[0]) + throw new ControlledError( + ErrorJob.DatasetValidationFailed, + HttpStatus.BAD_REQUEST, + ); + gt = (await this.storageService.downloadJsonLikeData( + `${urls.gtUrl.protocol}//${urls.gtUrl.host}${urls.gtUrl.pathname}`, + )) as any; + if (!gt || !gt.images || gt.images.length === 0) + throw new ControlledError( + ErrorJob.GroundThuthValidationFailed, + HttpStatus.BAD_REQUEST, + ); + + await this.checkImageConsistency(gt.images, data); + + return data.length - gt.images.length; + + case CvatJobType.IMAGE_BOXES_FROM_POINTS: + const points = (await this.storageService.downloadJsonLikeData( + urls.pointsUrl!.href, + )) as any; + gt = (await this.storageService.downloadJsonLikeData( + urls.gtUrl.href, + )) as any; + + if (!gt || !gt.images || gt.images.length === 0) { + throw new ControlledError( + ErrorJob.GroundThuthValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + gtEntries = 0; + gt.images.forEach((gtImage: CvatImageData) => { + const { id } = points.images.find( + (dataImage: CvatImageData) => + dataImage.file_name === gtImage.file_name, + ); + + if (id) { + const matchingAnnotations = points.annotations.filter( + (dataAnnotation: CvatAnnotationData) => + dataAnnotation.image_id === id, + ); + gtEntries += matchingAnnotations.length; + } + }); + + return points.annotations.length - gtEntries; + + case CvatJobType.IMAGE_SKELETONS_FROM_BOXES: + const boxes = (await this.storageService.downloadJsonLikeData( + urls.boxesUrl!.href, + )) as any; + gt = (await this.storageService.downloadJsonLikeData( + urls.gtUrl.href, + )) as any; + + if (!gt || !gt.images || gt.images.length === 0) { + throw new ControlledError( + ErrorJob.GroundThuthValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + gtEntries = 0; + gt.images.forEach((gtImage: CvatImageData) => { + const { id } = boxes.images.find( + (dataImage: CvatImageData) => + dataImage.file_name === gtImage.file_name, + ); + + if (id) { + const matchingAnnotations = boxes.annotations.filter( + (dataAnnotation: CvatAnnotationData) => + dataAnnotation.image_id === id, + ); + gtEntries += matchingAnnotations.length; + } + }); + + return boxes.annotations.length - gtEntries; + + default: + throw new ControlledError( + ErrorJob.InvalidRequestType, + HttpStatus.BAD_REQUEST, + ); + } + } + + private async checkImageConsistency( + gtImages: any[], + dataFiles: string[], + ): Promise { + const gtFileNames = gtImages.map((image: any) => image.file_name); + const baseFileNames = dataFiles.map((fileName) => + fileName.split('/').pop(), + ); + const missingFileNames = gtFileNames.filter( + (fileName: any) => !baseFileNames.includes(fileName), + ); + + if (missingFileNames.length !== 0) { + throw new ControlledError( + ErrorJob.ImageConsistency, + HttpStatus.BAD_REQUEST, + ); + } + } + + private async calculateCvatJobBounty( + params: CvatCalculateJobBounty, + ): Promise { + const { requestType, fundAmount, urls, nodesTotal } = params; + + const elementsCount = await this.getCvatElementsCount(urls, requestType); + + let jobSize = Number(this.cvatConfigService.jobSize); + + if (requestType === CvatJobType.IMAGE_SKELETONS_FROM_BOXES) { + const jobSizeMultiplier = Number( + this.cvatConfigService.skeletonsJobSizeMultiplier, + ); + jobSize *= jobSizeMultiplier; + } + + let totalJobs: number; + + // For each skeleton node CVAT creates a separate project thus increasing the number of jobs + if (requestType === CvatJobType.IMAGE_SKELETONS_FROM_BOXES && nodesTotal) { + totalJobs = Math.ceil(elementsCount / jobSize) * nodesTotal; + } else { + totalJobs = Math.ceil(elementsCount / jobSize); + } + + const jobBounty = + ethers.parseUnits(fundAmount.toString(), params.decimals) / + BigInt(totalJobs); + + return ethers.formatUnits(jobBounty, params.decimals); + } + + private async createCvatManifest( + dto: JobCvatDto, + requestType: CvatJobType, + tokenFundAmount: number, + decimals: number, + ): Promise { + if ( + (requestType === CvatJobType.IMAGE_SKELETONS_FROM_BOXES && + !dto.data.boxes) || + (requestType === CvatJobType.IMAGE_BOXES_FROM_POINTS && !dto.data.points) + ) { + throw new ControlledError(ErrorJob.DataNotExist, HttpStatus.CONFLICT); + } + + const urls = { + dataUrl: generateBucketUrl(dto.data.dataset, requestType), + gtUrl: generateBucketUrl(dto.groundTruth, requestType), + boxesUrl: dto.data.boxes + ? generateBucketUrl(dto.data.boxes, requestType) + : undefined, + pointsUrl: dto.data.points + ? generateBucketUrl(dto.data.points, requestType) + : undefined, + }; + + const jobBounty = await this.calculateCvatJobBounty({ + requestType, + fundAmount: tokenFundAmount, + decimals, + urls, + nodesTotal: dto.labels[0]?.nodes?.length, + }); + + return { + data: { + data_url: urls.dataUrl.href, + ...(urls.pointsUrl && { + points_url: urls.pointsUrl?.href, + }), + ...(urls.boxesUrl && { + boxes_url: urls.boxesUrl?.href, + }), + }, + annotation: { + labels: dto.labels, + description: dto.requesterDescription, + user_guide: dto.userGuide, + type: requestType as CvatJobType, + job_size: this.cvatConfigService.jobSize, + ...(dto.qualifications && { + qualifications: dto.qualifications, + }), + }, + validation: { + min_quality: dto.minQuality, + val_size: this.cvatConfigService.valSize, + gt_url: urls.gtUrl.href, + }, + job_bounty: jobBounty, + }; + } + + private async createAudinoManifest( + dto: JobAudinoDto, + requestType: AudinoJobType, + tokenFundAmount: number, + ): Promise { + const totalSegments = Math.ceil( + (dto.audioDuration * 1000) / dto.segmentDuration, + ); + + const jobBounty = + ethers.parseUnits(tokenFundAmount.toString(), 'ether') / + BigInt(totalSegments); + + return { + annotation: { + description: dto.requesterDescription, + labels: dto.labels, + qualifications: dto.qualifications || [], + type: requestType, + user_guide: dto.userGuide, + segment_duration: dto.segmentDuration, + }, + data: { + data_url: generateBucketUrl(dto.data.dataset, requestType).href, + }, + job_bounty: ethers.formatEther(jobBounty), + validation: { + gt_url: generateBucketUrl(dto.groundTruth, requestType).href, + min_quality: dto.minQuality, + }, + }; + } + + private async createHCaptchaManifest( + jobDto: JobCaptchaDto, + ): Promise { + const jobType = jobDto.annotations.typeOfJob; + const dataUrl = generateBucketUrl(jobDto.data, HCaptchaJobType.HCAPTCHA); + const objectsInBucket = await listObjectsInBucket(dataUrl); + + const commonManifestProperties = { + job_mode: JobCaptchaMode.BATCH, + requester_accuracy_target: jobDto.accuracyTarget, + request_config: {}, + restricted_audience: this.buildHCaptchaRestrictedAudience( + jobDto.advanced, + ), + requester_max_repeats: jobDto.maxRequests, + requester_min_repeats: jobDto.minRequests, + requester_question: { en: jobDto.annotations.labelingPrompt }, + job_total_tasks: objectsInBucket.length, + task_bid_price: jobDto.annotations.taskBidPrice, + taskdata_uri: await this.generateAndUploadTaskData( + dataUrl.href, + objectsInBucket, + ), + public_results: true, + oracle_stake: HCAPTCHA_ORACLE_STAKE, + repo_uri: this.web3ConfigService.hCaptchaReputationOracleURI, + ro_uri: this.web3ConfigService.hCaptchaRecordingOracleURI, + ...(jobDto.qualifications && { + qualifications: jobDto.qualifications, + }), + }; + + let groundTruthsData; + if (jobDto.annotations.groundTruths) { + groundTruthsData = await this.storageService.downloadJsonLikeData( + jobDto.annotations.groundTruths, + ); + } + + switch (jobType) { + case JobCaptchaShapeType.COMPARISON: + return { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.IMAGE_LABEL_BINARY, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: {}, + requester_question_example: jobDto.annotations.exampleImages || [], + }; + + case JobCaptchaShapeType.CATEGORIZATION: + return { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.IMAGE_LABEL_MULTIPLE_CHOICE, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: + this.buildHCaptchaRestrictedAnswerSet(groundTruthsData), + }; + + case JobCaptchaShapeType.POLYGON: + if (!jobDto.annotations.label) { + throw new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + const polygonManifest = { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + request_config: { + shape_type: JobCaptchaShapeType.POLYGON, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_POLYGON_MIN_POINTS, + max_points: HCAPTCHA_POLYGON_MAX_POINTS, + minimum_selection_area_per_shape: + HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, + }, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + requester_question_example: jobDto.annotations.exampleImages || [], + }; + + return polygonManifest; + + case JobCaptchaShapeType.POINT: + if (!jobDto.annotations.label) { + throw new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + const pointManifest = { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + request_config: { + shape_type: jobType, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_LANDMARK_MIN_POINTS, + max_points: HCAPTCHA_LANDMARK_MAX_POINTS, + }, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + requester_question_example: jobDto.annotations.exampleImages || [], + }; + + return pointManifest; + case JobCaptchaShapeType.BOUNDING_BOX: + if (!jobDto.annotations.label) { + throw new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + const boundingBoxManifest = { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, + request_config: { + shape_type: jobType, + min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, + max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, + min_points: HCAPTCHA_BOUNDING_BOX_MIN_POINTS, + max_points: HCAPTCHA_BOUNDING_BOX_MAX_POINTS, + }, + groundtruth_uri: jobDto.annotations.groundTruths, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + requester_question_example: jobDto.annotations.exampleImages || [], + }; + + return boundingBoxManifest; + case JobCaptchaShapeType.IMMO: + if (!jobDto.annotations.label) { + throw new ControlledError( + ErrorJob.JobParamsValidationFailed, + HttpStatus.BAD_REQUEST, + ); + } + + const immoManifest = { + ...commonManifestProperties, + request_type: JobCaptchaRequestType.TEXT_FREEE_NTRY, + request_config: { + multiple_choice_max_choices: 1, + multiple_choice_min_choices: 1, + overlap_threshold: null, + answer_type: 'str', + max_length: HCAPTCHA_IMMO_MAX_LENGTH, + min_length: HCAPTCHA_IMMO_MIN_LENGTH, + }, + requester_restricted_answer_set: { + [jobDto.annotations.label!]: { en: jobDto.annotations.label }, + }, + taskdata: [], + }; + + return immoManifest; + + default: + throw new ControlledError( + ErrorJob.HCaptchaInvalidJobType, + HttpStatus.CONFLICT, + ); + } + } + + private buildHCaptchaRestrictedAudience(advanced: JobCaptchaAdvancedDto) { + const restrictedAudience: RestrictedAudience = {}; + + restrictedAudience.sitekey = [ + { + [this.authConfigService.hCaptchaSiteKey]: { + score: 1, + }, + }, + ]; + + if (advanced.workerLanguage) { + restrictedAudience.lang = [{ [advanced.workerLanguage]: { score: 1 } }]; + } + + if (advanced.workerLocation) { + restrictedAudience.country = [ + { [advanced.workerLocation]: { score: 1 } }, + ]; + } + + if (advanced.targetBrowser) { + restrictedAudience.browser = [{ [advanced.targetBrowser]: { score: 1 } }]; + } + + return restrictedAudience; + } + + private buildHCaptchaRestrictedAnswerSet(groundTruthsData: any) { + const maxElements = 3; + const outputObject: any = {}; + + let elementCount = 0; + + for (const key of Object.keys(groundTruthsData)) { + if (elementCount >= maxElements) { + break; + } + + const value = groundTruthsData[key][0][0]; + outputObject[value] = { en: value, answer_example_uri: key }; + elementCount++; + } + + // Default case + outputObject['0'] = { en: HCAPTCHA_NOT_PRESENTED_LABEL }; + + return outputObject; + } + + private async generateAndUploadTaskData( + dataUrl: string, + objectNames: string[], + ) { + const data = objectNames.map((objectName) => { + return { + datapoint_uri: `${dataUrl}/${objectName}`, + datapoint_hash: 'undefined-hash', + task_key: uuidv4(), + }; + }); + + const { url } = await this.storageService.uploadJsonLikeData(data); + + return url; + } + + async uploadManifest( + chainId: ChainId, + data: any, + oracleAddresses: string[], + ): Promise { + let manifestFile = data; + + if (this.pgpConfigService.encrypt) { + const signer = this.web3Service.getSigner(chainId); + const publicKeys: string[] = [ + await KVStoreUtils.getPublicKey(chainId, signer.address), + ]; + + for (const address of oracleAddresses) { + const publicKey = await KVStoreUtils.getPublicKey(chainId, address); + if (publicKey) publicKeys.push(publicKey); + } + const encryptedManifest = await this.encryption.signAndEncrypt( + JSON.stringify(data), + publicKeys, + ); + manifestFile = encryptedManifest; + } + + return this.storageService.uploadJsonLikeData(manifestFile); + } + + private async validateManifest( + requestType: JobRequestType, + manifest: + | FortuneManifestDto + | CvatManifestDto + | HCaptchaManifestDto + | AudinoManifestDto, + ): Promise { + let dtoCheck; + + if (requestType === FortuneJobType.FORTUNE) { + dtoCheck = new FortuneManifestDto(); + } else if (requestType === HCaptchaJobType.HCAPTCHA) { + return; + dtoCheck = new HCaptchaManifestDto(); + } else if (requestType === AudinoJobType.AUDIO_TRANSCRIPTION) { + dtoCheck = new AudinoManifestDto(); + } else { + dtoCheck = new CvatManifestDto(); + } + + Object.assign(dtoCheck, manifest); + + const validationErrors: ValidationError[] = await validate(dtoCheck); + if (validationErrors.length > 0) { + throw new ControlledError( + ErrorJob.ManifestValidationFailed, + HttpStatus.NOT_FOUND, + ); + } + } + + async downloadManifest( + manifestUrl: string, + requestType: JobRequestType, + ): Promise< + | FortuneManifestDto + | CvatManifestDto + | HCaptchaManifestDto + | AudinoManifestDto + > { + const manifest = (await this.storageService.downloadJsonLikeData( + manifestUrl, + )) as ManifestDto; + + await this.validateManifest(requestType, manifest); + + return manifest; + } +} diff --git a/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts index 50fe915b0e..b08b0a94b1 100644 --- a/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/routing-protocol/routing-protocol.service.spec.ts @@ -8,7 +8,7 @@ import { NetworkConfigService } from '../../common/config/network-config.service import { Web3Service } from '../web3/web3.service'; import { ConfigService } from '@nestjs/config'; import { ControlledError } from '../../common/errors/controlled'; -import { JobRequestType } from '../../common/enums/job'; +import { FortuneJobType } from '../../common/enums/job'; import { ErrorRoutingProtocol } from '../../common/constants/errors'; import { HttpStatus } from '@nestjs/common'; import { hashString } from '../../common/utils'; @@ -354,7 +354,7 @@ describe('RoutingProtocolService', () => { const result = await routingProtocolService.selectOracles( ChainId.POLYGON_AMOY, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, ); expect(result.reputationOracle).toBeDefined(); expect(result.exchangeOracle).toBe('0xExchangeOracle1'); @@ -366,7 +366,7 @@ describe('RoutingProtocolService', () => { const result = await routingProtocolService.selectOracles( ChainId.POLYGON_AMOY, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, ); expect(result.exchangeOracle).toBe(''); expect(result.recordingOracle).toBe(''); @@ -395,7 +395,7 @@ describe('RoutingProtocolService', () => { await expect( routingProtocolService.validateOracles( chainId, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, reputationOracle, exchangeOracle, recordingOracle, @@ -418,7 +418,7 @@ describe('RoutingProtocolService', () => { await expect( routingProtocolService.validateOracles( chainId, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, invalidReputationOracle, ), ).rejects.toThrow( @@ -449,7 +449,7 @@ describe('RoutingProtocolService', () => { await expect( routingProtocolService.validateOracles( chainId, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, reputationOracle, 'invalidExchangeOracle', ), @@ -481,7 +481,7 @@ describe('RoutingProtocolService', () => { await expect( routingProtocolService.validateOracles( chainId, - JobRequestType.FORTUNE, + FortuneJobType.FORTUNE, reputationOracle, undefined, 'invalidRecordingOracle', diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts index 83bc6db03a..5d70b8157d 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.service.spec.ts @@ -29,7 +29,7 @@ import { ServerConfigService } from '../../common/config/server-config.service'; import { Web3ConfigService } from '../../common/config/web3-config.service'; import { ControlledError } from '../../common/errors/controlled'; import { JobRepository } from '../job/job.repository'; -import { JobRequestType } from '../../common/enums/job'; +import { FortuneJobType } from '../../common/enums/job'; import { faker } from '@faker-js/faker/.'; jest.mock('@human-protocol/sdk', () => ({ @@ -352,7 +352,7 @@ describe('WebhookService', () => { jest .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') - .mockResolvedValueOnce({ requestType: JobRequestType.FORTUNE } as any); + .mockResolvedValueOnce({ requestType: FortuneJobType.FORTUNE } as any); jest .spyOn(jobService, 'getOracleType') .mockReturnValueOnce(OracleType.FORTUNE); @@ -399,7 +399,7 @@ describe('WebhookService', () => { jest .spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress') - .mockResolvedValueOnce({ requestType: JobRequestType.FORTUNE } as any); + .mockResolvedValueOnce({ requestType: FortuneJobType.FORTUNE } as any); jest .spyOn(jobService, 'getOracleType') .mockReturnValueOnce(OracleType.FORTUNE); diff --git a/packages/apps/job-launcher/server/test/constants.ts b/packages/apps/job-launcher/server/test/constants.ts index d0e5d97a97..1734f85660 100644 --- a/packages/apps/job-launcher/server/test/constants.ts +++ b/packages/apps/job-launcher/server/test/constants.ts @@ -1,12 +1,11 @@ +import { FortuneJobType } from '../src/common/enums/job'; import { AWSRegions, StorageProviders } from '../src/common/enums/storage'; -import { JobRequestType } from '../src/common/enums/job'; +import { Web3Env } from '../src/common/enums/web3'; +import { CvatDataDto, StorageDataDto } from '../src/modules/job/job.dto'; import { FortuneManifestDto, - StorageDataDto, - CvatDataDto, Label, -} from '../src/modules/job/job.dto'; -import { Web3Env } from '../src/common/enums/web3'; +} from '../src/modules/manifest/manifest.dto'; export const MOCK_REQUESTER_TITLE = 'Mock job title'; export const MOCK_REQUESTER_DESCRIPTION = 'Mock job description'; @@ -85,7 +84,7 @@ export const MOCK_MANIFEST: FortuneManifestDto = { requesterTitle: 'Fortune', requesterDescription: 'Some desc', fundAmount: 10, - requestType: JobRequestType.FORTUNE, + requestType: FortuneJobType.FORTUNE, }; export const MOCK_ENCRYPTED_MANIFEST = 'encryptedManifest'; @@ -248,9 +247,6 @@ export const mockConfig: any = { PGP_PRIVATE_KEY: MOCK_PGP_PRIVATE_KEY, PGP_PASSPHRASE: MOCK_PGP_PASSPHRASE, REPUTATION_ORACLE_ADDRESS: MOCK_ADDRESS, - CVAT_EXCHANGE_ORACLE_ADDRESS: MOCK_ADDRESS, - FORTUNE_EXCHANGE_ORACLE_ADDRESS: MOCK_ADDRESS, - FORTUNE_RECORDING_ORACLE_ADDRESS: MOCK_ADDRESS, WEB3_PRIVATE_KEY: MOCK_PRIVATE_KEY, STRIPE_SECRET_KEY: MOCK_STRIPE_SECRET_KEY, STRIPE_API_VERSION: MOCK_STRIPE_API_VERSION, @@ -266,7 +262,6 @@ export const mockConfig: any = { CVAT_MAX_TIME: MOCK_CVAT_MAX_TIME, CVAT_VAL_SIZE: MOCK_CVAT_VAL_SIZE, CVAT_SKELETONS_JOB_SIZE_MULTIPLIER: MOCK_CVAT_SKELETONS_JOB_SIZE_MULTIPLIER, - CVAT_RECORDING_ORACLE_ADDRESS: MOCK_ADDRESS, MAX_RETRY_COUNT: MOCK_MAX_RETRY_COUNT, RPC_URL_POLYGON_AMOY: MOCK_WEB3_RPC_URL, SENDGRID_API_KEY: MOCK_SENDGRID_API_KEY, diff --git a/packages/apps/reputation-oracle/server/jest.config.ts b/packages/apps/reputation-oracle/server/jest.config.ts index 92a01987a8..e7de595d81 100644 --- a/packages/apps/reputation-oracle/server/jest.config.ts +++ b/packages/apps/reputation-oracle/server/jest.config.ts @@ -1,17 +1,20 @@ +import { createDefaultPreset } from 'ts-jest'; + process.env['GIT_HASH'] = 'test_value_hardcoded_in_jest_config'; +const jestTsPreset = createDefaultPreset({}); + module.exports = { + ...jestTsPreset, coverageDirectory: '../coverage', collectCoverageFrom: ['**/*.(t|j)s'], moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', testEnvironment: 'node', testRegex: '.*\\.spec\\.ts$', - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, moduleNameMapper: { '^uuid$': require.resolve('uuid'), '^typeorm$': require.resolve('typeorm'), }, + clearMocks: true, }; diff --git a/packages/apps/reputation-oracle/server/src/app.module.ts b/packages/apps/reputation-oracle/server/src/app.module.ts index 02f2541927..eef8a16222 100644 --- a/packages/apps/reputation-oracle/server/src/app.module.ts +++ b/packages/apps/reputation-oracle/server/src/app.module.ts @@ -5,31 +5,30 @@ import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; -import { envValidator } from './config'; -import { EnvConfigModule } from './config/config.module'; - -import { DatabaseModule } from './database/database.module'; - +import { AppController } from './app.controller'; import { JwtAuthGuard } from './common/guards'; import { ExceptionFilter } from './common/filters/exception.filter'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; import { HttpValidationPipe } from './common/pipes'; +import { envValidator, EnvConfigModule } from './config'; +import { DatabaseModule } from './database'; -import { HealthModule } from './modules/health/health.module'; -import { ReputationModule } from './modules/reputation/reputation.module'; -import { AuthModule } from './modules/auth/auth.module'; -import { KycModule } from './modules/kyc/kyc.module'; -import { CronJobModule } from './modules/cron-job/cron-job.module'; -import { QualificationModule } from './modules/qualification/qualification.module'; -import { EscrowCompletionModule } from './modules/escrow-completion/escrow-completion.module'; -import { IncomingWebhookModule } from './modules/webhook/webhook-incoming.module'; -import { OutgoingWebhookModule } from './modules/webhook/webhook-outgoing.module'; +import { AbuseModule } from './modules/abuse'; +import { AuthModule } from './modules/auth'; +import { CronJobModule } from './modules/cron-job'; +import { EscrowCompletionModule } from './modules/escrow-completion'; +import { HealthModule } from './modules/health'; +import { + IncomingWebhookModule, + OutgoingWebhookModule, +} from './modules/webhook'; +import { KycModule } from './modules/kyc'; +import { NDAModule } from './modules/nda'; +import { QualificationModule } from './modules/qualification'; +import { ReputationModule } from './modules/reputation'; import { UserModule } from './modules/user'; -import { NDAModule } from './modules/nda/nda.module'; import Environment from './utils/environment'; -import { AppController } from './app.controller'; -import { AbuseModule } from './modules/abuse/abuse.module'; @Module({ providers: [ diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index 469e3b32e8..a2e57e2cc9 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -7,8 +7,6 @@ export const CVAT_VALIDATION_META_FILENAME = 'validation_meta.json'; export const AUDINO_RESULTS_ANNOTATIONS_FILENAME = 'resulting_annotations.zip'; export const AUDINO_VALIDATION_META_FILENAME = 'validation_meta.json'; -export const DEFAULT_BULK_PAYOUT_TX_ID = 1; - export const HEADER_SIGNATURE_KEY = 'human-signature'; export const RESEND_EMAIL_VERIFICATION_PATH = diff --git a/packages/apps/reputation-oracle/server/src/common/enums/database.ts b/packages/apps/reputation-oracle/server/src/common/enums/database.ts deleted file mode 100644 index 622d737e8f..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/enums/database.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum PostgresErrorCodes { - Duplicated = '23505', - NumericFieldOverflow = '22003', -} diff --git a/packages/apps/reputation-oracle/server/src/common/enums/hcaptcha.ts b/packages/apps/reputation-oracle/server/src/common/enums/hcaptcha.ts deleted file mode 100644 index a82f9df468..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/enums/hcaptcha.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum TokenType { - AUTH = 'auth', - EXCHANGE = 'exchange', -} diff --git a/packages/apps/reputation-oracle/server/src/common/enums/index.ts b/packages/apps/reputation-oracle/server/src/common/enums/index.ts index f75da941d6..45fcd4ef6a 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/enums/index.ts @@ -1,5 +1,4 @@ -export * from './job'; -export * from './webhook'; export * from './collection'; -export * from './hcaptcha'; export * from './http'; +export * from './manifest'; +export * from './web3'; diff --git a/packages/apps/reputation-oracle/server/src/common/enums/job.ts b/packages/apps/reputation-oracle/server/src/common/enums/manifest.ts similarity index 71% rename from packages/apps/reputation-oracle/server/src/common/enums/job.ts rename to packages/apps/reputation-oracle/server/src/common/enums/manifest.ts index 635a565a6d..3a2c0a02b9 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/job.ts +++ b/packages/apps/reputation-oracle/server/src/common/enums/manifest.ts @@ -1,14 +1,15 @@ -export enum JobRequestType { +export enum FortuneJobType { + FORTUNE = 'fortune', +} + +export enum CvatJobType { IMAGE_BOXES = 'image_boxes', IMAGE_POINTS = 'image_points', IMAGE_BOXES_FROM_POINTS = 'image_boxes_from_points', IMAGE_SKELETONS_FROM_BOXES = 'image_skeletons_from_boxes', - FORTUNE = 'fortune', IMAGE_POLYGONS = 'image_polygons', - AUDIO_TRANSCRIPTION = 'audio_transcription', } -export enum SolutionError { - Duplicated = 'duplicated', - CurseWord = 'curse_word', +export enum AudinoJobType { + AUDIO_TRANSCRIPTION = 'audio_transcription', } diff --git a/packages/apps/reputation-oracle/server/src/common/errors/database.ts b/packages/apps/reputation-oracle/server/src/common/errors/database.ts deleted file mode 100644 index 653a8285c4..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/errors/database.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { QueryFailedError } from 'typeorm'; -import { PostgresErrorCodes } from '../enums/database'; -import { BaseError } from './base'; - -export class DatabaseError extends BaseError {} - -export function handleQueryFailedError(error: QueryFailedError): DatabaseError { - let message: string; - - switch ((error.driverError as any).code) { - case PostgresErrorCodes.Duplicated: - message = - (error.driverError as any).detail + (error.driverError as any).code; - break; - case PostgresErrorCodes.NumericFieldOverflow: - message = 'Incorrect amount'; - break; - default: - message = error.message; - break; - } - - return new DatabaseError(message, error); -} - -export function isDuplicatedError(error: unknown): boolean { - return ( - error instanceof DatabaseError && - error.message.includes(PostgresErrorCodes.Duplicated) - ); -} diff --git a/packages/apps/reputation-oracle/server/src/common/errors/manifest.ts b/packages/apps/reputation-oracle/server/src/common/errors/manifest.ts deleted file mode 100644 index 3ae3256d42..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/errors/manifest.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BaseError } from './base'; - -class ManifestError extends BaseError {} - -export class MissingManifestUrlError extends ManifestError { - constructor(public readonly escrowAddress: string) { - super('Manifest url is missing for escrow'); - } -} - -export class UnsupportedManifestTypeError extends ManifestError { - constructor(public readonly manifestType: unknown) { - super('Unsupported manifest type'); - } -} diff --git a/packages/apps/reputation-oracle/server/src/common/filters/exception.filter.ts b/packages/apps/reputation-oracle/server/src/common/filters/exception.filter.ts index 26c54c859b..d7d051436d 100644 --- a/packages/apps/reputation-oracle/server/src/common/filters/exception.filter.ts +++ b/packages/apps/reputation-oracle/server/src/common/filters/exception.filter.ts @@ -6,7 +6,7 @@ import { HttpException, } from '@nestjs/common'; import { Request, Response } from 'express'; -import { DatabaseError } from '../errors/database'; +import { DatabaseError, isDuplicatedError } from '../../database'; import logger from '../../logger'; import { transformKeysFromCamelToSnake } from '../../utils/case-converters'; @@ -25,9 +25,10 @@ export class ExceptionFilter implements IExceptionFilter { }; if (exception instanceof DatabaseError) { - status = HttpStatus.UNPROCESSABLE_ENTITY; - responseBody.message = exception.message; - + if (isDuplicatedError(exception)) { + status = HttpStatus.UNPROCESSABLE_ENTITY; + responseBody.message = 'Unprocessable entity'; + } this.logger.error('Database error', exception); } else if (exception instanceof HttpException) { status = exception.getStatus(); diff --git a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts index 9134a66860..d437e9cff8 100644 --- a/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts +++ b/packages/apps/reputation-oracle/server/src/common/guards/signature.auth.spec.ts @@ -1,12 +1,10 @@ jest.mock('@human-protocol/sdk'); +import { faker } from '@faker-js/faker'; import { EscrowUtils } from '@human-protocol/sdk'; import { ExecutionContext, HttpException, HttpStatus } from '@nestjs/common'; -import { - generateContractAddress, - generateEthWallet, -} from '../../../test/fixtures/web3'; +import { generateEthWallet } from '../../../test/fixtures/web3'; import { createExecutionContextMock, ExecutionContextMock, @@ -43,7 +41,7 @@ describe('SignatureAuthGuard', () => { executionContextMock = createExecutionContextMock(); body = { chain_id: generateTestnetChainId(), - escrow_address: generateContractAddress(), + escrow_address: faker.finance.ethereumAddress(), }; }); diff --git a/packages/apps/reputation-oracle/server/src/common/interfaces/job-result.ts b/packages/apps/reputation-oracle/server/src/common/interfaces/job-result.ts deleted file mode 100644 index 747744d3a6..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/interfaces/job-result.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SolutionError } from '../../common/enums'; - -export interface FortuneFinalResult { - workerAddress: string; - solution: string; - error?: SolutionError; -} - -interface CvatAnnotationMetaJobs { - job_id: number; - final_result_id: number; -} - -export interface CvatAnnotationMetaResults { - id: number; - job_id: number; - annotator_wallet_address: string; - annotation_quality: number; -} - -export interface CvatAnnotationMeta { - jobs: CvatAnnotationMetaJobs[]; - results: CvatAnnotationMetaResults[]; -} - -interface AudinoAnnotationMetaJob { - job_id: number; - final_result_id: number; -} - -export interface AudinoAnnotationMetaResult { - id: number; - job_id: number; - annotator_wallet_address: string; - annotation_quality: number; -} - -export interface AudinoAnnotationMeta { - jobs: AudinoAnnotationMetaJob[]; - results: AudinoAnnotationMetaResult[]; -} diff --git a/packages/apps/reputation-oracle/server/src/common/interfaces/manifest.ts b/packages/apps/reputation-oracle/server/src/common/interfaces/manifest.ts deleted file mode 100644 index 5ac5bbb4e8..0000000000 --- a/packages/apps/reputation-oracle/server/src/common/interfaces/manifest.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { JobRequestType } from '../enums'; - -interface CvatData { - data_url: string; -} - -interface Label { - name: string; -} - -interface Annotation { - labels: Label[]; - description: string; - type: JobRequestType; - job_size: number; - max_time: number; -} - -interface Validation { - min_quality: number; - val_size: number; - gt_url: string; -} - -export interface CvatManifest { - data: CvatData; - annotation: Annotation; - validation: Validation; - job_bounty: string; -} - -export interface FortuneManifest { - submissionsRequired: number; - requesterTitle: string; - requesterDescription: string; - fundAmount: number; - requestType: JobRequestType; -} - -interface AudinoData { - data_url: string; -} - -interface AudinoLabel { - name: string; -} - -interface AudinoValidation { - gt_url: string; - min_quality: number; -} - -interface AudinoAnnotation { - type: JobRequestType; - labels: AudinoLabel[]; - description: string; - segment_duration: number; -} - -export interface AudinoManifest { - data: AudinoData; - annotation: AudinoAnnotation; - job_bounty: string; - validation: AudinoValidation; -} - -export type JobManifest = FortuneManifest | CvatManifest | AudinoManifest; diff --git a/packages/apps/reputation-oracle/server/src/common/types/index.ts b/packages/apps/reputation-oracle/server/src/common/types/index.ts new file mode 100644 index 0000000000..da79a5f683 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/types/index.ts @@ -0,0 +1,3 @@ +export * from './job-result'; +export * from './manifest'; +export * from './request'; diff --git a/packages/apps/reputation-oracle/server/src/common/types/job-result.ts b/packages/apps/reputation-oracle/server/src/common/types/job-result.ts new file mode 100644 index 0000000000..2d9338ce89 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/types/job-result.ts @@ -0,0 +1,39 @@ +export type FortuneFinalResult = { + workerAddress: string; + solution: string; + error?: 'duplicated' | 'curse_word'; +}; + +type CvatAnnotationMetaJob = { + job_id: number; + final_result_id: number; +}; + +export type CvatAnnotationMetaResult = { + id: number; + job_id: number; + annotator_wallet_address: string; + annotation_quality: number; +}; + +export type CvatAnnotationMeta = { + jobs: CvatAnnotationMetaJob[]; + results: CvatAnnotationMetaResult[]; +}; + +type AudinoAnnotationMetaJob = { + job_id: number; + final_result_id: number; +}; + +export type AudinoAnnotationMetaResult = { + id: number; + job_id: number; + annotator_wallet_address: string; + annotation_quality: number; +}; + +export type AudinoAnnotationMeta = { + jobs: AudinoAnnotationMetaJob[]; + results: AudinoAnnotationMetaResult[]; +}; diff --git a/packages/apps/reputation-oracle/server/src/common/types/manifest.ts b/packages/apps/reputation-oracle/server/src/common/types/manifest.ts new file mode 100644 index 0000000000..11f12b4d8e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/common/types/manifest.ts @@ -0,0 +1,31 @@ +import { AudinoJobType, CvatJobType, FortuneJobType } from '../enums'; + +export type FortuneManifest = { + submissionsRequired: number; + fundAmount: number; + requestType: FortuneJobType; +}; + +export type CvatManifest = { + annotation: { + type: CvatJobType; + }; + validation: { + min_quality: number; + }; + job_bounty: string; +}; + +export type AudinoManifest = { + annotation: { + type: AudinoJobType; + }; + validation: { + min_quality: number; + }; + job_bounty: string; +}; + +export type JobManifest = FortuneManifest | CvatManifest | AudinoManifest; + +export type JobRequestType = FortuneJobType | CvatJobType | AudinoJobType; diff --git a/packages/apps/reputation-oracle/server/src/common/interfaces/request.ts b/packages/apps/reputation-oracle/server/src/common/types/request.ts similarity index 68% rename from packages/apps/reputation-oracle/server/src/common/interfaces/request.ts rename to packages/apps/reputation-oracle/server/src/common/types/request.ts index 15b2a7ed78..c63d60956d 100644 --- a/packages/apps/reputation-oracle/server/src/common/interfaces/request.ts +++ b/packages/apps/reputation-oracle/server/src/common/types/request.ts @@ -1,3 +1,3 @@ export interface RequestWithUser extends Request { - user: any; + user: { id: number }; } diff --git a/packages/apps/reputation-oracle/server/src/config/auth-config.service.ts b/packages/apps/reputation-oracle/server/src/config/auth-config.service.ts index 35997fc93b..a570882d4c 100644 --- a/packages/apps/reputation-oracle/server/src/config/auth-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/auth-config.service.ts @@ -10,7 +10,7 @@ export class AuthConfigService { * Required */ get jwtPrivateKey(): string { - return this.configService.getOrThrow('JWT_PRIVATE_KEY'); + return this.configService.getOrThrow('JWT_PRIVATE_KEY'); } /** @@ -18,7 +18,7 @@ export class AuthConfigService { * Required */ get jwtPublicKey(): string { - return this.configService.getOrThrow('JWT_PUBLIC_KEY'); + return this.configService.getOrThrow('JWT_PUBLIC_KEY'); } /** @@ -26,7 +26,7 @@ export class AuthConfigService { * Default: 600 */ get accessTokenExpiresIn(): number { - return +this.configService.get('JWT_ACCESS_TOKEN_EXPIRES_IN', 600); + return Number(this.configService.get('JWT_ACCESS_TOKEN_EXPIRES_IN')) || 600; } /** @@ -34,7 +34,9 @@ export class AuthConfigService { * Default: 3600000 */ get refreshTokenExpiresIn(): number { - return +this.configService.get('JWT_REFRESH_TOKEN_EXPIRES_IN', 3600) * 1000; + const configValueSeconds = + Number(this.configService.get('JWT_REFRESH_TOKEN_EXPIRES_IN')) || 3600; + return configValueSeconds * 1000; } /** @@ -42,9 +44,9 @@ export class AuthConfigService { * Default: 86400000 */ get verifyEmailTokenExpiresIn(): number { - return ( - +this.configService.get('VERIFY_EMAIL_TOKEN_EXPIRES_IN', 86400) * 1000 - ); + const configValueSeconds = + Number(this.configService.get('VERIFY_EMAIL_TOKEN_EXPIRES_IN')) || 86400; + return configValueSeconds * 1000; } /** @@ -52,15 +54,16 @@ export class AuthConfigService { * Default: 86400000 */ get forgotPasswordExpiresIn(): number { - return ( - +this.configService.get('FORGOT_PASSWORD_TOKEN_EXPIRES_IN', 86400) * 1000 - ); + const configValueSeconds = + Number(this.configService.get('FORGOT_PASSWORD_TOKEN_EXPIRES_IN')) || + 86400; + return configValueSeconds * 1000; } /** * Human APP email. */ get humanAppEmail(): string { - return this.configService.getOrThrow('HUMAN_APP_EMAIL'); + return this.configService.getOrThrow('HUMAN_APP_EMAIL'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/database-config.service.ts b/packages/apps/reputation-oracle/server/src/config/database-config.service.ts index a0174c0c49..d08323515b 100644 --- a/packages/apps/reputation-oracle/server/src/config/database-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/database-config.service.ts @@ -9,7 +9,7 @@ export class DatabaseConfigService { * The URL for connecting to the PostgreSQL database. */ get url(): string | undefined { - return this.configService.get('POSTGRES_URL'); + return this.configService.get('POSTGRES_URL'); } /** @@ -17,7 +17,7 @@ export class DatabaseConfigService { * Default: '127.0.0.1' */ get host(): string { - return this.configService.get('POSTGRES_HOST', '127.0.0.1'); + return this.configService.get('POSTGRES_HOST', '127.0.0.1'); } /** @@ -25,7 +25,7 @@ export class DatabaseConfigService { * Default: 5432 */ get port(): number { - return +this.configService.get('POSTGRES_PORT', 5432); + return Number(this.configService.get('POSTGRES_PORT')) || 5432; } /** @@ -33,7 +33,7 @@ export class DatabaseConfigService { * Default: 'operator' */ get user(): string { - return this.configService.get('POSTGRES_USER', 'operator'); + return this.configService.get('POSTGRES_USER', 'operator'); } /** @@ -41,7 +41,7 @@ export class DatabaseConfigService { * Default: 'qwerty' */ get password(): string { - return this.configService.get('POSTGRES_PASSWORD', 'qwerty'); + return this.configService.get('POSTGRES_PASSWORD', 'qwerty'); } /** @@ -49,10 +49,7 @@ export class DatabaseConfigService { * Default: 'reputation-oracle' */ get database(): string { - return this.configService.get( - 'POSTGRES_DATABASE', - 'reputation-oracle', - ); + return this.configService.get('POSTGRES_DATABASE', 'reputation-oracle'); } /** @@ -60,7 +57,7 @@ export class DatabaseConfigService { * Default: false */ get ssl(): boolean { - return this.configService.get('POSTGRES_SSL', 'false') === 'true'; + return this.configService.get('POSTGRES_SSL', 'false') === 'true'; } /** @@ -68,9 +65,6 @@ export class DatabaseConfigService { * Default: 'log,info,warn,error' */ get logging(): string { - return this.configService.get( - 'POSTGRES_LOGGING', - 'log,info,warn,error', - ); + return this.configService.get('POSTGRES_LOGGING', 'log,info,warn,error'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/email-config.service.ts b/packages/apps/reputation-oracle/server/src/config/email-config.service.ts index 44c04c84ac..7c248e5a16 100644 --- a/packages/apps/reputation-oracle/server/src/config/email-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/email-config.service.ts @@ -11,7 +11,7 @@ export class EmailConfigService { * Default: 'disabled' */ get apiKey(): string { - return this.configService.get('SENDGRID_API_KEY', 'disabled'); + return this.configService.get('SENDGRID_API_KEY', 'disabled'); } /** @@ -19,10 +19,7 @@ export class EmailConfigService { * Default: 'app@humanprotocol.org' */ get from(): string { - return this.configService.get( - 'EMAIL_FROM', - 'app@humanprotocol.org', - ); + return this.configService.get('EMAIL_FROM', 'app@humanprotocol.org'); } /** @@ -30,6 +27,6 @@ export class EmailConfigService { * Default: 'Human Protocol' */ get fromName(): string { - return this.configService.get('EMAIL_FROM_NAME', 'Human Protocol'); + return this.configService.get('EMAIL_FROM_NAME', 'Human Protocol'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/hcaptcha-config.service.ts b/packages/apps/reputation-oracle/server/src/config/hcaptcha-config.service.ts index 44ac92dc4d..426b2d6327 100644 --- a/packages/apps/reputation-oracle/server/src/config/hcaptcha-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/hcaptcha-config.service.ts @@ -10,7 +10,7 @@ export class HCaptchaConfigService { * Required */ get siteKey(): string { - return this.configService.getOrThrow('HCAPTCHA_SITE_KEY'); + return this.configService.getOrThrow('HCAPTCHA_SITE_KEY'); } /** @@ -18,7 +18,7 @@ export class HCaptchaConfigService { * Required */ get apiKey(): string { - return this.configService.getOrThrow('HCAPTCHA_API_KEY'); + return this.configService.getOrThrow('HCAPTCHA_API_KEY'); } /** @@ -26,7 +26,7 @@ export class HCaptchaConfigService { * Required */ get secret(): string { - return this.configService.getOrThrow('HCAPTCHA_SECRET'); + return this.configService.getOrThrow('HCAPTCHA_SECRET'); } /** @@ -34,7 +34,7 @@ export class HCaptchaConfigService { * Default: 'https://api.hcaptcha.com' */ get protectionURL(): string { - return this.configService.get( + return this.configService.get( 'HCAPTCHA_PROTECTION_URL', 'https://api.hcaptcha.com', ); @@ -45,7 +45,7 @@ export class HCaptchaConfigService { * Default: 'https://foundation-accounts.hmt.ai' */ get labelingURL(): string { - return this.configService.get( + return this.configService.get( 'HCAPTCHA_LABELING_URL', 'https://foundation-accounts.hmt.ai', ); @@ -56,9 +56,6 @@ export class HCaptchaConfigService { * Default: 'en' */ get defaultLabelerLang(): string { - return this.configService.get( - 'HCAPTCHA_DEFAULT_LABELER_LANG', - 'en', - ); + return this.configService.get('HCAPTCHA_DEFAULT_LABELER_LANG', 'en'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/index.ts b/packages/apps/reputation-oracle/server/src/config/index.ts index 24f6ec91c4..2e76538048 100644 --- a/packages/apps/reputation-oracle/server/src/config/index.ts +++ b/packages/apps/reputation-oracle/server/src/config/index.ts @@ -1 +1,16 @@ export * from './env-schema'; + +export { AuthConfigService } from './auth-config.service'; +export { DatabaseConfigService } from './database-config.service'; +export { EmailConfigService } from './email-config.service'; +export { HCaptchaConfigService } from './hcaptcha-config.service'; +export { KycConfigService } from './kyc-config.service'; +export { NDAConfigService } from './nda-config.service'; +export { PGPConfigService } from './pgp-config.service'; +export { ReputationConfigService } from './reputation-config.service'; +export { S3ConfigService } from './s3-config.service'; +export { ServerConfigService } from './server-config.service'; +export { Web3ConfigService, Web3Network } from './web3-config.service'; +export { SlackConfigService } from './slack-config.service'; + +export { EnvConfigModule } from './config.module'; diff --git a/packages/apps/reputation-oracle/server/src/config/kyc-config.service.ts b/packages/apps/reputation-oracle/server/src/config/kyc-config.service.ts index ee3c39a886..c3266abac4 100644 --- a/packages/apps/reputation-oracle/server/src/config/kyc-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/kyc-config.service.ts @@ -10,7 +10,7 @@ export class KycConfigService { * Required */ get apiKey(): string { - return this.configService.getOrThrow('KYC_API_KEY'); + return this.configService.getOrThrow('KYC_API_KEY'); } /** @@ -18,7 +18,7 @@ export class KycConfigService { * Required */ get apiPrivateKey(): string { - return this.configService.getOrThrow('KYC_API_PRIVATE_KEY'); + return this.configService.getOrThrow('KYC_API_PRIVATE_KEY'); } /** @@ -26,7 +26,7 @@ export class KycConfigService { * Default: 'https://stationapi.veriff.com/v1' */ get baseUrl(): string { - return this.configService.get( + return this.configService.get( 'KYC_BASE_URL', 'https://stationapi.veriff.com/v1', ); diff --git a/packages/apps/reputation-oracle/server/src/config/nda-config.service.ts b/packages/apps/reputation-oracle/server/src/config/nda-config.service.ts index 391c38d574..b26ae74676 100644 --- a/packages/apps/reputation-oracle/server/src/config/nda-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/nda-config.service.ts @@ -9,6 +9,6 @@ export class NDAConfigService { * Latest NDA Url. */ get latestNdaUrl(): string { - return this.configService.getOrThrow('NDA_URL'); + return this.configService.getOrThrow('NDA_URL'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/pgp-config.service.ts b/packages/apps/reputation-oracle/server/src/config/pgp-config.service.ts index 65e8111159..3347104737 100644 --- a/packages/apps/reputation-oracle/server/src/config/pgp-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/pgp-config.service.ts @@ -10,20 +10,20 @@ export class PGPConfigService { * Default: false */ get encrypt(): boolean { - return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; + return this.configService.get('PGP_ENCRYPT', 'false') === 'true'; } /** * The private key used for PGP encryption or decryption. */ get privateKey(): string { - return this.configService.getOrThrow('PGP_PRIVATE_KEY'); + return this.configService.getOrThrow('PGP_PRIVATE_KEY'); } /** * The passphrase associated with the PGP private key. */ get passphrase(): string { - return this.configService.getOrThrow('PGP_PASSPHRASE'); + return this.configService.getOrThrow('PGP_PASSPHRASE'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/reputation-config.service.ts b/packages/apps/reputation-oracle/server/src/config/reputation-config.service.ts index 49a5ba99c3..f7399cbb84 100644 --- a/packages/apps/reputation-oracle/server/src/config/reputation-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/reputation-config.service.ts @@ -11,7 +11,7 @@ export class ReputationConfigService { * Default: 300 */ get lowLevel(): number { - return +this.configService.get('REPUTATION_LEVEL_LOW', 300); + return Number(this.configService.get('REPUTATION_LEVEL_LOW')) || 300; } /** @@ -20,6 +20,6 @@ export class ReputationConfigService { * Default: 700 */ get highLevel(): number { - return +this.configService.get('REPUTATION_LEVEL_HIGH', 700); + return Number(this.configService.get('REPUTATION_LEVEL_HIGH')) || 700; } } diff --git a/packages/apps/reputation-oracle/server/src/config/s3-config.service.ts b/packages/apps/reputation-oracle/server/src/config/s3-config.service.ts index 54fd656944..8a927ec563 100644 --- a/packages/apps/reputation-oracle/server/src/config/s3-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/s3-config.service.ts @@ -10,7 +10,7 @@ export class S3ConfigService { * Default: '127.0.0.1' */ get endpoint(): string { - return this.configService.get('S3_ENDPOINT', '127.0.0.1'); + return this.configService.get('S3_ENDPOINT', '127.0.0.1'); } /** @@ -18,7 +18,7 @@ export class S3ConfigService { * Default: 9000 */ get port(): number { - return +this.configService.get('S3_PORT', 9000); + return Number(this.configService.get('S3_PORT')) || 9000; } /** @@ -26,7 +26,7 @@ export class S3ConfigService { * Required */ get accessKey(): string { - return this.configService.getOrThrow('S3_ACCESS_KEY'); + return this.configService.getOrThrow('S3_ACCESS_KEY'); } /** @@ -34,7 +34,7 @@ export class S3ConfigService { * Required */ get secretKey(): string { - return this.configService.getOrThrow('S3_SECRET_KEY'); + return this.configService.getOrThrow('S3_SECRET_KEY'); } /** @@ -42,7 +42,7 @@ export class S3ConfigService { * Default: 'reputation' */ get bucket(): string { - return this.configService.get('S3_BUCKET', 'reputation'); + return this.configService.get('S3_BUCKET', 'reputation'); } /** @@ -50,6 +50,6 @@ export class S3ConfigService { * Default: false */ get useSSL(): boolean { - return this.configService.get('S3_USE_SSL', 'false') === 'true'; + return this.configService.get('S3_USE_SSL', 'false') === 'true'; } } diff --git a/packages/apps/reputation-oracle/server/src/config/server-config.service.ts b/packages/apps/reputation-oracle/server/src/config/server-config.service.ts index db5474877e..362789f6a3 100644 --- a/packages/apps/reputation-oracle/server/src/config/server-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/server-config.service.ts @@ -6,7 +6,7 @@ export class ServerConfigService { constructor(private configService: ConfigService) {} get gitHash(): string { - return this.configService.get('GIT_HASH', ''); + return this.configService.get('GIT_HASH', ''); } /** @@ -14,7 +14,7 @@ export class ServerConfigService { * Default: 'localhost' */ get host(): string { - return this.configService.get('HOST', 'localhost'); + return this.configService.get('HOST', 'localhost'); } /** @@ -22,7 +22,7 @@ export class ServerConfigService { * Default: 5003 */ get port(): number { - return +this.configService.get('PORT', 5003); + return Number(this.configService.get('PORT')) || 5003; } /** @@ -30,7 +30,7 @@ export class ServerConfigService { * Default: 'http://localhost:3001' */ get feURL(): string { - return this.configService.get('FE_URL', 'http://localhost:3001'); + return this.configService.get('FE_URL', 'http://localhost:3001'); } /** @@ -38,7 +38,7 @@ export class ServerConfigService { * Default: 'session_key' */ get sessionSecret(): string { - return this.configService.get('SESSION_SECRET', 'session_key'); + return this.configService.get('SESSION_SECRET', 'session_key'); } /** @@ -46,7 +46,7 @@ export class ServerConfigService { * Default: 5 */ get maxRetryCount(): number { - return +this.configService.get('MAX_RETRY_COUNT', 5); + return Number(this.configService.get('MAX_RETRY_COUNT')) || 5; } /** @@ -54,12 +54,9 @@ export class ServerConfigService { * Default: 1 day (24 * 60 * 60 * 1000 ms) */ get qualificationMinValidity(): number { - return ( - +this.configService.get('QUALIFICATION_MIN_VALIDITY', 1) * - 24 * - 60 * - 60 * - 1000 - ); + const configValueDays = + Number(this.configService.get('QUALIFICATION_MIN_VALIDITY')) || 1; + + return configValueDays * 24 * 60 * 60 * 1000; } } diff --git a/packages/apps/reputation-oracle/server/src/config/slack-config.service.ts b/packages/apps/reputation-oracle/server/src/config/slack-config.service.ts index dac95dcf77..4599c94432 100644 --- a/packages/apps/reputation-oracle/server/src/config/slack-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/slack-config.service.ts @@ -5,12 +5,12 @@ import { ConfigService } from '@nestjs/config'; export class SlackConfigService { constructor(private configService: ConfigService) {} get abuseWebhookUrl(): string { - return this.configService.getOrThrow('ABUSE_SLACK_WEBHOOK_URL'); + return this.configService.getOrThrow('ABUSE_SLACK_WEBHOOK_URL'); } get abuseOauthToken(): string { - return this.configService.getOrThrow('ABUSE_SLACK_OAUTH_TOKEN'); + return this.configService.getOrThrow('ABUSE_SLACK_OAUTH_TOKEN'); } get abuseSigningSecret(): string { - return this.configService.getOrThrow('ABUSE_SLACK_SIGNING_SECRET'); + return this.configService.getOrThrow('ABUSE_SLACK_SIGNING_SECRET'); } } diff --git a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts index a550796c47..dbb40d55f4 100644 --- a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts @@ -23,7 +23,7 @@ export class Web3ConfigService { * Default: 'testnet' */ get network(): Web3Network { - return this.configService.get('WEB3_ENV', Web3Network.TESTNET); + return this.configService.get('WEB3_ENV', Web3Network.TESTNET); } /** @@ -31,7 +31,7 @@ export class Web3ConfigService { * Required */ get privateKey(): string { - return this.configService.getOrThrow('WEB3_PRIVATE_KEY'); + return this.configService.getOrThrow('WEB3_PRIVATE_KEY'); } /** @@ -53,23 +53,17 @@ export class Web3ConfigService { * Default: 1 */ get gasPriceMultiplier(): number { - return +this.configService.get('GAS_PRICE_MULTIPLIER', 1); + return Number(this.configService.get('GAS_PRICE_MULTIPLIER')) || 1; } getRpcUrlByChainId(chainId: number): string | undefined { const rpcUrlsByChainId: Record = { - [ChainId.POLYGON]: this.configService.get('RPC_URL_POLYGON'), - [ChainId.POLYGON_AMOY]: this.configService.get( - 'RPC_URL_POLYGON_AMOY', - ), - [ChainId.BSC_MAINNET]: this.configService.get( - 'RPC_URL_BSC_MAINNET', - ), - [ChainId.BSC_TESTNET]: this.configService.get( - 'RPC_URL_BSC_TESTNET', - ), - [ChainId.SEPOLIA]: this.configService.get('RPC_URL_SEPOLIA'), - [ChainId.LOCALHOST]: this.configService.get('RPC_URL_LOCALHOST'), + [ChainId.POLYGON]: this.configService.get('RPC_URL_POLYGON'), + [ChainId.POLYGON_AMOY]: this.configService.get('RPC_URL_POLYGON_AMOY'), + [ChainId.BSC_MAINNET]: this.configService.get('RPC_URL_BSC_MAINNET'), + [ChainId.BSC_TESTNET]: this.configService.get('RPC_URL_BSC_TESTNET'), + [ChainId.SEPOLIA]: this.configService.get('RPC_URL_SEPOLIA'), + [ChainId.LOCALHOST]: this.configService.get('RPC_URL_LOCALHOST'), }; return rpcUrlsByChainId[chainId]; diff --git a/packages/apps/reputation-oracle/server/src/database/base.repository.ts b/packages/apps/reputation-oracle/server/src/database/base.repository.ts index 7f6e16341f..61f5996e08 100644 --- a/packages/apps/reputation-oracle/server/src/database/base.repository.ts +++ b/packages/apps/reputation-oracle/server/src/database/base.repository.ts @@ -1,14 +1,5 @@ -import { - DataSource, - EntityTarget, - ObjectLiteral, - QueryFailedError, - Repository, -} from 'typeorm'; -import { - DatabaseError, - handleQueryFailedError, -} from '../common/errors/database'; +import { DataSource, EntityTarget, ObjectLiteral, Repository } from 'typeorm'; +import { DatabaseError, handleDbError } from './errors'; import { BaseEntity } from './base.entity'; export class BaseRepository< @@ -26,11 +17,7 @@ export class BaseRepository< await this.insert(item); } catch (error) { - if (error instanceof QueryFailedError) { - throw handleQueryFailedError(error); - } else { - throw error; - } + throw handleDbError(error); } return item; } @@ -40,11 +27,7 @@ export class BaseRepository< item.updatedAt = new Date(); await this.save(item); } catch (error) { - if (error instanceof QueryFailedError) { - throw handleQueryFailedError(error); - } else { - throw error; - } + throw handleDbError(error); } return item; } @@ -67,11 +50,7 @@ export class BaseRepository< return result.affected > 0; } catch (error) { - if (error instanceof QueryFailedError) { - throw handleQueryFailedError(error); - } else { - throw error; - } + throw handleDbError(error); } } @@ -79,11 +58,7 @@ export class BaseRepository< try { await this.remove(item); } catch (error) { - if (error instanceof QueryFailedError) { - throw handleQueryFailedError(error); - } else { - throw error; - } + throw handleDbError(error); } } } diff --git a/packages/apps/reputation-oracle/server/src/database/database.module.ts b/packages/apps/reputation-oracle/server/src/database/database.module.ts index f2e53645f1..52f41561e9 100644 --- a/packages/apps/reputation-oracle/server/src/database/database.module.ts +++ b/packages/apps/reputation-oracle/server/src/database/database.module.ts @@ -8,7 +8,7 @@ import { TokenEntity } from '../modules/auth/token.entity'; import { UserEntity } from '../modules/user/user.entity'; import { KycEntity } from '../modules/kyc/kyc.entity'; import { CronJobEntity } from '../modules/cron-job/cron-job.entity'; -import { DatabaseConfigService } from '../config/database-config.service'; +import { DatabaseConfigService } from '../config'; import { SiteKeyEntity } from '../modules/user/site-key.entity'; import { QualificationEntity } from '../modules/qualification/qualification.entity'; import { UserQualificationEntity } from '../modules/qualification/user-qualification.entity'; diff --git a/packages/apps/reputation-oracle/server/src/database/errors.ts b/packages/apps/reputation-oracle/server/src/database/errors.ts new file mode 100644 index 0000000000..74d06da4fb --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/errors.ts @@ -0,0 +1,26 @@ +import { BaseError } from '../common/errors/base'; + +enum PostgresErrorCodes { + Duplicated = '23505', +} + +export class DatabaseError extends BaseError {} + +export enum DatabaseErrorMessages { + DUPLICATED = 'Entity duplication error', +} + +export function handleDbError(error: any): DatabaseError { + if (error.code === PostgresErrorCodes.Duplicated) { + return new DatabaseError(DatabaseErrorMessages.DUPLICATED); + } + + return new DatabaseError(error.message, error); +} + +export function isDuplicatedError(error: unknown): boolean { + return ( + error instanceof DatabaseError && + error.message === DatabaseErrorMessages.DUPLICATED + ); +} diff --git a/packages/apps/reputation-oracle/server/src/database/index.ts b/packages/apps/reputation-oracle/server/src/database/index.ts new file mode 100644 index 0000000000..564a684bb3 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/index.ts @@ -0,0 +1,8 @@ +export { BaseEntity } from './base.entity'; +export { BaseRepository } from './base.repository'; +export { DatabaseModule } from './database.module'; +export { + DatabaseErrorMessages, + DatabaseError, + isDuplicatedError, +} from './errors'; diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures/index.ts similarity index 83% rename from packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts rename to packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures/index.ts index 1eec11f86c..d187210da1 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/fixtures/index.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; +import { HCaptchaConfigService } from '../../../config'; export const mockHCaptchaConfigService: Omit< HCaptchaConfigService, diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts index 83d3417863..289f6a54d7 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.guard.spec.ts @@ -7,7 +7,7 @@ import { } from '../../../test/mock-creators/nest'; import { HCaptchaGuard } from './hcaptcha.guard'; -import { AuthConfigService } from 'src/config/auth-config.service'; +import { AuthConfigService } from '../../config'; import { HCaptchaService } from './hcaptcha.service'; const mockHCaptchaService = { diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts index d54c5c1097..3e9ed38688 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.spec.ts @@ -4,7 +4,7 @@ import { Test } from '@nestjs/testing'; import { ethers } from 'ethers'; import { HCaptchaService } from './hcaptcha.service'; -import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; +import { HCaptchaConfigService } from '../../config'; import { createHttpServiceMock, diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts index 910f38a0fe..4643db340c 100644 --- a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/hcaptcha.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { ethers } from 'ethers'; import { firstValueFrom } from 'rxjs'; -import { HCaptchaConfigService } from '../../config/hcaptcha-config.service'; +import { HCaptchaConfigService } from '../../config'; import logger from '../../logger'; import { diff --git a/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/index.ts b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/index.ts new file mode 100644 index 0000000000..49e90acc83 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/integrations/hcaptcha/index.ts @@ -0,0 +1 @@ +export { HCaptchaModule } from './hcaptcha.module'; diff --git a/packages/apps/reputation-oracle/server/src/integrations/slack-bot-app/index.ts b/packages/apps/reputation-oracle/server/src/integrations/slack-bot-app/index.ts new file mode 100644 index 0000000000..0f86bbe233 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/integrations/slack-bot-app/index.ts @@ -0,0 +1 @@ +export { SlackBotApp } from './slack-bot-app'; diff --git a/packages/apps/reputation-oracle/server/src/main.ts b/packages/apps/reputation-oracle/server/src/main.ts index b7f598ce6f..62c688a44b 100644 --- a/packages/apps/reputation-oracle/server/src/main.ts +++ b/packages/apps/reputation-oracle/server/src/main.ts @@ -3,12 +3,13 @@ import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { json, urlencoded } from 'body-parser'; +import { useContainer } from 'class-validator'; import helmet from 'helmet'; +import { IncomingMessage, ServerResponse } from 'http'; + import { AppModule } from './app.module'; -import { useContainer } from 'class-validator'; -import { ServerConfigService } from './config/server-config.service'; +import { ServerConfigService } from './config'; import logger, { nestLoggerOverride } from './logger'; -import { IncomingMessage, ServerResponse } from 'http'; function rawBodyMiddleware( req: any, diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-auth.guard.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-auth.guard.ts similarity index 99% rename from packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-auth.guard.ts rename to packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-auth.guard.ts index 324296dbdd..0aef547d51 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-auth.guard.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-auth.guard.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; + import { SlackAuthGuard } from '../../common/guards/slack.auth'; import { SlackConfigService } from '../../config/slack-config.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.spec.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.spec.ts similarity index 97% rename from packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.spec.ts rename to packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.spec.ts index cecaaf3c10..2c44b30887 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.spec.ts @@ -3,9 +3,9 @@ import { Test } from '@nestjs/testing'; import { HttpService } from '@nestjs/axios'; import { createHttpServiceMock } from '../../../test/mock-creators/nest'; -import { SlackConfigService } from '../../config/slack-config.service'; +import { SlackConfigService } from '../../config'; -import { AbuseSlackBot } from './abuse.slack-bot'; +import { AbuseSlackBot } from './abuse-slack-bot'; import { AbuseDecision } from './constants'; const mockHttpService = createHttpServiceMock(); diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.ts similarity index 95% rename from packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.ts rename to packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.ts index d829278b8e..95dba0f892 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.slack-bot.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse-slack-bot.ts @@ -1,12 +1,13 @@ import { ChainId } from '@human-protocol/sdk'; import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; -import { SlackConfigService } from '../../config/slack-config.service'; -import { SlackBotApp } from '../../integrations/slack-bot-app/slack-bot-app'; -import { AbuseDecision } from './constants'; import { View } from '@slack/web-api'; import { IncomingWebhookSendArguments } from '@slack/webhook'; +import { SlackConfigService } from '../../config'; +import { SlackBotApp } from '../../integrations/slack-bot-app'; +import { AbuseDecision } from './constants'; + @Injectable() export class AbuseSlackBot extends SlackBotApp { constructor( diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts index 813bf83a63..35d4273953 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts @@ -14,16 +14,18 @@ import { ApiResponse, ApiTags, } from '@nestjs/swagger'; + +import { Public } from '../../common/decorators'; +import { RequestWithUser } from '../../common/types'; + import { AbuseResponseDto, ReportAbuseDto, SlackInteractionDto, } from './abuse.dto'; import { AbuseService } from './abuse.service'; -import { RequestWithUser } from '../../common/interfaces/request'; -import { Public } from '../../common/decorators'; import { AbuseRepository } from './abuse.repository'; -import { AbuseSlackAuthGuard } from './abuse.slack-auth.guard'; +import { AbuseSlackAuthGuard } from './abuse-slack-auth.guard'; @ApiTags('Abuse') @Controller('/abuse') diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index 4bb74de154..b828c78490 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -1,9 +1,10 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { AbuseStatus } from './constants'; -import { IsChainId } from '../../common/validators'; import { IsEthereumAddress, IsString } from 'class-validator'; +import { IsChainId } from '../../common/validators'; +import { AbuseStatus } from './constants'; + export class ReportAbuseDto { @ApiProperty({ name: 'chain_id' }) @IsChainId() diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts index acc9b51058..717cae7df9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts @@ -1,9 +1,9 @@ +import { ChainId } from '@human-protocol/sdk'; import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; -import { UserEntity } from '../user/user.entity'; -import { ChainId } from '@human-protocol/sdk'; +import { BaseEntity } from '../../database'; +import type { UserEntity } from '../user'; import { AbuseDecision, AbuseStatus } from './constants'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'abuses' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.module.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.module.ts index 5e995f6eb5..2dc3548c94 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.module.ts @@ -1,12 +1,14 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; -import { AbuseSlackBot } from './abuse.slack-bot'; + +import { Web3Module } from '../web3'; +import { OutgoingWebhookModule } from '../webhook'; + +import { AbuseSlackAuthGuard } from './abuse-slack-auth.guard'; +import { AbuseSlackBot } from './abuse-slack-bot'; +import { AbuseController } from './abuse.controller'; import { AbuseRepository } from './abuse.repository'; import { AbuseService } from './abuse.service'; -import { Web3Module } from '../web3/web3.module'; -import { OutgoingWebhookModule } from '../webhook/webhook-outgoing.module'; -import { AbuseController } from './abuse.controller'; -import { AbuseSlackAuthGuard } from './abuse.slack-auth.guard'; @Module({ imports: [HttpModule, Web3Module, OutgoingWebhookModule], diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.repository.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.repository.ts index f07647b0b8..b19a4800d3 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.repository.ts @@ -6,10 +6,11 @@ import { LessThanOrEqual, Not, } from 'typeorm'; -import { AbuseStatus } from './constants'; -import { ServerConfigService } from '../../config/server-config.service'; -import { BaseRepository } from '../../database/base.repository'; + +import { ServerConfigService } from '../../config'; +import { BaseRepository } from '../../database'; import { AbuseEntity } from './abuse.entity'; +import { AbuseStatus } from './constants'; type FindOptions = { relations?: FindManyOptions['relations']; diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts index 43bd05d038..e39275ea72 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts @@ -10,16 +10,15 @@ import { } from '@human-protocol/sdk'; import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; -import { PostgresErrorCodes } from '../../common/enums/database'; -import { DatabaseError } from '../../common/errors/database'; -import { ServerConfigService } from '../../config/server-config.service'; +import { DatabaseError, DatabaseErrorMessages } from '../../database'; +import { ServerConfigService } from '../../config'; import { generateTestnetChainId } from '../web3/fixtures'; -import { Web3Service } from '../web3/web3.service'; -import { OutgoingWebhookEventType } from '../webhook/types'; -import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; +import { Web3Service } from '../web3'; +import { OutgoingWebhookEventType, OutgoingWebhookService } from '../webhook'; + import { AbuseRepository } from './abuse.repository'; import { AbuseService } from './abuse.service'; -import { AbuseSlackBot } from './abuse.slack-bot'; +import { AbuseSlackBot } from './abuse-slack-bot'; import { AbuseDecision, AbuseStatus } from './constants'; import { generateAbuseEntity } from './fixtures'; @@ -67,7 +66,7 @@ describe('AbuseService', () => { abuseService = moduleRef.get(AbuseService); }); - beforeEach(() => { + afterEach(() => { jest.resetAllMocks(); }); @@ -331,7 +330,7 @@ describe('AbuseService', () => { } as IOperator); mockOutgoingWebhookService.createOutgoingWebhook.mockRejectedValueOnce( - new DatabaseError(PostgresErrorCodes.Duplicated), + new DatabaseError(DatabaseErrorMessages.DUPLICATED), ); await abuseService.processAbuseRequests(); diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts index 56ae182ffc..04c3b30015 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts @@ -6,12 +6,12 @@ import { } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import { ethers } from 'ethers'; -import { isDuplicatedError } from '../../common/errors/database'; -import { ServerConfigService } from '../../config/server-config.service'; +import { isDuplicatedError } from '../../database'; +import { ServerConfigService } from '../../config'; import logger from '../../logger'; -import { Web3Service } from '../web3/web3.service'; -import { OutgoingWebhookEventType } from '../webhook/types'; -import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; +import { Web3Service } from '../web3'; +import { OutgoingWebhookEventType, OutgoingWebhookService } from '../webhook'; + import { AbuseEntity } from './abuse.entity'; import { AbuseRepository } from './abuse.repository'; import { AbuseDecision, AbuseStatus } from './constants'; @@ -21,7 +21,7 @@ import { ReportAbuseInput, SlackInteraction, } from './types'; -import { AbuseSlackBot } from './abuse.slack-bot'; +import { AbuseSlackBot } from './abuse-slack-bot'; @Injectable() export class AbuseService { diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts similarity index 75% rename from packages/apps/reputation-oracle/server/src/modules/abuse/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts index d4336027e6..e79e622b2b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts @@ -1,8 +1,8 @@ import { faker } from '@faker-js/faker'; -import { generateTestnetChainId } from '../web3/fixtures'; -import { AbuseStatus } from './constants'; -import { generateWorkerUser } from '../user/fixtures'; -import { AbuseEntity } from './abuse.entity'; +import { generateTestnetChainId } from '../../web3/fixtures'; +import { AbuseStatus } from '../constants'; +import { generateWorkerUser } from '../../user/fixtures'; +import { AbuseEntity } from '../abuse.entity'; export function generateAbuseEntity( overrides?: Partial, diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/index.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/index.ts new file mode 100644 index 0000000000..16abf3f982 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/index.ts @@ -0,0 +1,2 @@ +export { AbuseService } from './abuse.service'; +export { AbuseModule } from './abuse.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts index 523095a796..986cfa8175 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.controller.ts @@ -16,13 +16,15 @@ import { UseFilters, HttpCode, } from '@nestjs/common'; + import { Public } from '../../common/decorators'; -import { AuthService } from './auth.service'; -import { RequestWithUser } from '../../common/interfaces/request'; +import { RequestWithUser } from '../../common/types'; import { HCaptchaGuard } from '../../integrations/hcaptcha/hcaptcha.guard'; + +import { AuthService } from './auth.service'; import { TokenRepository } from './token.repository'; import { TokenType } from './token.entity'; -import { AuthControllerErrorsFilter } from './auth.error.filter'; +import { AuthControllerErrorsFilter } from './auth.error-filter'; import { ForgotPasswordDto, SuccessAuthDto, @@ -216,7 +218,7 @@ export class AuthController { async resendEmailVerification( @Req() request: RequestWithUser, ): Promise { - await this.authService.resendEmailVerification(request.user); + await this.authService.resendEmailVerification(request.user.id); } @ApiOperation({ diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.filter.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.error-filter.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/auth/auth.error.filter.ts rename to packages/apps/reputation-oracle/server/src/modules/auth/auth.error-filter.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts index 799ca09542..225e5a0210 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; -import { AuthConfigService } from '../../config/auth-config.service'; -import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; -import { EmailModule } from '../email/module'; +import { AuthConfigService } from '../../config'; +import { HCaptchaModule } from '../../integrations/hcaptcha'; +import { EmailModule } from '../email'; import { UserModule } from '../user'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts index db811119bd..fefa53161c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.spec.ts @@ -9,16 +9,17 @@ import { omit } from 'lodash'; import { generateES256Keys } from '../../../test/fixtures/crypto'; import { generateEthWallet } from '../../../test/fixtures/web3'; -import { SignatureType } from '../../common/enums/web3'; -import { AuthConfigService } from '../../config/auth-config.service'; -import { NDAConfigService } from '../../config/nda-config.service'; -import { ServerConfigService } from '../../config/server-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { SignatureType } from '../../common/enums'; +import { + AuthConfigService, + NDAConfigService, + ServerConfigService, + Web3ConfigService, +} from '../../config'; import * as secutiryUtils from '../../utils/security'; -import { SiteKeyRepository } from '../user/site-key.repository'; +import { SiteKeyRepository } from '../user'; import * as web3Utils from '../../utils/web3'; -import { EmailAction } from '../email/constants'; -import { EmailService } from '../email/email.service'; +import { EmailAction, EmailService } from '../email'; import { UserStatus, UserRole, @@ -223,22 +224,18 @@ describe('AuthService', () => { it('should throw DuplicatedUserAddressError', async () => { const ethWallet = generateEthWallet(); const signature = faker.string.alpha(); - mockUserRepository.findOneByAddress.mockImplementationOnce( - async (address) => { - if (address === ethWallet.address) { - return { - evmAddress: ethWallet.address, - } as UserEntity; - } - return null; - }, - ); + mockUserRepository.findOneByAddress.mockResolvedValueOnce({ + evmAddress: ethWallet.address, + } as UserEntity); await expect( service.web3Signup(signature, ethWallet.address), ).rejects.toThrow( new AuthErrors.DuplicatedUserAddressError(ethWallet.address), ); + expect(mockUserRepository.findOneByAddress).toHaveBeenCalledWith( + ethWallet.address, + ); }); it('should throw AuthError(AuthErrorMessage.INVALID_WEB3_SIGNATURE)', async () => { @@ -1011,7 +1008,7 @@ describe('AuthService', () => { }); describe('emailVerification', () => { - it('should verify an email', async () => { + it('should verify an email and remove token', async () => { const mockToken = { userId: faker.number.int(), uuid: faker.string.uuid(), @@ -1032,6 +1029,8 @@ describe('AuthService', () => { status: UserStatus.ACTIVE, }, ); + expect(mockTokenRepository.deleteOne).toHaveBeenCalledTimes(1); + expect(mockTokenRepository.deleteOne).toHaveBeenCalledWith(mockToken); }); it('should throw AuthError(AuthErrorMessage.INVALID_EMAIL_TOKEN) if token not found', async () => { @@ -1046,20 +1045,22 @@ describe('AuthService', () => { ); }); - it('should throw AuthError(AuthErrorMessage.EMAIL_TOKEN_EXPIRED)', async () => { + it('should throw AuthError(AuthErrorMessage.EMAIL_TOKEN_EXPIRED) and remove token', async () => { const uuid = faker.string.uuid(); - - mockTokenRepository.findOneByUuidAndType.mockResolvedValueOnce({ + const mockToken = { uuid, expiresAt: faker.date.past(), - } as TokenEntity); - mockTokenRepository.findOneByUuidAndType.mockResolvedValueOnce(null); + } as TokenEntity; + + mockTokenRepository.findOneByUuidAndType.mockResolvedValueOnce(mockToken); await expect(service.emailVerification(uuid)).rejects.toThrow( new AuthErrors.AuthError( AuthErrors.AuthErrorMessage.EMAIL_TOKEN_EXPIRED, ), ); + expect(mockTokenRepository.deleteOne).toHaveBeenCalledTimes(1); + expect(mockTokenRepository.deleteOne).toHaveBeenCalledWith(mockToken); }); }); @@ -1070,7 +1071,7 @@ describe('AuthService', () => { const user = generateWorkerUser({ status: UserStatus.PENDING }); const tokenUuid = faker.string.uuid(); - mockUserRepository.findOneByEmail.mockResolvedValueOnce(user); + mockUserRepository.findOneById.mockResolvedValueOnce(user); mockTokenRepository.findOneByUserIdAndType.mockResolvedValueOnce( existingEmailToken as TokenEntity, ); @@ -1078,7 +1079,7 @@ describe('AuthService', () => { uuid: tokenUuid, } as TokenEntity); - await service.resendEmailVerification(user); + await service.resendEmailVerification(user.id); if (existingEmailToken) { expect(mockTokenRepository.deleteOne).toHaveBeenCalledTimes(1); @@ -1109,9 +1110,9 @@ describe('AuthService', () => { async (userStatus) => { const user = generateWorkerUser({ status: userStatus }); - mockUserRepository.findOneByEmail.mockResolvedValueOnce(null); + mockUserRepository.findOneById.mockResolvedValueOnce(null); - await service.resendEmailVerification(user); + await service.resendEmailVerification(user.id); expect( mockTokenRepository.findOneByUserIdAndType, diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts index 3cf9243793..bc7b374ff4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.service.ts @@ -2,18 +2,19 @@ import { KVStoreKeys, KVStoreUtils, Role } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import { SignatureType } from '../../common/enums/web3'; -import { AuthConfigService } from '../../config/auth-config.service'; -import { NDAConfigService } from '../../config/nda-config.service'; -import { ServerConfigService } from '../../config/server-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { SignatureType } from '../../common/enums'; +import { + AuthConfigService, + NDAConfigService, + ServerConfigService, + Web3ConfigService, +} from '../../config'; import logger from '../../logger'; import * as httpUtils from '../../utils/http'; import * as securityUtils from '../../utils/security'; import * as web3Utils from '../../utils/web3'; -import { EmailAction } from '../email/constants'; -import { EmailService } from '../email/email.service'; +import { EmailAction, EmailService } from '../email'; import { OperatorStatus, SiteKeyRepository, @@ -39,11 +40,7 @@ import { } from './auth.error'; import { TokenEntity, TokenType } from './token.entity'; import { TokenRepository } from './token.repository'; - -type AuthTokens = { - accessToken: string; - refreshToken: string; -}; +import type { AuthTokens } from './types'; @Injectable() export class AuthService { @@ -414,16 +411,22 @@ export class AuthService { } if (new Date() > tokenEntity.expiresAt) { + await this.tokenRepository.deleteOne(tokenEntity); throw new AuthError(AuthErrorMessage.EMAIL_TOKEN_EXPIRED); } await this.userRepository.updateOneById(tokenEntity.userId, { status: UserStatus.ACTIVE, }); + await this.tokenRepository.deleteOne(tokenEntity); } - async resendEmailVerification(user: Web2UserEntity): Promise { - if (user.status !== UserStatus.PENDING) { + async resendEmailVerification(userId: number): Promise { + const user = (await this.userRepository.findOneById( + userId, + )) as Web2UserEntity; + + if (!user || user.status !== UserStatus.PENDING) { return; } diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/index.ts b/packages/apps/reputation-oracle/server/src/modules/auth/index.ts new file mode 100644 index 0000000000..faa5c33607 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/auth/index.ts @@ -0,0 +1 @@ +export { AuthModule } from './auth.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/jwt-http-strategy.ts b/packages/apps/reputation-oracle/server/src/modules/auth/jwt-http-strategy.ts index 8c5d7cd8b6..af522263e9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/jwt-http-strategy.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/jwt-http-strategy.ts @@ -7,21 +7,15 @@ import { LOGOUT_PATH, RESEND_EMAIL_VERIFICATION_PATH, } from '../../common/constants'; -import { UserEntity, UserStatus, UserRepository } from '../user'; -import { AuthConfigService } from '../../config/auth-config.service'; -import { TokenType } from './token.entity'; -import { TokenRepository } from './token.repository'; +import { UserStatus } from '../user'; +import { AuthConfigService } from '../../config'; @Injectable() export class JwtHttpStrategy extends PassportStrategy( Strategy, JWT_STRATEGY_NAME, ) { - constructor( - private readonly userRepository: UserRepository, - private readonly tokenRepository: TokenRepository, - authConfigService: AuthConfigService, - ) { + constructor(authConfigService: AuthConfigService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, @@ -32,36 +26,16 @@ export class JwtHttpStrategy extends PassportStrategy( async validate( @Req() request: any, - payload: { user_id: number }, - ): Promise { - const token = await this.tokenRepository.findOneByUserIdAndType( - payload.user_id, - TokenType.REFRESH, - ); - - if (!token) { - throw new UnauthorizedException('User is not authorized'); - } - - const user = await this.userRepository.findOneById(payload.user_id, { - relations: { - kyc: true, - siteKeys: true, - }, - }); - - if (!user) { - throw new UnauthorizedException('User not found'); - } - + payload: { user_id: number; status: UserStatus }, + ): Promise<{ id: number }> { if ( - user.status !== UserStatus.ACTIVE && + payload.status !== UserStatus.ACTIVE && request.url !== RESEND_EMAIL_VERIFICATION_PATH && request.url !== LOGOUT_PATH ) { throw new UnauthorizedException('User not active'); } - return user; + return { id: payload.user_id }; } } diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts index 88c81e2432..259ea90a2d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.entity.ts @@ -9,7 +9,7 @@ import { import type { UserEntity } from '../user'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; export enum TokenType { EMAIL = 'email', diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts index 100f5baf29..e4f3fd3a4d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/token.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource, FindManyOptions } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { TokenEntity, TokenType } from './token.entity'; type FindOptions = { diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/types.ts b/packages/apps/reputation-oracle/server/src/modules/auth/types.ts new file mode 100644 index 0000000000..d6912f4561 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/auth/types.ts @@ -0,0 +1,4 @@ +export type AuthTokens = { + accessToken: string; + refreshToken: string; +}; diff --git a/packages/apps/reputation-oracle/server/src/common/enums/cron-job.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/constants.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/common/enums/cron-job.ts rename to packages/apps/reputation-oracle/server/src/modules/cron-job/constants.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts index e7c6f3faaa..e23db64fa5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.entity.ts @@ -1,8 +1,9 @@ import { Column, Entity, Index } from 'typeorm'; -import { BaseEntity } from '../../database/base.entity'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { CronJobType } from '../../common/enums/cron-job'; +import { BaseEntity } from '../../database'; + +import { CronJobType } from './constants'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'cron-jobs' }) @Index(['cronJobType'], { unique: true }) @@ -17,5 +18,5 @@ export class CronJobEntity extends BaseEntity { startedAt: Date; @Column({ type: 'timestamptz', nullable: true }) - completedAt?: Date | null; + completedAt: Date | null; } diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts index 7a73f75e4b..1fd4374fab 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; -import { EscrowCompletionModule } from '../escrow-completion/escrow-completion.module'; -import { IncomingWebhookModule } from '../webhook/webhook-incoming.module'; -import { OutgoingWebhookModule } from '../webhook/webhook-outgoing.module'; +import { AbuseModule } from '../abuse'; +import { EscrowCompletionModule } from '../escrow-completion'; +import { IncomingWebhookModule, OutgoingWebhookModule } from '../webhook'; import { CronJobService } from './cron-job.service'; import { CronJobRepository } from './cron-job.repository'; -import { AbuseModule } from '../abuse/abuse.module'; @Module({ imports: [ diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.repository.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.repository.ts index 20cad40abb..e49e90021c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.repository.ts @@ -1,16 +1,18 @@ import { Injectable } from '@nestjs/common'; -import { CronJobType } from '../../common/enums/cron-job'; -import { BaseRepository } from '../../database/base.repository'; import { DataSource } from 'typeorm'; + +import { BaseRepository } from '../../database'; + +import { CronJobType } from './constants'; import { CronJobEntity } from './cron-job.entity'; @Injectable() export class CronJobRepository extends BaseRepository { - constructor(private dataSource: DataSource) { + constructor(dataSource: DataSource) { super(CronJobEntity, dataSource); } - public async findOneByType(type: CronJobType): Promise { + async findOneByType(type: CronJobType): Promise { return this.findOne({ where: { cronJobType: type } }); } } diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts index 0be8b3b76a..fb493997bf 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.spec.ts @@ -1,134 +1,131 @@ +import { faker } from '@faker-js/faker'; +import { createMock } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; -import { CronJobService } from './cron-job.service'; -import { CronJobRepository } from './cron-job.repository'; + +import { AbuseService } from '../abuse'; +import { EscrowCompletionService } from '../escrow-completion'; +import { IncomingWebhookService, OutgoingWebhookService } from '../webhook'; + +import { CronJobType } from './constants'; import { CronJobEntity } from './cron-job.entity'; -import { CronJobType } from '../../common/enums/cron-job'; -import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; -import { IncomingWebhookService } from '../webhook/webhook-incoming.service'; -import { EscrowCompletionService } from '../escrow-completion/escrow-completion.service'; -import { AbuseService } from '../abuse/abuse.service'; +import { CronJobRepository } from './cron-job.repository'; +import { CronJobService } from './cron-job.service'; +import { generateCronJob } from './fixtures'; + +const mockedCronJobRepository = createMock(); +const mockedIncomingWebhookService = createMock(); +const mockedOutgoingWebhookService = createMock(); +const mockedEscrowCompletionService = createMock(); +const mockedAbuseService = createMock(); describe('CronJobService', () => { let service: CronJobService; - let cronJobRepository: jest.Mocked; - let incomingWebhookService: jest.Mocked; - let outgoingWebhookService: jest.Mocked; - let escrowCompletionService: jest.Mocked; - let abuseService: jest.Mocked; - beforeEach(async () => { + beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ CronJobService, { provide: CronJobRepository, - useValue: { - findOneByType: jest.fn(), - createUnique: jest.fn(), - updateOne: jest.fn(), - }, + useValue: mockedCronJobRepository, }, { provide: IncomingWebhookService, - useValue: { - processPendingIncomingWebhooks: jest.fn(), - }, + useValue: mockedIncomingWebhookService, }, { provide: OutgoingWebhookService, - useValue: { - processPendingOutgoingWebhooks: jest.fn(), - }, + useValue: mockedOutgoingWebhookService, }, { provide: EscrowCompletionService, - useValue: { - processPendingEscrowCompletion: jest.fn(), - processPaidEscrowCompletion: jest.fn(), - processAwaitingPayouts: jest.fn(), - }, + useValue: mockedEscrowCompletionService, }, { provide: AbuseService, - useValue: { - processAbuseRequests: jest.fn(), - processClassifiedAbuses: jest.fn(), - }, + useValue: mockedAbuseService, }, ], }).compile(); service = module.get(CronJobService); - cronJobRepository = module.get(CronJobRepository); - incomingWebhookService = module.get(IncomingWebhookService); - outgoingWebhookService = module.get(OutgoingWebhookService); - escrowCompletionService = module.get(EscrowCompletionService); - abuseService = module.get(AbuseService); + }); + + afterEach(() => { + jest.resetAllMocks(); }); describe('startCronJob', () => { + const now = Date.now(); + + beforeAll(() => { + jest.useFakeTimers({ now }); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(() => { + mockedCronJobRepository.createUnique.mockImplementation(async (v) => v); + mockedCronJobRepository.updateOne.mockImplementation(async (v) => v); + }); + it('should create a new cron job if none exists', async () => { - cronJobRepository.findOneByType.mockResolvedValue(null); - cronJobRepository.createUnique.mockResolvedValue(new CronJobEntity()); + mockedCronJobRepository.findOneByType.mockResolvedValueOnce(null); const result = await service.startCronJob( CronJobType.ProcessPendingIncomingWebhook, ); - expect(cronJobRepository.findOneByType).toHaveBeenCalledWith( + expect(mockedCronJobRepository.findOneByType).toHaveBeenCalledWith( CronJobType.ProcessPendingIncomingWebhook, ); - expect(cronJobRepository.createUnique).toHaveBeenCalledWith({ + const expectedJobData = { cronJobType: CronJobType.ProcessPendingIncomingWebhook, - startedAt: expect.any(Date), - }); + startedAt: new Date(now), + }; + expect(mockedCronJobRepository.createUnique).toHaveBeenCalledTimes(1); + expect(mockedCronJobRepository.createUnique).toHaveBeenCalledWith( + expectedJobData, + ); expect(result).toBeInstanceOf(CronJobEntity); + expect(result).toEqual(expect.objectContaining(expectedJobData)); }); it('should update an existing cron job', async () => { - const existingCronJob = new CronJobEntity(); - existingCronJob.startedAt = new Date(); - cronJobRepository.findOneByType.mockResolvedValue(existingCronJob); + const existingCronJob = generateCronJob(); + mockedCronJobRepository.findOneByType.mockResolvedValueOnce({ + ...existingCronJob, + }); + + const result = await service.startCronJob(existingCronJob.cronJobType); const updatedCronJob = { ...existingCronJob, - startedAt: new Date(), + startedAt: new Date(now), completedAt: null, }; - cronJobRepository.updateOne.mockResolvedValue(updatedCronJob as any); - - const result = await service.startCronJob( - CronJobType.ProcessPendingIncomingWebhook, - ); - - expect(cronJobRepository.findOneByType).toHaveBeenCalledWith( - CronJobType.ProcessPendingIncomingWebhook, + expect(mockedCronJobRepository.findOneByType).toHaveBeenCalledWith( + existingCronJob.cronJobType, ); - expect(cronJobRepository.updateOne).toHaveBeenCalledWith( - expect.objectContaining({ - startedAt: expect.any(Date), - completedAt: null, - }), + expect(mockedCronJobRepository.updateOne).toHaveBeenCalledTimes(1); + expect(mockedCronJobRepository.updateOne).toHaveBeenCalledWith( + updatedCronJob, ); - expect( - Math.abs( - new Date(updatedCronJob.startedAt).getTime() - - existingCronJob.startedAt.getTime(), - ), - ).toBeLessThanOrEqual(1000); expect(result).toEqual(updatedCronJob); }); }); describe('isCronJobRunning', () => { it('should return false if no cron job exists', async () => { - cronJobRepository.findOneByType.mockResolvedValue(null); + mockedCronJobRepository.findOneByType.mockResolvedValueOnce(null); const result = await service.isCronJobRunning( CronJobType.ProcessPendingIncomingWebhook, ); - expect(cronJobRepository.findOneByType).toHaveBeenCalledWith( + expect(mockedCronJobRepository.findOneByType).toHaveBeenCalledWith( CronJobType.ProcessPendingIncomingWebhook, ); expect(result).toBe(false); @@ -137,13 +134,15 @@ describe('CronJobService', () => { it('should return false if the last cron job is completed', async () => { const completedCronJob = new CronJobEntity(); completedCronJob.completedAt = new Date(); - cronJobRepository.findOneByType.mockResolvedValue(completedCronJob); + mockedCronJobRepository.findOneByType.mockResolvedValueOnce( + completedCronJob, + ); const result = await service.isCronJobRunning( CronJobType.ProcessPendingIncomingWebhook, ); - expect(cronJobRepository.findOneByType).toHaveBeenCalledWith( + expect(mockedCronJobRepository.findOneByType).toHaveBeenCalledWith( CronJobType.ProcessPendingIncomingWebhook, ); expect(result).toBe(false); @@ -151,13 +150,15 @@ describe('CronJobService', () => { it('should return true if the last cron job is not completed', async () => { const runningCronJob = new CronJobEntity(); - cronJobRepository.findOneByType.mockResolvedValue(runningCronJob); + mockedCronJobRepository.findOneByType.mockResolvedValueOnce( + runningCronJob, + ); const result = await service.isCronJobRunning( CronJobType.ProcessPendingIncomingWebhook, ); - expect(cronJobRepository.findOneByType).toHaveBeenCalledWith( + expect(mockedCronJobRepository.findOneByType).toHaveBeenCalledWith( CronJobType.ProcessPendingIncomingWebhook, ); expect(result).toBe(true); @@ -165,372 +166,149 @@ describe('CronJobService', () => { }); describe('completeCronJob', () => { - it('should complete a cron job', async () => { - const cronJobEntity = new CronJobEntity(); - - const completedCronJob = { - ...cronJobEntity, - completedAt: new Date(), - }; - - cronJobRepository.updateOne.mockResolvedValue(completedCronJob as any); - - const result = await service.completeCronJob(cronJobEntity); - - expect(cronJobRepository.updateOne).toHaveBeenCalledWith( - expect.objectContaining({ - ...cronJobEntity, - completedAt: expect.any(Date), - }), - ); - - expect(result.completedAt).toBeInstanceOf(Date); + const now = Date.now(); - expect( - Math.abs( - new Date(result.completedAt!).getTime() - - completedCronJob.completedAt.getTime(), - ), - ).toBeLessThanOrEqual(1000); + beforeAll(() => { + jest.useFakeTimers({ now }); }); - it('should throw an error if the cron job is already completed', async () => { - const cronJobEntity = new CronJobEntity(); - cronJobEntity.completedAt = new Date(); - - await expect(service.completeCronJob(cronJobEntity)).rejects.toThrow( - new Error('Cron job is already completed'), - ); - }); - }); - - describe('processPendingIncomingWebhooks', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processPendingIncomingWebhooks(); - - expect( - incomingWebhookService.processPendingIncomingWebhooks, - ).not.toHaveBeenCalled(); - }); - - it('should process pending webhooks and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processPendingIncomingWebhooks(); - - expect( - incomingWebhookService.processPendingIncomingWebhooks, - ).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalled(); - expect(service.completeCronJob).toHaveBeenCalled(); - }); - - it('should complete the cron job after processing', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - incomingWebhookService.processPendingIncomingWebhooks.mockRejectedValue( - new Error('Processing error'), - ); - - await service.processPendingIncomingWebhooks(); - - expect(service.completeCronJob).toHaveBeenCalled(); - }); - }); - - describe('processPendingEscrowCompletion', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processPendingEscrowCompletion(); - - expect( - escrowCompletionService.processPendingEscrowCompletion, - ).not.toHaveBeenCalled(); - }); - - it('should process pending escrow completion and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processPendingEscrowCompletion(); - - expect( - escrowCompletionService.processPendingEscrowCompletion, - ).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalled(); - expect(service.completeCronJob).toHaveBeenCalled(); + afterAll(() => { + jest.useRealTimers(); }); - it('should complete the cron job after processing', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - escrowCompletionService.processPendingEscrowCompletion.mockRejectedValue( - new Error('Processing error'), - ); - - await service.processPendingEscrowCompletion(); - - expect(service.completeCronJob).toHaveBeenCalled(); + beforeEach(() => { + mockedCronJobRepository.updateOne.mockImplementation(async (v) => v); }); - }); - describe('processAwaitingEscrowPayouts', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processAwaitingEscrowPayouts(); - - expect( - escrowCompletionService.processAwaitingPayouts, - ).not.toHaveBeenCalled(); - }); - - it('should process pending escrow completion and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processAwaitingEscrowPayouts(); - - expect(escrowCompletionService.processAwaitingPayouts).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalled(); - expect(service.completeCronJob).toHaveBeenCalled(); - }); - - it('should complete the cron job after processing', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - escrowCompletionService.processAwaitingPayouts.mockRejectedValue( - new Error('Processing error'), - ); - - await service.processAwaitingEscrowPayouts(); - - expect(service.completeCronJob).toHaveBeenCalled(); - }); - }); - - describe('processPaidEscrowCompletion', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processPaidEscrowCompletion(); - - expect( - escrowCompletionService.processPaidEscrowCompletion, - ).not.toHaveBeenCalled(); - }); - - it('should process paid escrow completion and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processPaidEscrowCompletion(); - - expect( - escrowCompletionService.processPaidEscrowCompletion, - ).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalled(); - expect(service.completeCronJob).toHaveBeenCalled(); - }); + it('should complete a cron job', async () => { + const cronJobEntity = new CronJobEntity(); - it('should complete the cron job after processing', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); + const result = await service.completeCronJob({ + ...cronJobEntity, + }); - escrowCompletionService.processPaidEscrowCompletion.mockRejectedValue( - new Error('Processing error'), + const completedCronJob = { + ...cronJobEntity, + completedAt: new Date(now), + }; + expect(mockedCronJobRepository.updateOne).toHaveBeenCalledTimes(1); + expect(mockedCronJobRepository.updateOne).toHaveBeenCalledWith( + completedCronJob, ); - - await service.processPaidEscrowCompletion(); - - expect(service.completeCronJob).toHaveBeenCalled(); + expect(result).toEqual(completedCronJob); }); }); - describe('processPendingOutgoingWebhooks', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processPendingOutgoingWebhooks(); + describe('cron job handlers', () => { + let spyOnIsCronJobRunning: jest.SpyInstance; + let spyOnStartCronJob: jest.SpyInstance; + let spyOnCompleteCronJob: jest.SpyInstance; - expect( - outgoingWebhookService.processPendingOutgoingWebhooks, - ).not.toHaveBeenCalled(); - }); - - it('should process pending outgoing webhooks and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processPendingOutgoingWebhooks(); - - expect( - outgoingWebhookService.processPendingOutgoingWebhooks, - ).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalled(); - expect(service.completeCronJob).toHaveBeenCalled(); - }); - - it('should complete the cron job after processing', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest + beforeAll(() => { + spyOnIsCronJobRunning = jest + .spyOn(service, 'isCronJobRunning') + .mockImplementation(); + spyOnStartCronJob = jest .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest + .mockImplementation(); + spyOnCompleteCronJob = jest .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - outgoingWebhookService.processPendingOutgoingWebhooks.mockRejectedValue( - new Error('Processing error'), - ); - - await service.processPendingOutgoingWebhooks(); - - expect(service.completeCronJob).toHaveBeenCalled(); - }); - }); - - describe('processClassifiedAbuses', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); - - await service.processClassifiedAbuses(); - - expect(abuseService.processClassifiedAbuses).not.toHaveBeenCalled(); + .mockImplementation(); }); - it('should process classified abuses and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - await service.processClassifiedAbuses(); - - expect(abuseService.processClassifiedAbuses).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalledWith( - CronJobType.ProcessClassifiedAbuse, - ); - expect(service.completeCronJob).toHaveBeenCalled(); + afterAll(() => { + spyOnIsCronJobRunning.mockRestore(); + spyOnStartCronJob.mockRestore(); + spyOnCompleteCronJob.mockRestore(); }); - it('should complete the cron job even if processing fails', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); - - abuseService.processClassifiedAbuses.mockRejectedValue( - new Error('Processing error'), - ); + describe.each([ + { + method: 'processPendingIncomingWebhooks', + cronJobType: 'process-pending-incoming-webhook', + processorMock: + mockedIncomingWebhookService.processPendingIncomingWebhooks, + }, + { + method: 'processPendingEscrowCompletion', + cronJobType: 'process-pending-escrow-completion-tracking', + processorMock: mockedEscrowCompletionService.processPendingRecords, + }, + { + method: 'processAwaitingEscrowPayouts', + cronJobType: 'process-awaiting-escrow-payouts', + processorMock: mockedEscrowCompletionService.processAwaitingPayouts, + }, + { + method: 'processPaidEscrowCompletion', + cronJobType: 'process-paid-escrow-completion-tracking', + processorMock: mockedEscrowCompletionService.processPaidEscrows, + }, + { + method: 'processPendingOutgoingWebhooks', + cronJobType: 'process-pending-outgoing-webhook', + processorMock: + mockedOutgoingWebhookService.processPendingOutgoingWebhooks, + }, + { + method: 'processAbuseRequests', + cronJobType: 'process-requested-abuse', + processorMock: mockedAbuseService.processAbuseRequests, + }, + { + method: 'processClassifiedAbuses', + cronJobType: 'process-classified-abuse', + processorMock: mockedAbuseService.processClassifiedAbuses, + }, + ])('$method', ({ method, cronJobType, processorMock }) => { + let cronJob: CronJobEntity; + + beforeEach(() => { + cronJob = generateCronJob({ + cronJobType: cronJobType as CronJobType, + }); + }); - await service.processClassifiedAbuses(); + it('should skip processing if a cron job is already running', async () => { + spyOnIsCronJobRunning.mockResolvedValueOnce(true); - expect(service.completeCronJob).toHaveBeenCalled(); - }); - }); + await (service as any)[method](); - describe('processAbuseRequests', () => { - it('should skip processing if a cron job is already running', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(true); + expect(spyOnIsCronJobRunning).toHaveBeenCalledTimes(1); + expect(spyOnIsCronJobRunning).toHaveBeenCalledWith(cronJobType); + expect(processorMock).not.toHaveBeenCalled(); + expect(spyOnStartCronJob).not.toHaveBeenCalled(); + expect(spyOnCompleteCronJob).not.toHaveBeenCalled(); + }); - await service.processAbuseRequests(); + it(`should process ${cronJobType} and complete the cron job`, async () => { + spyOnIsCronJobRunning.mockResolvedValueOnce(false); + spyOnStartCronJob.mockResolvedValueOnce(cronJob); - expect(abuseService.processAbuseRequests).not.toHaveBeenCalled(); - }); + await (service as any)[method](); - it('should process abuse requests and complete the cron job', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); + expect(service.startCronJob).toHaveBeenCalledTimes(1); + expect(service.startCronJob).toHaveBeenCalledWith(cronJobType); - await service.processAbuseRequests(); + expect(processorMock).toHaveBeenCalledTimes(1); - expect(abuseService.processAbuseRequests).toHaveBeenCalled(); - expect(service.startCronJob).toHaveBeenCalledWith( - CronJobType.ProcessRequestedAbuse, - ); - expect(service.completeCronJob).toHaveBeenCalled(); - }); + expect(service.completeCronJob).toHaveBeenCalledTimes(1); + expect(service.completeCronJob).toHaveBeenCalledWith(cronJob); + }); - it('should complete the cron job even if processing fails', async () => { - jest.spyOn(service, 'isCronJobRunning').mockResolvedValue(false); - jest - .spyOn(service, 'startCronJob') - .mockResolvedValue(new CronJobEntity()); - jest - .spyOn(service, 'completeCronJob') - .mockResolvedValue(new CronJobEntity()); + it('should complete the cron job when processing fails', async () => { + spyOnIsCronJobRunning.mockResolvedValueOnce(false); + spyOnStartCronJob.mockResolvedValueOnce(cronJob); - abuseService.processAbuseRequests.mockRejectedValue( - new Error('Processing error'), - ); + mockedIncomingWebhookService.processPendingIncomingWebhooks.mockRejectedValueOnce( + new Error(faker.lorem.sentence()), + ); - await service.processAbuseRequests(); + await (service as any)[method](); - expect(service.completeCronJob).toHaveBeenCalled(); + expect(service.completeCronJob).toHaveBeenCalledTimes(1); + expect(service.completeCronJob).toHaveBeenCalledWith(cronJob); + }); }); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts index 3ad7cc8035..148c91f33f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/cron-job.service.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; -import { CronJobType } from '../../common/enums/cron-job'; - import logger from '../../logger'; -import { AbuseService } from '../abuse/abuse.service'; -import { EscrowCompletionService } from '../escrow-completion/escrow-completion.service'; -import { IncomingWebhookService } from '../webhook/webhook-incoming.service'; -import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; + +import { AbuseService } from '../abuse'; +import { EscrowCompletionService } from '../escrow-completion'; +import { IncomingWebhookService, OutgoingWebhookService } from '../webhook'; + +import { CronJobType } from './constants'; import { CronJobEntity } from './cron-job.entity'; import { CronJobRepository } from './cron-job.repository'; @@ -27,10 +27,8 @@ export class CronJobService { * Starts a new cron job of the specified type if it's not already running. * If a cron job of the specified type doesn't exist, creates a new one and returns it. * If the cron job already exists, updates its start time and clears the completion time. - * @param cronJobType The type of cron job to start. - * @returns {Promise} A Promise containing the started or updated cron job entity. */ - public async startCronJob(cronJobType: CronJobType): Promise { + async startCronJob(cronJobType: CronJobType): Promise { const cronJob = await this.cronJobRepository.findOneByType(cronJobType); if (!cronJob) { @@ -39,17 +37,14 @@ export class CronJobService { cronJobEntity.startedAt = new Date(); return this.cronJobRepository.createUnique(cronJobEntity); } + cronJob.startedAt = new Date(); cronJob.completedAt = null; + return this.cronJobRepository.updateOne(cronJob); } - /** - * Checks if a cron job of the specified type is currently running. - * @param cronJobType The type of cron job to check. - * @returns {Promise} A Promise indicating whether the cron job is running. - */ - public async isCronJobRunning(cronJobType: CronJobType): Promise { + async isCronJobRunning(cronJobType: CronJobType): Promise { const lastCronJob = await this.cronJobRepository.findOneByType(cronJobType); if (!lastCronJob || lastCronJob.completedAt) { @@ -57,33 +52,17 @@ export class CronJobService { } this.logger.info('Previous cron job is not completed yet', { cronJobType }); + return true; } - /** - * Marks the specified cron job entity as completed. - * Throws an error if the cron job entity is already marked as completed. - * @param cronJobEntity The cron job entity to mark as completed. - * @returns {Promise} A Promise containing the updated cron job entity. - */ - public async completeCronJob( - cronJobEntity: CronJobEntity, - ): Promise { - if (cronJobEntity.completedAt) { - throw new Error('Cron job is already completed'); - } - + async completeCronJob(cronJobEntity: CronJobEntity): Promise { cronJobEntity.completedAt = new Date(); return this.cronJobRepository.updateOne(cronJobEntity); } - /** - * Processes all pending incoming webhooks, marking them as completed upon success. - * Handles any errors by logging them and updating the webhook status. - * @returns {Promise} A promise that resolves when all pending incoming webhooks have been processed. - */ @Cron('*/2 * * * *') - public async processPendingIncomingWebhooks(): Promise { + async processPendingIncomingWebhooks(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessPendingIncomingWebhook, ); @@ -107,14 +86,8 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Processes pending escrow completion tracking to manage escrow lifecycle actions. - * Checks escrow status and, if appropriate, saves results and initiates payouts. - * Handles errors and logs detailed messages. - * @returns {Promise} A promise that resolves when the operation is complete. - */ @Cron('*/2 * * * *') - public async processPendingEscrowCompletion(): Promise { + async processPendingEscrowCompletion(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessPendingEscrowCompletionTracking, ); @@ -129,7 +102,7 @@ export class CronJobService { ); try { - await this.escrowCompletionService.processPendingEscrowCompletion(); + await this.escrowCompletionService.processPendingRecords(); } catch (error) { this.logger.error('Error processing pending escrow completion', error); } @@ -138,13 +111,8 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Processes paid escrow completion tracking, finalizing escrow operations if completed. - * Notifies oracles via callbacks, logs errors, and updates tracking status. - * @returns {Promise} A promise that resolves when the paid escrow tracking has been processed. - */ @Cron('*/2 * * * *') - public async processPaidEscrowCompletion(): Promise { + async processPaidEscrowCompletion(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessPaidEscrowCompletionTracking, ); @@ -159,7 +127,7 @@ export class CronJobService { ); try { - await this.escrowCompletionService.processPaidEscrowCompletion(); + await this.escrowCompletionService.processPaidEscrows(); } catch (error) { this.logger.error('Error processing paid escrow completion', error); } @@ -168,15 +136,14 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Processes pending outgoing webhooks, sending requests to designated URLs. - * Updates each webhook's status upon success, retries or logs errors as necessary. - * @returns {Promise} A promise that resolves once all pending outgoing webhooks have been processed. - */ @Cron('*/2 * * * *') - public async processPendingOutgoingWebhooks(): Promise { - if (await this.isCronJobRunning(CronJobType.ProcessPendingOutgoingWebhook)) + async processPendingOutgoingWebhooks(): Promise { + const isCronJobRunning = await this.isCronJobRunning( + CronJobType.ProcessPendingOutgoingWebhook, + ); + if (isCronJobRunning) { return; + } this.logger.info('Pending outgoing webhooks START'); const cronJob = await this.startCronJob( @@ -193,12 +160,8 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Runs processing of awaiting payouts for escrow completion. - * @returns {Promise} A promise that resolves when the processing is finished. - */ @Cron('*/2 * * * *') - public async processAwaitingEscrowPayouts(): Promise { + async processAwaitingEscrowPayouts(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessAwaitingEscrowPayouts, ); @@ -222,12 +185,8 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Process a pending abuse request. - * @returns {Promise} - Returns a promise that resolves when the operation is complete. - */ @Cron('*/2 * * * *') - public async processAbuseRequests(): Promise { + async processAbuseRequests(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessRequestedAbuse, ); @@ -249,12 +208,8 @@ export class CronJobService { await this.completeCronJob(cronJob); } - /** - * Process a classified abuse. - * @returns {Promise} - Returns a promise that resolves when the operation is complete. - */ @Cron('*/2 * * * *') - public async processClassifiedAbuses(): Promise { + async processClassifiedAbuses(): Promise { const isCronJobRunning = await this.isCronJobRunning( CronJobType.ProcessClassifiedAbuse, ); diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/fixtures/index.ts new file mode 100644 index 0000000000..8e41dcf57b --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/fixtures/index.ts @@ -0,0 +1,20 @@ +import { faker } from '@faker-js/faker'; + +import { CronJobEntity } from '../cron-job.entity'; +import { CronJobType } from '../constants'; + +const cronJobTypes = Object.values(CronJobType); + +export function generateCronJob( + overrides: Partial = {}, +): CronJobEntity { + return { + id: faker.number.int(), + createdAt: faker.date.recent(), + updatedAt: new Date(), + cronJobType: faker.helpers.arrayElement(cronJobTypes), + startedAt: new Date(), + completedAt: null, + ...overrides, + }; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/cron-job/index.ts b/packages/apps/reputation-oracle/server/src/modules/cron-job/index.ts new file mode 100644 index 0000000000..0310bd932c --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/cron-job/index.ts @@ -0,0 +1 @@ +export { CronJobModule } from './cron-job.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/email/module.ts b/packages/apps/reputation-oracle/server/src/modules/email/email.module.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/email/module.ts rename to packages/apps/reputation-oracle/server/src/modules/email/email.module.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/email/index.ts b/packages/apps/reputation-oracle/server/src/modules/email/index.ts new file mode 100644 index 0000000000..0a9ef7531e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/email/index.ts @@ -0,0 +1,3 @@ +export { EmailAction } from './constants'; +export { EmailModule } from './email.module'; +export { EmailService } from './email.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.spec.ts index a225eccdb6..e069287166 100644 --- a/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.spec.ts @@ -4,13 +4,16 @@ import { faker } from '@faker-js/faker'; import { Test, TestingModule } from '@nestjs/testing'; import { MailService } from '@sendgrid/mail'; -import Environment from '../../utils/environment'; -import { SENDGRID_TEMPLATES, SERVICE_NAME } from './sendgrid.service'; +import { EmailConfigService } from '../../config'; import logger from '../../logger'; -import { getTemplateId } from './sendgrid.service'; +import Environment from '../../utils/environment'; import { EmailAction } from './constants'; -import { EmailConfigService } from '../../config/email-config.service'; -import { SendgridEmailService } from './sendgrid.service'; +import { + getTemplateId, + SendgridEmailService, + SENDGRID_TEMPLATES, + SERVICE_NAME, +} from './sendgrid.service'; const mockMailService = { setApiKey: jest.fn(), diff --git a/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.ts b/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.ts index cdd3b37848..eca415c323 100644 --- a/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/email/sendgrid.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { MailService } from '@sendgrid/mail'; -import { EmailService } from './email.service'; -import { EmailAction, SENDGRID_API_KEY_REGEX } from './constants'; +import { EmailConfigService } from '../../config'; import logger from '../../logger'; -import { EmailConfigService } from '../../config/email-config.service'; import Environment from '../../utils/environment'; +import { EmailAction, SENDGRID_API_KEY_REGEX } from './constants'; +import { EmailService } from './email.service'; export const SENDGRID_TEMPLATES = { signup: 'd-ca99cc7410aa4e6dab3e6042d5ecb9a3', diff --git a/packages/apps/reputation-oracle/server/src/modules/encryption/encryption.module.ts b/packages/apps/reputation-oracle/server/src/modules/encryption/encryption.module.ts index 8033e9a750..c1fbba77cb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/encryption/encryption.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/encryption/encryption.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { PgpEncryptionService } from './pgp-encryption.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/encryption/index.ts b/packages/apps/reputation-oracle/server/src/modules/encryption/index.ts new file mode 100644 index 0000000000..e25b493fc5 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/encryption/index.ts @@ -0,0 +1,2 @@ +export { EncryptionModule } from './encryption.module'; +export { PgpEncryptionService } from './pgp-encryption.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.spec.ts index b260663ec0..c21f363a6b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.spec.ts @@ -14,13 +14,12 @@ import { faker } from '@faker-js/faker'; import { Encryption, EncryptionUtils, KVStoreUtils } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; -import { PGPConfigService } from '../../config/pgp-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { PGPConfigService, Web3ConfigService } from '../../config'; import { generateTestnetChainId, mockWeb3ConfigService, } from '../web3/fixtures'; -import { Web3Service } from '../web3/web3.service'; +import { Web3Service } from '../web3'; import { PgpEncryptionService } from './pgp-encryption.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.ts b/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.ts index c533cb5797..03ad18e601 100644 --- a/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/encryption/pgp-encryption.service.ts @@ -6,10 +6,10 @@ import { } from '@human-protocol/sdk'; import { Injectable, OnModuleInit } from '@nestjs/common'; -import { PGPConfigService } from '../../config/pgp-config.service'; +import { PGPConfigService } from '../../config'; import logger from '../../logger'; -import { Web3Service } from '../web3/web3.service'; +import { Web3Service } from '../web3'; @Injectable() export class PgpEncryptionService implements OnModuleInit { diff --git a/packages/apps/reputation-oracle/server/src/common/enums/webhook.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/constants.ts similarity index 78% rename from packages/apps/reputation-oracle/server/src/common/enums/webhook.ts rename to packages/apps/reputation-oracle/server/src/modules/escrow-completion/constants.ts index abdc7e0a78..74385a2045 100644 --- a/packages/apps/reputation-oracle/server/src/common/enums/webhook.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/constants.ts @@ -1,4 +1,5 @@ -// TODO: Move to escrow completion module +export const DEFAULT_BULK_PAYOUT_TX_ID = 1; + export enum EscrowCompletionStatus { PENDING = 'pending', AWAITING_PAYOUTS = 'awaiting_payouts', diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts index 6e86e2556b..04f0c39236 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.entity.ts @@ -1,37 +1,37 @@ import { Column, Entity, Index } from 'typeorm'; -import { BaseEntity } from '../../database/base.entity'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { EscrowCompletionStatus } from '../../common/enums'; -import { ChainId } from '@human-protocol/sdk'; +import { BaseEntity } from '../../database'; + +import { EscrowCompletionStatus } from './constants'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'escrow_completion_tracking' }) @Index(['escrowAddress', 'chainId'], { unique: true }) export class EscrowCompletionEntity extends BaseEntity { @Column({ type: 'int' }) - public chainId: ChainId; + chainId: number; @Column({ type: 'varchar' }) - public escrowAddress: string; + escrowAddress: string; @Column({ type: 'varchar', nullable: true }) - public finalResultsUrl: string; + finalResultsUrl: string | null; @Column({ type: 'varchar', nullable: true }) - public finalResultsHash: string; + finalResultsHash: string | null; @Column({ type: 'varchar', nullable: true }) - public failureDetail: string; + failureDetail: string | null; @Column({ type: 'int' }) - public retriesCount: number; + retriesCount: number; @Column({ type: 'timestamptz' }) - public waitUntil: Date; + waitUntil: Date; @Column({ type: 'enum', enum: EscrowCompletionStatus, }) - public status: EscrowCompletionStatus; + status: EscrowCompletionStatus; } diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts index 281754d006..bff3713fbb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.module.ts @@ -1,16 +1,27 @@ import { Module } from '@nestjs/common'; -import { PayoutModule } from '../payout/payout.module'; -import { ReputationModule } from '../reputation/reputation.module'; -import { Web3Module } from '../web3/web3.module'; +import { ReputationModule } from '../reputation'; +import { StorageModule } from '../storage'; +import { Web3Module } from '../web3'; + +// Using direct import instead of using index.ts due to the circular dependency import { OutgoingWebhookModule } from '../webhook/webhook-outgoing.module'; import { EscrowCompletionRepository } from './escrow-completion.repository'; import { EscrowCompletionService } from './escrow-completion.service'; import { EscrowPayoutsBatchRepository } from './escrow-payouts-batch.repository'; +import { EscrowResultsProcessingModule } from './results-processing/module'; +import { EscrowPayoutsCalculationModule } from './payouts-calculation/module'; @Module({ - imports: [Web3Module, PayoutModule, ReputationModule, OutgoingWebhookModule], + imports: [ + EscrowResultsProcessingModule, + EscrowPayoutsCalculationModule, + StorageModule, + Web3Module, + ReputationModule, + OutgoingWebhookModule, + ], providers: [ EscrowCompletionService, EscrowCompletionRepository, diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.repository.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.repository.ts index 2454e455d4..3e002a88c8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.repository.ts @@ -1,20 +1,22 @@ import { Injectable } from '@nestjs/common'; -import { BaseRepository } from '../../database/base.repository'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ServerConfigService } from '../../config/server-config.service'; + +import { ServerConfigService } from '../../config'; +import { BaseRepository } from '../../database'; + +import { EscrowCompletionStatus } from './constants'; import { EscrowCompletionEntity } from './escrow-completion.entity'; -import { EscrowCompletionStatus } from '../../common/enums'; @Injectable() export class EscrowCompletionRepository extends BaseRepository { constructor( - private dataSource: DataSource, - public readonly serverConfigService: ServerConfigService, + dataSource: DataSource, + private readonly serverConfigService: ServerConfigService, ) { super(EscrowCompletionEntity, dataSource); } - public findByStatus( + findByStatus( status: EscrowCompletionStatus, ): Promise { return this.find({ diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts index db8cc51412..eeabf34dfa 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts @@ -1,793 +1,1121 @@ +jest.mock('@human-protocol/sdk', () => { + const mockedSdk = jest.createMockFromModule< + typeof import('@human-protocol/sdk') + >('@human-protocol/sdk'); + + return { + ...mockedSdk, + ESCROW_BULK_PAYOUT_MAX_ITEMS: 2, + }; +}); + +import { faker } from '@faker-js/faker'; import { createMock } from '@golevelup/ts-jest'; -import { ChainId, EscrowClient, EscrowStatus } from '@human-protocol/sdk'; -import { HttpService } from '@nestjs/axios'; -import { ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; import { - MOCK_ADDRESS, - MOCK_BACKOFF_INTERVAL_SECONDS, - MOCK_FILE_HASH, - MOCK_FILE_URL, - MOCK_MAX_RETRY_COUNT, - MOCK_PRIVATE_KEY, - MOCK_REPUTATION_ORACLE_ADDRESS, - MOCK_WEBHOOK_URL, - mockConfig, -} from '../../../test/constants'; -import { EscrowCompletionStatus } from '../../common/enums/webhook'; -import { Web3Service } from '../web3/web3.service'; -import { EscrowCompletionRepository } from './escrow-completion.repository'; -import { EscrowCompletionEntity } from './escrow-completion.entity'; -import { Web3ConfigService } from '../../config/web3-config.service'; -import { ServerConfigService } from '../../config/server-config.service'; -import { EscrowCompletionService } from './escrow-completion.service'; -import { PostgresErrorCodes } from '../../common/enums/database'; -import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; -import { PayoutService } from '../payout/payout.service'; -import { ReputationService } from '../reputation/reputation.service'; -import { DatabaseError } from '../../common/errors/database'; -import { OutgoingWebhookRepository } from '../webhook/webhook-outgoing.repository'; -import { StorageService } from '../storage/storage.service'; -import { ReputationRepository } from '../reputation/reputation.repository'; -import { ReputationConfigService } from '../../config/reputation-config.service'; -import { PGPConfigService } from '../../config/pgp-config.service'; -import { S3ConfigService } from '../../config/s3-config.service'; -import { EscrowPayoutsBatchRepository } from './escrow-payouts-batch.repository'; -import { PgpEncryptionService } from '../encryption/pgp-encryption.service'; - -jest.mock('@human-protocol/sdk', () => ({ - ...jest.requireActual('@human-protocol/sdk'), - OperatorUtils: { - getOperator: jest.fn().mockImplementation(() => { - return { webhookUrl: MOCK_WEBHOOK_URL }; - }), - }, - TransactionUtils: { - getTransaction: jest.fn(), - }, -})); - -jest.mock('../../utils/backoff', () => ({ - ...jest.requireActual('../../utils/backoff'), - calculateExponentialBackoffMs: jest - .fn() - .mockReturnValue(MOCK_BACKOFF_INTERVAL_SECONDS * 1000), -})); - -const ESCROW_CLIENT_MOCK = { - getJobLauncherAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), - getExchangeOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), - getRecordingOracleAddress: jest.fn().mockResolvedValue(MOCK_ADDRESS), - complete: jest.fn().mockResolvedValue(null), - getStatus: jest.fn().mockResolvedValue(EscrowStatus.Launched), - createBulkPayoutTransaction: jest.fn(), -}; + EscrowClient, + EscrowStatus, + EscrowUtils, + OperatorUtils, +} from '@human-protocol/sdk'; +import { Test } from '@nestjs/testing'; +import * as crypto from 'crypto'; +import stringify from 'json-stable-stringify'; +import _ from 'lodash'; -EscrowClient.build = jest.fn().mockResolvedValue(ESCROW_CLIENT_MOCK); +import { createSignerMock, type SignerMock } from '../../../test/fixtures/web3'; -const MOCK_ADDRESS_PAYOUT = { - address: MOCK_ADDRESS, - amount: BigInt(42), -}; -const mockPayouts = [ - MOCK_ADDRESS_PAYOUT, - ...Array.from({ length: 99 }, (_value, index) => { - const _index = index + 1; - return { - address: `0x${_index}`.padEnd(42, '0'), - amount: BigInt(_index), - }; - }), -]; - -describe('escrowCompletionService', () => { - let escrowCompletionService: EscrowCompletionService, - escrowCompletionRepository: EscrowCompletionRepository, - escrowPayoutsBatchRepository: EscrowPayoutsBatchRepository, - web3ConfigService: Web3ConfigService, - outgoingWebhookService: OutgoingWebhookService, - reputationService: ReputationService, - payoutService: PayoutService; - - const signerMock = { - address: MOCK_ADDRESS, - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), - sendTransaction: jest.fn().mockImplementation(() => ({ - wait: jest.fn(), - })), - signTransaction: jest.fn().mockResolvedValue('signed-tx'), - }; +import { CvatJobType, FortuneJobType } from '../../common/enums'; +import { ServerConfigService } from '../../config'; - const mockConfigService = { - get: jest.fn((key: string) => mockConfig[key]), - getOrThrow: jest.fn((key: string) => { - if (!mockConfig[key]) - throw new Error(`Configuration key "${key}" does not exist`); - return mockConfig[key]; - }), - }; +import { ReputationService } from '../reputation'; +import { StorageService } from '../storage'; +import { OutgoingWebhookService } from '../webhook'; +import { WalletWithProvider, Web3Service } from '../web3'; +import { generateTestnetChainId } from '../web3/fixtures'; - // Mock Web3Service - const mockWeb3Service = { - getSigner: jest.fn().mockReturnValue(signerMock), - calculateGasPrice: jest.fn().mockReturnValue(1000n), - }; +import { EscrowCompletionStatus } from './constants'; +import { + generateEscrowCompletion, + generateEscrowPayoutsBatch, +} from './fixtures/escrow-completion'; +import { EscrowCompletionService } from './escrow-completion.service'; +import { EscrowCompletionRepository } from './escrow-completion.repository'; +import { EscrowPayoutsBatchRepository } from './escrow-payouts-batch.repository'; +import { + AudinoResultsProcessor, + CvatResultsProcessor, + FortuneResultsProcessor, +} from './results-processing'; +import { + AudinoPayoutsCalculator, + CvatPayoutsCalculator, + FortunePayoutsCalculator, +} from './payouts-calculation'; +import { generateFortuneManifest } from './fixtures'; + +const mockServerConfigService = { + maxRetryCount: faker.number.int({ min: 2, max: 5 }), +}; - beforeEach(async () => { +const mockEscrowCompletionRepository = createMock(); +const mockEscrowPayoutsBatchRepository = + createMock(); +const mockWeb3Service = createMock(); +const mockStorageService = createMock(); +const mockOutgoingWebhookService = createMock(); +const mockReputationService = createMock(); +const mockFortuneResultsProcessor = createMock(); +const mockCvatResultsProcessor = createMock(); +const mockFortunePayoutsCalculator = createMock(); +const mockCvatPayoutsCalculator = createMock(); + +const mockedEscrowClient = jest.mocked(EscrowClient); +const mockedEscrowUtils = jest.mocked(EscrowUtils); +const mockedOperatorUtils = jest.mocked(OperatorUtils); + +describe('EscrowCompletionService', () => { + let service: EscrowCompletionService; + + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [ - { - provide: ConfigService, - useValue: { - get: jest.fn((key: string) => mockConfig[key]), - getOrThrow: jest.fn((key: string) => { - if (!mockConfig[key]) { - throw new Error(`Configuration key "${key}" does not exist`); - } - return mockConfig[key]; - }), - }, - }, EscrowCompletionService, { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue(signerMock), - }, + provide: ServerConfigService, + useValue: mockServerConfigService, }, { provide: EscrowCompletionRepository, - useValue: createMock(), + useValue: mockEscrowCompletionRepository, }, { provide: EscrowPayoutsBatchRepository, - useValue: createMock(), + useValue: mockEscrowPayoutsBatchRepository, }, { - provide: OutgoingWebhookRepository, - useValue: createMock(), + provide: Web3Service, + useValue: mockWeb3Service, }, { - provide: ReputationRepository, - useValue: createMock(), + provide: StorageService, + useValue: mockStorageService, }, - // Mocked services { - provide: ConfigService, - useValue: mockConfigService, + provide: OutgoingWebhookService, + useValue: mockOutgoingWebhookService, }, { - provide: Web3Service, - useValue: mockWeb3Service, + provide: ReputationService, + useValue: mockReputationService, }, { - provide: StorageService, - useValue: createMock(), + provide: FortuneResultsProcessor, + useValue: mockFortuneResultsProcessor, + }, + { + provide: CvatResultsProcessor, + useValue: mockCvatResultsProcessor, + }, + { + provide: FortunePayoutsCalculator, + useValue: mockFortunePayoutsCalculator, + }, + { + provide: CvatPayoutsCalculator, + useValue: mockCvatPayoutsCalculator, + }, + { + provide: AudinoResultsProcessor, + useValue: createMock(), + }, + { + provide: AudinoPayoutsCalculator, + useValue: createMock(), }, - OutgoingWebhookService, - PayoutService, - ReputationService, - Web3ConfigService, - ServerConfigService, - ReputationConfigService, - S3ConfigService, - PGPConfigService, - PgpEncryptionService, - { provide: HttpService, useValue: createMock() }, ], }).compile(); - escrowCompletionService = moduleRef.get( - EscrowCompletionService, - ); - - outgoingWebhookService = moduleRef.get( - OutgoingWebhookService, - ); - payoutService = moduleRef.get(PayoutService); - reputationService = moduleRef.get(ReputationService); - escrowCompletionRepository = moduleRef.get(EscrowCompletionRepository); - escrowPayoutsBatchRepository = moduleRef.get(EscrowPayoutsBatchRepository); - web3ConfigService = moduleRef.get(Web3ConfigService); - - jest - .spyOn(web3ConfigService, 'privateKey', 'get') - .mockReturnValue(MOCK_PRIVATE_KEY); + service = moduleRef.get(EscrowCompletionService); }); afterEach(() => { - jest.clearAllMocks(); + jest.resetAllMocks(); }); describe('createEscrowCompletion', () => { - const escrowCompletionEntity: Partial = { - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - status: EscrowCompletionStatus.PENDING, - waitUntil: new Date(), - retriesCount: 0, - }; + it('creates escrow completion tracking record with proper defaults', async () => { + const chainId = generateTestnetChainId(); + const escrowAddress = faker.finance.ethereumAddress(); - it('should successfully create escrow completion with valid DTO', async () => { - jest - .spyOn(escrowCompletionRepository, 'createUnique') - .mockResolvedValue(escrowCompletionEntity as EscrowCompletionEntity); + const now = Date.now(); + jest.useFakeTimers({ now }); - await escrowCompletionService.createEscrowCompletion( - ChainId.LOCALHOST, - MOCK_ADDRESS, - ); + await service.createEscrowCompletion(chainId, escrowAddress); + + jest.useRealTimers(); - expect(escrowCompletionRepository.createUnique).toHaveBeenCalledWith( + expect(mockEscrowCompletionRepository.createUnique).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.createUnique).toHaveBeenCalledWith( expect.objectContaining({ - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - status: EscrowCompletionStatus.PENDING, + chainId, + escrowAddress, + status: 'pending', retriesCount: 0, - waitUntil: expect.any(Date), + waitUntil: new Date(now), }), ); }); }); - describe('processPendingEscrowCompletion', () => { - let processResultsMock: any, calculatePayoutsMock: any; - let escrowCompletionEntity1: Partial, - escrowCompletionEntity2: Partial; + describe('processPendingRecords', () => { + const mockGetEscrowStatus = jest.fn(); + let spyOnCreateEscrowPayoutsBatch: jest.SpyInstance; - beforeEach(() => { - escrowCompletionEntity1 = { - id: 1, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - status: EscrowCompletionStatus.PENDING, - waitUntil: new Date(), - retriesCount: 0, - }; + beforeAll(() => { + spyOnCreateEscrowPayoutsBatch = jest + .spyOn(service as any, 'createEscrowPayoutsBatch') + .mockImplementation(); + }); - escrowCompletionEntity2 = { - id: 2, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - status: EscrowCompletionStatus.PENDING, - waitUntil: new Date(), - retriesCount: 0, - }; + afterAll(() => { + spyOnCreateEscrowPayoutsBatch.mockRestore(); + }); - ESCROW_CLIENT_MOCK.getStatus.mockResolvedValue(EscrowStatus.Pending); + beforeEach(() => { + mockedEscrowClient.build.mockResolvedValue({ + getStatus: mockGetEscrowStatus, + } as unknown as EscrowClient); + }); - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([ - escrowCompletionEntity1 as any, - escrowCompletionEntity2 as any, - ]); + describe('handle failures', () => { + const testError = new Error(faker.lorem.sentence()); - processResultsMock = jest.spyOn(payoutService as any, 'processResults'); - processResultsMock.mockResolvedValue({ - url: MOCK_FILE_URL, - hash: MOCK_FILE_HASH, + beforeEach(() => { + mockGetEscrowStatus.mockRejectedValue(testError); }); - calculatePayoutsMock = jest.spyOn( - payoutService as any, - 'calculatePayouts', - ); - calculatePayoutsMock.mockResolvedValue(mockPayouts); - }); + it('should process multiple items and handle failure for each', async () => { + const pendingRecords = [ + generateEscrowCompletion(EscrowCompletionStatus.PENDING), + generateEscrowCompletion(EscrowCompletionStatus.PENDING), + ]; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce( + _.cloneDeep(pendingRecords), + ); - it('should save results and payouts batches for all of the pending escrows completion', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + await service.processPendingRecords(); - const updateOneEscrowCompletionMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 2, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: pendingRecords[0].id, + retriesCount: pendingRecords[0].retriesCount + 1, + }), + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: pendingRecords[1].id, + retriesCount: pendingRecords[0].retriesCount + 1, + }), + ); + }); - const createPayoutsBatchMock = jest.spyOn( - escrowPayoutsBatchRepository, - 'createUnique', - ); + it('should handle failure for item that has 1 retry left', async () => { + const pendingRecord = generateEscrowCompletion( + EscrowCompletionStatus.PENDING, + ); + pendingRecord.retriesCount = mockServerConfigService.maxRetryCount - 1; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...pendingRecord, + }, + ]); - await escrowCompletionService.processPendingEscrowCompletion(); + await service.processPendingRecords(); - expect(processResultsMock).toHaveBeenCalledTimes(1); - expect(calculatePayoutsMock).toHaveBeenCalledTimes(1); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...pendingRecord, + retriesCount: pendingRecord.retriesCount + 1, + waitUntil: expect.any(Date), + }); + }); - expect(createPayoutsBatchMock).toHaveBeenCalledTimes(2); - expect(createPayoutsBatchMock).toHaveBeenLastCalledWith({ - escrowCompletionTrackingId: escrowCompletionEntity1.id, - payouts: [ + it('should handle failure and set failed status for item that has no retries left', async () => { + const pendingRecord = generateEscrowCompletion( + EscrowCompletionStatus.PENDING, + ); + pendingRecord.retriesCount = mockServerConfigService.maxRetryCount; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ { - ...MOCK_ADDRESS_PAYOUT, - amount: MOCK_ADDRESS_PAYOUT.amount.toString(), + ...pendingRecord, }, - ], - payoutsHash: 'fd316abc331a3eb6c858e9ec87578986f954b366', + ]); + + await service.processPendingRecords(); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...pendingRecord, + failureDetail: `Error message: ${testError.message}`, + status: 'failed', + }); }); + }); + + it('should skip final results and payout batches processing if escrow status is not pending', async () => { + const pendingRecord = generateEscrowCompletion( + EscrowCompletionStatus.PENDING, + ); + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...pendingRecord, + }, + ]); + mockGetEscrowStatus.mockResolvedValue( + faker.helpers.arrayElement([ + EscrowStatus.Launched, + EscrowStatus.Cancelled, + EscrowStatus.Complete, + EscrowStatus.Paid, + EscrowStatus.Partial, + ]), + ); - expect(updateOneEscrowCompletionMock).toHaveBeenCalledTimes(2); + await service.processPendingRecords(); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes(1); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...pendingRecord, + status: EscrowCompletionStatus.AWAITING_PAYOUTS, + }); }); - it('should handle errors and continue processing other entities', async () => { - escrowCompletionEntity1.finalResultsUrl = MOCK_FILE_URL; - escrowCompletionEntity2.finalResultsUrl = MOCK_FILE_URL; + it('should correctly process escrow that has pending status', async () => { + const pendingRecord = generateEscrowCompletion( + EscrowCompletionStatus.PENDING, + ); + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...pendingRecord, + }, + ]); + mockGetEscrowStatus.mockResolvedValue(EscrowStatus.Pending); - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([ - escrowCompletionEntity1 as any, - escrowCompletionEntity2 as any, - ]); + const manifestUrl = faker.internet.url(); + mockedEscrowUtils.getEscrow.mockResolvedValueOnce({ manifestUrl } as any); - jest - .spyOn(payoutService, 'calculatePayouts') - .mockImplementationOnce(() => { - throw new Error('Test error'); - }) // Fails for escrowCompletionEntity1 - .mockResolvedValue([]); // Succeeds for escrowCompletionEntity2 + const fortuneManifest = generateFortuneManifest(); + mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( + fortuneManifest, + ); + const finalResultsUrl = faker.internet.url(); + const finalResultsHash = faker.string.hexadecimal({ length: 42 }); + mockFortuneResultsProcessor.storeResults.mockResolvedValueOnce({ + url: finalResultsUrl, + hash: finalResultsHash, + }); + const calculatedPayouts = [ + { + address: faker.finance.ethereumAddress(), + amount: faker.number.bigInt(), + }, + ]; + mockFortunePayoutsCalculator.calculate.mockResolvedValueOnce( + calculatedPayouts, + ); - await escrowCompletionService.processPendingEscrowCompletion(); + await service.processPendingRecords(); - // Verify that the first entity's error is handled, with retriesCount incremented to 1 - expect(escrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect(mockedEscrowUtils.getEscrow).toHaveBeenCalledWith( + pendingRecord.chainId, + pendingRecord.escrowAddress, + ); + expect(mockStorageService.downloadJsonLikeData).toHaveBeenCalledWith( + manifestUrl, + ); + expect(mockFortuneResultsProcessor.storeResults).toHaveBeenCalledTimes(1); + expect(mockFortuneResultsProcessor.storeResults).toHaveBeenCalledWith( + pendingRecord.chainId, + pendingRecord.escrowAddress, + fortuneManifest, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( expect.objectContaining({ - id: escrowCompletionEntity1.id, - status: EscrowCompletionStatus.PENDING, - retriesCount: 1, // Retries count should be 1 after the error + id: pendingRecord.id, + finalResultsUrl, + finalResultsHash, }), ); - // Verify that the second entity is successfully processed and its status is updated to 'PAID' - expect(escrowCompletionRepository.updateOne).toHaveBeenCalledWith( + + expect(mockFortunePayoutsCalculator.calculate).toHaveBeenCalledTimes(1); + expect(mockFortunePayoutsCalculator.calculate).toHaveBeenCalledWith({ + manifest: fortuneManifest, + finalResultsUrl, + chainId: pendingRecord.chainId, + escrowAddress: pendingRecord.escrowAddress, + }); + expect(spyOnCreateEscrowPayoutsBatch).toHaveBeenCalledTimes(1); + expect(spyOnCreateEscrowPayoutsBatch).toHaveBeenCalledWith( + pendingRecord.id, + calculatedPayouts, + ); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( expect.objectContaining({ - id: escrowCompletionEntity2.id, + id: pendingRecord.id, status: EscrowCompletionStatus.AWAITING_PAYOUTS, - retriesCount: 0, }), ); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes(2); }); - it('should mark the escrow completion as FAILED if retries exceed the threshold', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + it('should prepare multiple batches if amount of receivers exceeds the limit', async () => { + const pendingRecord = generateEscrowCompletion( + EscrowCompletionStatus.PENDING, + ); + pendingRecord.finalResultsUrl = faker.internet.url(); + pendingRecord.finalResultsHash = faker.string.hexadecimal({ length: 42 }); - const error = new Error('Processing error'); - const loggerErrorSpy = jest.spyOn( - escrowCompletionService['logger'], - 'error', + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...pendingRecord, + }, + ]); + mockGetEscrowStatus.mockResolvedValue(EscrowStatus.Pending); + mockedEscrowUtils.getEscrow.mockResolvedValueOnce({} as any); + mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( + generateFortuneManifest(), ); - escrowCompletionEntity1.retriesCount = MOCK_MAX_RETRY_COUNT; - processResultsMock.mockRejectedValueOnce(error); + const firstAddressPayout = { + address: `0x1${faker.finance.ethereumAddress().slice(3)}`, + amount: faker.number.bigInt(), + }; + const secondAddressPayout = { + address: `0x2${faker.finance.ethereumAddress().slice(3)}`, + amount: faker.number.bigInt(), + }; + const thirdAddressPayout = { + address: `0x3${faker.finance.ethereumAddress().slice(3)}`, + amount: faker.number.bigInt(), + }; - await escrowCompletionService.processPendingEscrowCompletion(); + mockFortunePayoutsCalculator.calculate.mockResolvedValueOnce( + faker.helpers.shuffle([ + firstAddressPayout, + secondAddressPayout, + thirdAddressPayout, + ]), + ); - expect(escrowCompletionRepository.updateOne).toHaveBeenCalledWith( + await service.processPendingRecords(); + + expect(spyOnCreateEscrowPayoutsBatch).toHaveBeenCalledTimes(2); + expect(spyOnCreateEscrowPayoutsBatch).toHaveBeenCalledWith( + pendingRecord.id, + [firstAddressPayout, secondAddressPayout], + ); + expect(spyOnCreateEscrowPayoutsBatch).toHaveBeenCalledWith( + pendingRecord.id, + [thirdAddressPayout], + ); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes(1); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( expect.objectContaining({ - status: EscrowCompletionStatus.FAILED, - retriesCount: MOCK_MAX_RETRY_COUNT, + id: pendingRecord.id, + status: EscrowCompletionStatus.AWAITING_PAYOUTS, }), ); - expect(loggerErrorSpy).toHaveBeenCalledWith( - 'Failed to process pending escrow completion', + }); + }); + + describe('createEscrowPayoutsBatch', () => { + it('should create payouts batch with correct data', async () => { + const payoutsBatch = [ { - error, - escrowCompletionEntityId: expect.any(Number), + address: faker.finance.ethereumAddress(), + amount: faker.number.bigInt(), }, + ]; + const escrowCompletionId = faker.number.int(); + + await service['createEscrowPayoutsBatch']( + escrowCompletionId, + payoutsBatch, + ); + + const payoutsWithStringifiedAmount = payoutsBatch.map((b) => ({ + address: b.address, + amount: b.amount.toString(), + })); + + const expectedHash = crypto + .createHash('sha256') + .update(stringify(payoutsWithStringifiedAmount) as string) + .digest('hex'); + + expect( + mockEscrowPayoutsBatchRepository.createUnique, + ).toHaveBeenCalledTimes(1); + expect( + mockEscrowPayoutsBatchRepository.createUnique, + ).toHaveBeenCalledWith( + expect.objectContaining({ + escrowCompletionTrackingId: escrowCompletionId, + payouts: payoutsWithStringifiedAmount, + payoutsHash: expectedHash, + txNonce: undefined, + }), ); }); + }); - it('should skip processResults when finalResultsUrl is not empty', async () => { - escrowCompletionEntity1.finalResultsUrl = MOCK_FILE_URL; + describe('processAwaitingPayouts', () => { + let spyOnProcessPayoutsBatch: jest.SpyInstance; - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + beforeAll(() => { + spyOnProcessPayoutsBatch = jest + .spyOn(service as any, 'processPayoutsBatch') + .mockImplementation(); + }); - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + afterAll(() => { + spyOnProcessPayoutsBatch.mockRestore(); + }); - await escrowCompletionService.processPendingEscrowCompletion(); + describe('handle failures', () => { + const testError = new Error(faker.lorem.sentence()); - expect(updateOneMock).toHaveBeenCalledTimes(1); - expect(processResultsMock).toHaveBeenCalledTimes(0); - expect(calculatePayoutsMock).toHaveBeenCalledTimes(1); - }); + beforeEach(() => { + mockEscrowPayoutsBatchRepository.findForEscrowCompletionTracking.mockResolvedValue( + [generateEscrowPayoutsBatch()], + ); + }); - it('should skip calculatePayouts when escrowStatus is not pending', async () => { - ESCROW_CLIENT_MOCK.getStatus.mockResolvedValue(EscrowStatus.Launched); + it('should process multiple items and handle failure for each', async () => { + const awaitingPayoutsRecords = [ + generateEscrowCompletion(EscrowCompletionStatus.AWAITING_PAYOUTS), + generateEscrowCompletion(EscrowCompletionStatus.AWAITING_PAYOUTS), + ]; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce( + _.cloneDeep(awaitingPayoutsRecords), + ); + spyOnProcessPayoutsBatch.mockRejectedValue(testError); - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + await service.processAwaitingPayouts(); - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 2, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: awaitingPayoutsRecords[0].id, + retriesCount: awaitingPayoutsRecords[0].retriesCount + 1, + }), + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: awaitingPayoutsRecords[1].id, + retriesCount: awaitingPayoutsRecords[1].retriesCount + 1, + }), + ); + }); + + it('should handle failure for item that has 1 retry left', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + awaitingPayoutsRecord.retriesCount = + mockServerConfigService.maxRetryCount - 1; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...awaitingPayoutsRecord, + }, + ]); + spyOnProcessPayoutsBatch.mockRejectedValue(testError); - await escrowCompletionService.processPendingEscrowCompletion(); + await service.processAwaitingPayouts(); - expect(updateOneMock).toHaveBeenCalledTimes(1); - expect(processResultsMock).toHaveBeenCalledTimes(0); - expect(calculatePayoutsMock).toHaveBeenCalledTimes(0); - }); - }); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...awaitingPayoutsRecord, + retriesCount: awaitingPayoutsRecord.retriesCount + 1, + waitUntil: expect.any(Date), + }); + }); - describe('processPaidEscrowCompletion', () => { - let assessEscrowPartiesMock: jest.SpyInstance, - createOutgoingWebhookMock: jest.SpyInstance; - let escrowCompletionEntity1: Partial, - escrowCompletionEntity2: Partial; + it('should handle failure and set failed status for item that has no retries left', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + awaitingPayoutsRecord.retriesCount = + mockServerConfigService.maxRetryCount; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...awaitingPayoutsRecord, + }, + ]); + spyOnProcessPayoutsBatch.mockRejectedValue(testError); - beforeEach(() => { - ESCROW_CLIENT_MOCK.getStatus.mockResolvedValue(EscrowStatus.Partial); - escrowCompletionEntity1 = { - id: 1, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - finalResultsUrl: MOCK_FILE_URL, - finalResultsHash: MOCK_FILE_HASH, - status: EscrowCompletionStatus.PAID, - waitUntil: new Date(), - retriesCount: 0, - }; + await service.processAwaitingPayouts(); - escrowCompletionEntity2 = { - id: 2, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - finalResultsUrl: MOCK_FILE_URL, - finalResultsHash: MOCK_FILE_HASH, - status: EscrowCompletionStatus.PAID, - waitUntil: new Date(), - retriesCount: 0, - }; + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...awaitingPayoutsRecord, + failureDetail: 'Error message: Not all payouts batches succeeded', + status: 'failed', + }); + }); + + it('should not mark as paid if not all payout batches succeeded', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + awaitingPayoutsRecord.retriesCount = + mockServerConfigService.maxRetryCount - 1; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...awaitingPayoutsRecord, + }, + ]); + mockEscrowPayoutsBatchRepository.findForEscrowCompletionTracking.mockResolvedValue( + [generateEscrowPayoutsBatch(), generateEscrowPayoutsBatch()], + ); + spyOnProcessPayoutsBatch.mockRejectedValueOnce(testError); - assessEscrowPartiesMock = jest - .spyOn(reputationService, 'assessEscrowParties') - .mockResolvedValue(); + await service.processAwaitingPayouts(); - createOutgoingWebhookMock = jest - .spyOn(outgoingWebhookService, 'createOutgoingWebhook') - .mockResolvedValue(); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...awaitingPayoutsRecord, + retriesCount: awaitingPayoutsRecord.retriesCount + 1, + waitUntil: expect.any(Date), + }); + }); }); - it('should assess reputation scores and create outgoing webhook for all of the partially paid escrows completion', async () => { - ESCROW_CLIENT_MOCK.getStatus.mockResolvedValueOnce(EscrowStatus.Partial); + it('should correctly process payouts batches', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...awaitingPayoutsRecord, + }, + ]); + const firstPayoutsBatch = generateEscrowPayoutsBatch(); + const secondPayoutsBatch = generateEscrowPayoutsBatch(); + mockEscrowPayoutsBatchRepository.findForEscrowCompletionTracking.mockResolvedValue( + [firstPayoutsBatch, secondPayoutsBatch], + ); - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + await service.processAwaitingPayouts(); - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + expect( + mockEscrowPayoutsBatchRepository.findForEscrowCompletionTracking, + ).toHaveBeenCalledTimes(1); + expect( + mockEscrowPayoutsBatchRepository.findForEscrowCompletionTracking, + ).toHaveBeenCalledWith(awaitingPayoutsRecord.id); - await escrowCompletionService.processPaidEscrowCompletion(); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes(1); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...awaitingPayoutsRecord, + status: 'paid', + }); - expect(updateOneMock).toHaveBeenCalledWith( - expect.objectContaining({ - id: escrowCompletionEntity1.id, - status: EscrowCompletionStatus.COMPLETED, - }), + /** + * This entity is used by reference while processing, + * so if we pass it as is to matcher - it fails + * due to having different status + */ + const expectedEscrowCompletionArg = expect.objectContaining({ + chainId: awaitingPayoutsRecord.chainId, + escrowAddress: awaitingPayoutsRecord.escrowAddress, + finalResultsUrl: awaitingPayoutsRecord.finalResultsUrl, + finalResultsHash: awaitingPayoutsRecord.finalResultsHash, + }); + + expect(spyOnProcessPayoutsBatch).toHaveBeenCalledTimes(2); + expect(spyOnProcessPayoutsBatch).toHaveBeenCalledWith( + expectedEscrowCompletionArg, + firstPayoutsBatch, + ); + expect(spyOnProcessPayoutsBatch).toHaveBeenCalledWith( + expectedEscrowCompletionArg, + secondPayoutsBatch, ); - expect(assessEscrowPartiesMock).toHaveBeenCalledTimes(1); }); + }); - it('should handle errors during entity processing without skipping remaining entities', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([ - escrowCompletionEntity1 as any, - escrowCompletionEntity2 as any, - ]); + describe('processPayoutsBatch', () => { + let mockedSigner: SignerMock; + const mockedCreateBulkPayoutTransaction = jest.fn(); + let mockedRawTransaction: { nonce: number }; - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockImplementationOnce(() => { - throw new Error('Test error'); - }) - .mockResolvedValueOnce(escrowCompletionEntity2 as any); + beforeEach(() => { + mockedSigner = createSignerMock(); + mockWeb3Service.getSigner.mockReturnValueOnce( + mockedSigner as unknown as WalletWithProvider, + ); - await escrowCompletionService.processPaidEscrowCompletion(); + mockedEscrowClient.build.mockResolvedValue({ + createBulkPayoutTransaction: mockedCreateBulkPayoutTransaction, + } as unknown as EscrowClient); - expect(updateOneMock).toHaveBeenCalledTimes(3); - expect(assessEscrowPartiesMock).toHaveBeenCalledTimes(2); + mockedRawTransaction = { + nonce: faker.number.int(), + }; + mockedCreateBulkPayoutTransaction.mockResolvedValueOnce( + mockedRawTransaction, + ); }); - it('should mark the escrow completion as FAILED if retries exceed the threshold', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); - - const error = new Error('Processing error'); - const loggerErrorSpy = jest.spyOn( - escrowCompletionService['logger'], - 'error', + it('should succesfully process payouts batch', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, ); + const payoutsBatch = generateEscrowPayoutsBatch(); - escrowCompletionEntity1.retriesCount = MOCK_MAX_RETRY_COUNT; - assessEscrowPartiesMock.mockRejectedValueOnce(error); + await service['processPayoutsBatch'](awaitingPayoutsRecord, { + ...payoutsBatch, + }); - await escrowCompletionService.processPaidEscrowCompletion(); + expect(mockEscrowPayoutsBatchRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowPayoutsBatchRepository.updateOne).toHaveBeenCalledWith({ + ...payoutsBatch, + txNonce: mockedRawTransaction.nonce, + }); - expect(escrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect(mockedSigner.sendTransaction).toHaveBeenCalledTimes(1); + expect(mockedSigner.sendTransaction).toHaveBeenCalledWith( + mockedRawTransaction, + ); + expect(mockedSigner.__transactionResponse.wait).toHaveBeenCalledTimes(1); + + expect(mockEscrowPayoutsBatchRepository.deleteOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowPayoutsBatchRepository.deleteOne).toHaveBeenCalledWith( expect.objectContaining({ - status: EscrowCompletionStatus.FAILED, - retriesCount: MOCK_MAX_RETRY_COUNT, + id: payoutsBatch.id, }), ); - expect(loggerErrorSpy).toHaveBeenCalledWith( - 'Failed to process paid escrow completion', - { - error, - escrowCompletionEntityId: expect.any(Number), - }, - ); }); - it('should handle duplicate errors when creating outgoing webhooks and not update entity status', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); - - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); - - createOutgoingWebhookMock = jest - .spyOn(outgoingWebhookService, 'createOutgoingWebhook') - .mockImplementation(() => { - throw new DatabaseError( - 'Duplicate entry error', - PostgresErrorCodes.Duplicated, - ); + it('should reset nonce if expired', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + const payoutsBatch = generateEscrowPayoutsBatch(); + + const testError = new Error('Synthetic error'); + (testError as any).code = 'NONCE_EXPIRED'; + + mockedSigner.sendTransaction.mockRejectedValueOnce(testError); + + let thrownError; + try { + await service['processPayoutsBatch'](awaitingPayoutsRecord, { + ...payoutsBatch, }); + } catch (error) { + thrownError = error; + } - await escrowCompletionService.processPaidEscrowCompletion(); + expect(thrownError).toEqual(testError); - expect(createOutgoingWebhookMock).toHaveBeenCalled(); - expect(updateOneMock).not.toHaveBeenCalledWith({ - id: escrowCompletionEntity1.id, - status: EscrowCompletionStatus.COMPLETED, + expect( + mockEscrowPayoutsBatchRepository.updateOne, + ).toHaveBeenNthCalledWith(2, { + ...payoutsBatch, + txNonce: null, }); + + expect(mockEscrowPayoutsBatchRepository.deleteOne).toHaveBeenCalledTimes( + 0, + ); }); - it('should skip assessReputationScores when escrowStatus is not paid', async () => { - ESCROW_CLIENT_MOCK.getStatus.mockResolvedValue(EscrowStatus.Launched); + it('should not update nonce if already set', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + const payoutsBatch = generateEscrowPayoutsBatch(); + payoutsBatch.txNonce = mockedRawTransaction.nonce; - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValue([escrowCompletionEntity1 as any]); + await service['processPayoutsBatch'](awaitingPayoutsRecord, { + ...payoutsBatch, + }); - const updateOneMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + expect(mockEscrowPayoutsBatchRepository.updateOne).toHaveBeenCalledTimes( + 0, + ); + }); - await escrowCompletionService.processPaidEscrowCompletion(); + it('throws when transaction is failed', async () => { + const awaitingPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.AWAITING_PAYOUTS, + ); + const payoutsBatch = generateEscrowPayoutsBatch(); + payoutsBatch.txNonce = mockedRawTransaction.nonce; - expect(updateOneMock).toHaveBeenCalledTimes(1); - expect(assessEscrowPartiesMock).toHaveBeenCalledTimes(0); - expect(createOutgoingWebhookMock).toHaveBeenCalledTimes(2); - }); - }); + const testError = new Error('Synthetic error'); - describe('processAwaitingPayouts', () => { - let escrowCompletionEntity1: Partial; - let processPayoutsBatchMock: jest.SpyInstance; + mockedSigner.__transactionResponse.wait.mockRejectedValueOnce(testError); - beforeEach(() => { - escrowCompletionEntity1 = { - id: 1, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - finalResultsUrl: MOCK_FILE_URL, - finalResultsHash: MOCK_FILE_HASH, - status: EscrowCompletionStatus.AWAITING_PAYOUTS, - waitUntil: new Date(), - retriesCount: 0, - }; + let thrownError; + try { + await service['processPayoutsBatch'](awaitingPayoutsRecord, { + ...payoutsBatch, + }); + } catch (error) { + thrownError = error; + } - processPayoutsBatchMock = jest - .spyOn(escrowCompletionService as any, 'processPayoutsBatch') - .mockResolvedValue(undefined); - }); + expect(thrownError).toEqual(testError); - afterAll(() => { - processPayoutsBatchMock.mockRestore(); + expect(mockEscrowPayoutsBatchRepository.updateOne).toHaveBeenCalledTimes( + 0, + ); + expect(mockEscrowPayoutsBatchRepository.deleteOne).toHaveBeenCalledTimes( + 0, + ); }); + }); - it('should move completion entity to status paid when all payouts finished', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValueOnce([escrowCompletionEntity1 as any]); + describe('processPaidEscrows', () => { + const mockGetEscrowStatus = jest.fn(); + const mockCompleteEscrow = jest.fn(); + let launcherAddress: string; + let exchangeOracleAddress: string; - jest - .spyOn(escrowPayoutsBatchRepository, 'findForEscrowCompletionTracking') - .mockResolvedValueOnce([ - { - escrowCompletionTrackingId: escrowCompletionEntity1.id, - payouts: [MOCK_ADDRESS_PAYOUT], - payoutsHash: 'test-hash', - }, - ] as any); + beforeEach(() => { + mockedEscrowClient.build.mockResolvedValue({ + getStatus: mockGetEscrowStatus, + complete: mockCompleteEscrow, + } as unknown as EscrowClient); + + launcherAddress = faker.finance.ethereumAddress(); + exchangeOracleAddress = faker.finance.ethereumAddress(); + + mockedEscrowUtils.getEscrow.mockResolvedValueOnce({ + launcher: launcherAddress, + exchangeOracle: exchangeOracleAddress, + } as any); + }); - const updateOneEscrowCompletionMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + describe('handle failures', () => { + const testError = new Error(faker.lorem.sentence()); - await escrowCompletionService.processAwaitingPayouts(); + beforeEach(() => { + mockGetEscrowStatus.mockRejectedValue(testError); + }); - expect(processPayoutsBatchMock).toHaveBeenCalledTimes(1); + it('should process multiple items and handle failure for each', async () => { + const paidPayoutsRecords = [ + generateEscrowCompletion(EscrowCompletionStatus.PAID), + generateEscrowCompletion(EscrowCompletionStatus.PAID), + ]; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce( + _.cloneDeep(paidPayoutsRecords), + ); + + await service.processPaidEscrows(); - expect(updateOneEscrowCompletionMock).toHaveBeenCalledTimes(1); - expect(updateOneEscrowCompletionMock).toHaveBeenCalledWith({ - ...escrowCompletionEntity1, - status: EscrowCompletionStatus.PAID, + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 2, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: paidPayoutsRecords[0].id, + retriesCount: paidPayoutsRecords[0].retriesCount + 1, + }), + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith( + expect.objectContaining({ + id: paidPayoutsRecords[1].id, + retriesCount: paidPayoutsRecords[1].retriesCount + 1, + }), + ); }); - }); - it('should handle errors and keep processing other entities', async () => { - jest - .spyOn(escrowCompletionRepository, 'findByStatus') - .mockResolvedValueOnce([escrowCompletionEntity1 as any]); - - const numBathes = 2; - jest - .spyOn(escrowPayoutsBatchRepository, 'findForEscrowCompletionTracking') - .mockResolvedValueOnce( - Array.from( - { length: numBathes }, - () => - ({ - escrowCompletionTrackingId: escrowCompletionEntity1.id, - payouts: [MOCK_ADDRESS_PAYOUT], - payoutsHash: 'test-hash', - }) as any, - ), + it('should handle failure for item that has 1 retry left', async () => { + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, ); + paidPayoutsRecord.retriesCount = + mockServerConfigService.maxRetryCount - 1; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); - processPayoutsBatchMock.mockRejectedValueOnce(new Error('Failed payout')); + await service.processPaidEscrows(); - const updateOneEscrowCompletionMock = jest - .spyOn(escrowCompletionRepository, 'updateOne') - .mockResolvedValue(escrowCompletionEntity1 as any); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + retriesCount: paidPayoutsRecord.retriesCount + 1, + waitUntil: expect.any(Date), + }); + }); - await escrowCompletionService.processAwaitingPayouts(); + it('should handle failure and set failed status for item that has no retries left', async () => { + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, + ); + paidPayoutsRecord.retriesCount = mockServerConfigService.maxRetryCount; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); - expect(processPayoutsBatchMock).toHaveBeenCalledTimes(2); + await service.processPaidEscrows(); - expect(updateOneEscrowCompletionMock).toHaveBeenCalledTimes(1); - expect(updateOneEscrowCompletionMock).toHaveBeenCalledWith({ - ...escrowCompletionEntity1, - retriesCount: 1, + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + failureDetail: `Error message: ${testError.message}`, + status: 'failed', + }); }); - }); - }); - describe('processPayoutsBatch', () => { - const escrowCompletionEntity = { - id: 1, - chainId: ChainId.LOCALHOST, - escrowAddress: MOCK_ADDRESS, - finalResultsUrl: MOCK_FILE_URL, - finalResultsHash: MOCK_FILE_HASH, - status: EscrowCompletionStatus.AWAITING_PAYOUTS, - waitUntil: new Date(), - retriesCount: 0, - }; - const rawTransaction = { - from: MOCK_REPUTATION_ORACLE_ADDRESS, - to: MOCK_ADDRESS, - data: '0xtest-encoded-contact-data', - nonce: 0, - }; + it('should handle failure when no webhook url for oracle', async () => { + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, + ); + paidPayoutsRecord.retriesCount = mockServerConfigService.maxRetryCount; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); + mockGetEscrowStatus.mockResolvedValueOnce(EscrowStatus.Paid); + mockedOperatorUtils.getOperator.mockResolvedValue({ + webhookUrl: '', + } as any); - beforeAll(() => { - ESCROW_CLIENT_MOCK.createBulkPayoutTransaction.mockImplementation( - (...args) => { - const txOptions = args.at(-1); - return { - ...rawTransaction, - nonce: txOptions.nonce ?? rawTransaction.nonce, - }; - }, - ); - }); + await service.processPaidEscrows(); - it('should correctly process payouts batch at first try', async () => { - const updateOneBatchSpy = jest.spyOn( - escrowPayoutsBatchRepository, - 'updateOne', - ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + failureDetail: 'Error message: Webhook url is no set for oracle', + status: 'failed', + }); + }); - const payoutsBatch = { - id: 1, - payouts: [MOCK_ADDRESS_PAYOUT], - payoutsHash: 'test-payouts-hash', - txNonce: null, - }; + it('should handle error when creating webhooks', async () => { + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, + ); + paidPayoutsRecord.retriesCount = mockServerConfigService.maxRetryCount; + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); + mockGetEscrowStatus.mockResolvedValueOnce(EscrowStatus.Paid); + mockedOperatorUtils.getOperator.mockResolvedValue({ + webhookUrl: faker.internet.url(), + } as any); + mockOutgoingWebhookService.createOutgoingWebhook.mockRejectedValueOnce( + testError, + ); - await (escrowCompletionService as any).processPayoutsBatch( - escrowCompletionEntity, - { ...payoutsBatch }, - ); + await service.processPaidEscrows(); - expect(updateOneBatchSpy).toHaveBeenNthCalledWith(1, { - ...payoutsBatch, - txNonce: rawTransaction.nonce, + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + failureDetail: expect.stringContaining( + 'Failed to create outgoing webhook for oracle. Address: 0x', + ), + status: 'failed', + }); }); - expect(signerMock.sendTransaction).toHaveBeenCalledTimes(1); - expect(signerMock.sendTransaction).toHaveBeenLastCalledWith( - rawTransaction, - ); }); - it('should reuse nonce if exists in batch', async () => { - const updateOneBatchSpy = jest.spyOn( - escrowPayoutsBatchRepository, - 'updateOne', - ); + it.each([EscrowStatus.Partial, EscrowStatus.Paid])( + 'should properly complete escrow with status "%s"', + async (escrowStatus) => { + mockGetEscrowStatus.mockResolvedValueOnce(escrowStatus); + const mockGasPrice = faker.number.bigInt(); + mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(mockGasPrice); - const payoutsBatch = { - id: 1, - payouts: [MOCK_ADDRESS_PAYOUT], - payoutsHash: 'test-payouts-hash', - txNonce: 15, - }; + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, + ); + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); - await (escrowCompletionService as any).processPayoutsBatch( - escrowCompletionEntity, - payoutsBatch, - ); + const launcherWebhookUrl = faker.internet.url(); + const exchangeOracleWebhookUrl = faker.internet.url(); + mockedOperatorUtils.getOperator.mockImplementation( + async (_chainId, address) => { + let webhookUrl: string; + switch (address) { + case launcherAddress: + webhookUrl = launcherWebhookUrl; + break; + case exchangeOracleAddress: + webhookUrl = exchangeOracleWebhookUrl; + break; + default: + webhookUrl = faker.internet.url(); + break; + } + return { webhookUrl } as any; + }, + ); - expect(updateOneBatchSpy).toHaveBeenCalledWith({ - ...payoutsBatch, - }); - expect(signerMock.sendTransaction).toHaveBeenCalledTimes(1); - expect(signerMock.sendTransaction).toHaveBeenCalledWith({ - ...rawTransaction, - nonce: payoutsBatch.txNonce, - }); - }); + await service.processPaidEscrows(); - it('should erase tx data when nonce already used', async () => { - const updateOneBatchSpy = jest.spyOn( - escrowPayoutsBatchRepository, - 'updateOne', - ); - const NONCE_EXPIRED_CODE = 'NONCE_EXPIRED'; - signerMock.sendTransaction.mockRejectedValueOnce({ - code: NONCE_EXPIRED_CODE, - }); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + status: 'completed', + }); + expect(mockCompleteEscrow).toHaveBeenCalledWith( + paidPayoutsRecord.escrowAddress, + { + gasPrice: mockGasPrice, + }, + ); + expect(mockReputationService.assessEscrowParties).toHaveBeenCalledTimes( + 1, + ); + expect(mockReputationService.assessEscrowParties).toHaveBeenCalledWith( + paidPayoutsRecord.chainId, + paidPayoutsRecord.escrowAddress, + ); - const payoutsBatch = { - id: 1, - payouts: [MOCK_ADDRESS_PAYOUT], - payoutsHash: 'test-payouts-hash', - txNonce: 15, - }; + const expectedWebhookData = { + chainId: paidPayoutsRecord.chainId, + escrowAddress: paidPayoutsRecord.escrowAddress, + eventType: 'escrow_completed', + }; + expect( + mockOutgoingWebhookService.createOutgoingWebhook, + ).toHaveBeenCalledTimes(2); + expect( + mockOutgoingWebhookService.createOutgoingWebhook, + ).toHaveBeenCalledWith(expectedWebhookData, launcherWebhookUrl); + expect( + mockOutgoingWebhookService.createOutgoingWebhook, + ).toHaveBeenCalledWith(expectedWebhookData, exchangeOracleWebhookUrl); + }, + ); - let thrownError: any; - try { - await (escrowCompletionService as any).processPayoutsBatch( - escrowCompletionEntity, - { ...payoutsBatch }, + it.each([ + EscrowStatus.Cancelled, + EscrowStatus.Pending, + EscrowStatus.Complete, + ])( + 'should not comlete escrow if its status is not partial or paid [%#]', + async (escrowStatus) => { + mockGetEscrowStatus.mockResolvedValueOnce(escrowStatus); + + const paidPayoutsRecord = generateEscrowCompletion( + EscrowCompletionStatus.PAID, ); - } catch (error) { - thrownError = error; - } + mockEscrowCompletionRepository.findByStatus.mockResolvedValueOnce([ + { + ...paidPayoutsRecord, + }, + ]); - expect(thrownError).toBeDefined(); - expect(thrownError.code).toBe(NONCE_EXPIRED_CODE); + mockedOperatorUtils.getOperator.mockResolvedValue({ + webhookUrl: faker.internet.url(), + } as any); - expect(updateOneBatchSpy).toHaveBeenLastCalledWith({ - ...payoutsBatch, - txNonce: undefined, - }); - }); + await service.processPaidEscrows(); + + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledTimes( + 1, + ); + expect(mockEscrowCompletionRepository.updateOne).toHaveBeenCalledWith({ + ...paidPayoutsRecord, + status: 'completed', + }); + expect(mockCompleteEscrow).toHaveBeenCalledTimes(0); + expect(mockReputationService.assessEscrowParties).toHaveBeenCalledTimes( + 0, + ); + }, + ); + }); + + describe('getEscrowResultsProcessor', () => { + it.each(Object.values(FortuneJobType))( + 'should return fortune processor for "%s" job type', + (jobRequestType) => { + expect(service['getEscrowResultsProcessor'](jobRequestType)).toBe( + mockFortuneResultsProcessor, + ); + }, + ); + it.each(Object.values(CvatJobType))( + 'should return cvat processor for "%s" job type', + (jobRequestType) => { + expect(service['getEscrowResultsProcessor'](jobRequestType)).toBe( + mockCvatResultsProcessor, + ); + }, + ); + }); + + describe('getEscrowPayoutsCalculator', () => { + it.each(Object.values(FortuneJobType))( + 'should return fortune calculator for "%s" job type', + (jobRequestType) => { + expect(service['getEscrowPayoutsCalculator'](jobRequestType)).toBe( + mockFortunePayoutsCalculator, + ); + }, + ); + it.each(Object.values(CvatJobType))( + 'should return cvat calculator for "%s" job type', + (jobRequestType) => { + expect(service['getEscrowPayoutsCalculator'](jobRequestType)).toBe( + mockCvatPayoutsCalculator, + ); + }, + ); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts index d0ce93b467..d0a4cdd285 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts @@ -1,35 +1,50 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import crypto from 'crypto'; -import { ethers } from 'ethers'; -import stringify from 'json-stable-stringify'; -import _ from 'lodash'; import { ESCROW_BULK_PAYOUT_MAX_ITEMS, ChainId, EscrowClient, EscrowStatus, + EscrowUtils, OperatorUtils, } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; -import { EscrowCompletionStatus } from '../../common/enums'; -import { ServerConfigService } from '../../config/server-config.service'; -import { EscrowCompletionRepository } from './escrow-completion.repository'; -import { EscrowCompletionEntity } from './escrow-completion.entity'; + +import crypto from 'crypto'; +import { ethers } from 'ethers'; +import stringify from 'json-stable-stringify'; +import _ from 'lodash'; + +import { BACKOFF_INTERVAL_SECONDS } from '../../common/constants'; +import { isDuplicatedError } from '../../database'; +import { JobManifest, JobRequestType } from '../../common/types'; +import { ServerConfigService } from '../../config'; +import logger from '../../logger'; import { calculateExponentialBackoffMs } from '../../utils/backoff'; -import { - BACKOFF_INTERVAL_SECONDS, - DEFAULT_BULK_PAYOUT_TX_ID, -} from '../../common/constants'; -import { PayoutService } from '../payout/payout.service'; -import { ReputationService } from '../reputation/reputation.service'; -import { Web3Service } from '../web3/web3.service'; +import * as manifestUtils from '../../utils/manifest'; + +import { ReputationService } from '../reputation'; +import { StorageService } from '../storage'; +import { Web3Service } from '../web3'; import { OutgoingWebhookService } from '../webhook/webhook-outgoing.service'; -import { isDuplicatedError } from '../../common/errors/database'; -import { CalculatedPayout } from '../payout/payout.interface'; +import { OutgoingWebhookEventType } from '../webhook/types'; + +import { DEFAULT_BULK_PAYOUT_TX_ID, EscrowCompletionStatus } from './constants'; +import { EscrowCompletionRepository } from './escrow-completion.repository'; +import { EscrowCompletionEntity } from './escrow-completion.entity'; import { EscrowPayoutsBatchEntity } from './escrow-payouts-batch.entity'; import { EscrowPayoutsBatchRepository } from './escrow-payouts-batch.repository'; -import logger from '../../logger'; -import { OutgoingWebhookEventType } from '../webhook/types'; +import { + AudinoResultsProcessor, + CvatResultsProcessor, + EscrowResultsProcessor, + FortuneResultsProcessor, +} from './results-processing'; +import { + AudinoPayoutsCalculator, + CvatPayoutsCalculator, + FortunePayoutsCalculator, + EscrowPayoutsCalculator, + CalculatedPayout, +} from './payouts-calculation'; @Injectable() export class EscrowCompletionService { @@ -38,43 +53,35 @@ export class EscrowCompletionService { }); constructor( + private readonly serverConfigService: ServerConfigService, private readonly escrowCompletionRepository: EscrowCompletionRepository, private readonly escrowPayoutsBatchRepository: EscrowPayoutsBatchRepository, private readonly web3Service: Web3Service, + private readonly storageService: StorageService, private readonly outgoingWebhookService: OutgoingWebhookService, - private readonly payoutService: PayoutService, private readonly reputationService: ReputationService, - public readonly serverConfigService: ServerConfigService, + private readonly audinoResultsProcessor: AudinoResultsProcessor, + private readonly cvatResultsProcessor: CvatResultsProcessor, + private readonly fortuneResultsProcessor: FortuneResultsProcessor, + private readonly audinoPayoutsCalculator: AudinoPayoutsCalculator, + private readonly cvatPayoutsCalculator: CvatPayoutsCalculator, + private readonly fortunePayoutsCalculator: FortunePayoutsCalculator, ) {} - /** - * Creates a tracking record for escrow completion in the repository. - * Sets initial status to 'PENDING'. - * @param {ChainId} chainId - The blockchain chain ID. - * @param {string} escrowAddress - The address of the escrow contract. - */ - public async createEscrowCompletion( + async createEscrowCompletion( chainId: ChainId, escrowAddress: string, ): Promise { - let escrowCompletionEntity = new EscrowCompletionEntity(); + const escrowCompletionEntity = new EscrowCompletionEntity(); escrowCompletionEntity.chainId = chainId; escrowCompletionEntity.escrowAddress = escrowAddress; escrowCompletionEntity.status = EscrowCompletionStatus.PENDING; escrowCompletionEntity.waitUntil = new Date(); escrowCompletionEntity.retriesCount = 0; - escrowCompletionEntity = await this.escrowCompletionRepository.createUnique( - escrowCompletionEntity, - ); + await this.escrowCompletionRepository.createUnique(escrowCompletionEntity); } - /** - * Handles errors that occur during escrow completion. - * If retry count is below the maximum, increments retry count and reschedules; otherwise, marks as 'FAILED'. - * @param escrowCompletionEntity - The escrow entity. - * @param failureDetail - Reason for the failure. - */ private async handleEscrowCompletionError( escrowCompletionEntity: EscrowCompletionEntity, failureDetail: string, @@ -98,7 +105,7 @@ export class EscrowCompletionService { await this.escrowCompletionRepository.updateOne(escrowCompletionEntity); } - public async processPendingEscrowCompletion(): Promise { + async processPendingRecords(): Promise { const escrowCompletionEntities = await this.escrowCompletionRepository.findByStatus( EscrowCompletionStatus.PENDING, @@ -115,10 +122,24 @@ export class EscrowCompletionService { escrowCompletionEntity.escrowAddress, ); if (escrowStatus === EscrowStatus.Pending) { + const escrowData = await EscrowUtils.getEscrow( + escrowCompletionEntity.chainId, + escrowCompletionEntity.escrowAddress, + ); + const manifest = + await this.storageService.downloadJsonLikeData( + escrowData.manifestUrl as string, + ); + const jobRequestType = manifestUtils.getJobRequestType(manifest); + if (!escrowCompletionEntity.finalResultsUrl) { - const { url, hash } = await this.payoutService.processResults( + const escrowResultsProcessor = + this.getEscrowResultsProcessor(jobRequestType); + + const { url, hash } = await escrowResultsProcessor.storeResults( escrowCompletionEntity.chainId, escrowCompletionEntity.escrowAddress, + manifest, ); escrowCompletionEntity.finalResultsUrl = url; @@ -128,11 +149,14 @@ export class EscrowCompletionService { ); } - const calculatedPayouts = await this.payoutService.calculatePayouts( - escrowCompletionEntity.chainId, - escrowCompletionEntity.escrowAddress, - escrowCompletionEntity.finalResultsUrl, - ); + const payoutsCalculator = + this.getEscrowPayoutsCalculator(jobRequestType); + const calculatedPayouts = await payoutsCalculator.calculate({ + manifest, + chainId: escrowCompletionEntity.chainId, + escrowAddress: escrowCompletionEntity.escrowAddress, + finalResultsUrl: escrowCompletionEntity.finalResultsUrl, + }); /** * When creating payout batches we need to guarantee deterministic result, @@ -174,20 +198,19 @@ export class EscrowCompletionService { await this.handleEscrowCompletionError( escrowCompletionEntity, - `Error message: ${error.message})`, + `Error message: ${error.message}`, ); continue; } } } - public async processPaidEscrowCompletion(): Promise { + async processPaidEscrows(): Promise { const escrowCompletionEntities = await this.escrowCompletionRepository.findByStatus( EscrowCompletionStatus.PAID, ); - // TODO: Add DB transactions for (const escrowCompletionEntity of escrowCompletionEntities) { try { const { chainId, escrowAddress } = escrowCompletionEntity; @@ -196,26 +219,26 @@ export class EscrowCompletionService { const escrowClient = await EscrowClient.build(signer); const escrowStatus = await escrowClient.getStatus(escrowAddress); - if ([EscrowStatus.Partial, EscrowStatus.Paid].includes(escrowStatus)) { await escrowClient.complete(escrowAddress, { gasPrice: await this.web3Service.calculateGasPrice(chainId), }); - // TODO: Technically it's possible that the escrow completion could occur before the reputation scores are assessed, - // and the app might go down during this window. Currently, there isn’t a clear approach to handle this situation. - // Consider revisiting this section to explore potential solutions to improve resilience in such scenarios. + /** + * This operation can fail and lost, so it's "at most once" + */ await this.reputationService.assessEscrowParties( chainId, escrowAddress, ); } - const oracleAddresses = await Promise.all([ - escrowClient.getJobLauncherAddress(escrowAddress), - escrowClient.getExchangeOracleAddress(escrowAddress), - // escrowClient.getRecordingOracleAddress(escrowAddress), - ]); + const escrowData = await EscrowUtils.getEscrow(chainId, escrowAddress); + + const oracleAddresses: string[] = [ + escrowData.launcher as string, + escrowData.exchangeOracle as string, + ]; const webhookPayload = { chainId, @@ -224,7 +247,6 @@ export class EscrowCompletionService { }; let allWebhooksCreated = true; - for (const oracleAddress of oracleAddresses) { const oracleData = await OperatorUtils.getOperator( chainId, @@ -291,7 +313,7 @@ export class EscrowCompletionService { } } - public async createEscrowPayoutsBatch( + private async createEscrowPayoutsBatch( escrowCompletionId: number, payoutsBatch: CalculatedPayout[], ): Promise { @@ -301,7 +323,7 @@ export class EscrowCompletionService { })); const batchHash = crypto - .createHash('sha1') + .createHash('sha256') .update(stringify(formattedPayouts) as string) .digest('hex'); @@ -315,7 +337,7 @@ export class EscrowCompletionService { ); } - public async processAwaitingPayouts(): Promise { + async processAwaitingPayouts(): Promise { const escrowCompletionEntities = await this.escrowCompletionRepository.findByStatus( EscrowCompletionStatus.AWAITING_PAYOUTS, @@ -383,8 +405,8 @@ export class EscrowCompletionService { escrowCompletionEntity.escrowAddress, Array.from(recipientToAmountMap.keys()), Array.from(recipientToAmountMap.values()), - escrowCompletionEntity.finalResultsUrl, - escrowCompletionEntity.finalResultsHash, + escrowCompletionEntity.finalResultsUrl as string, + escrowCompletionEntity.finalResultsHash as string, DEFAULT_BULK_PAYOUT_TX_ID, false, { @@ -397,10 +419,9 @@ export class EscrowCompletionService { if (!payoutsBatch.txNonce) { payoutsBatch.txNonce = rawTransaction.nonce; + await this.escrowPayoutsBatchRepository.updateOne(payoutsBatch); } - await this.escrowPayoutsBatchRepository.updateOne(payoutsBatch); - try { const transactionResponse = await signer.sendTransaction(rawTransaction); await transactionResponse.wait(); @@ -408,11 +429,51 @@ export class EscrowCompletionService { await this.escrowPayoutsBatchRepository.deleteOne(payoutsBatch); } catch (error) { if (ethers.isError(error, 'NONCE_EXPIRED')) { - delete payoutsBatch.txNonce; + payoutsBatch.txNonce = null; await this.escrowPayoutsBatchRepository.updateOne(payoutsBatch); } throw error; } } + + private getEscrowResultsProcessor( + jobRequestType: JobRequestType, + ): EscrowResultsProcessor { + if (manifestUtils.isFortuneJobType(jobRequestType)) { + return this.fortuneResultsProcessor; + } + + if (manifestUtils.isCvatJobType(jobRequestType)) { + return this.cvatResultsProcessor; + } + + if (manifestUtils.isAudinoJobType(jobRequestType)) { + return this.audinoResultsProcessor; + } + + throw new Error( + `No escrow results processor defined for '${jobRequestType}' jobs`, + ); + } + + private getEscrowPayoutsCalculator( + jobRequestType: JobRequestType, + ): EscrowPayoutsCalculator { + if (manifestUtils.isFortuneJobType(jobRequestType)) { + return this.fortunePayoutsCalculator; + } + + if (manifestUtils.isCvatJobType(jobRequestType)) { + return this.cvatPayoutsCalculator; + } + + if (manifestUtils.isAudinoJobType(jobRequestType)) { + return this.audinoPayoutsCalculator; + } + + throw new Error( + `No escrow payouts calculator defined for '${jobRequestType}' jobs`, + ); + } } diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts index 8b2cd88e22..3501f0bc82 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.entity.ts @@ -1,10 +1,11 @@ import { Column, Entity, Index, ManyToOne } from 'typeorm'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; + import type { EscrowCompletionEntity } from './escrow-completion.entity'; -export type EscrowPayout = { +type EscrowPayout = { address: string; amount: string; }; @@ -13,7 +14,7 @@ export type EscrowPayout = { @Index(['escrowCompletionTrackingId', 'payoutsHash'], { unique: true }) export class EscrowPayoutsBatchEntity extends BaseEntity { @ManyToOne('EscrowCompletionEntity', { onDelete: 'CASCADE' }) - escrowCompletionTracking: EscrowCompletionEntity; + escrowCompletionTracking?: EscrowCompletionEntity; @Column() escrowCompletionTrackingId: number; @@ -23,11 +24,11 @@ export class EscrowPayoutsBatchEntity extends BaseEntity { * No need to query or index this field, just store. */ @Column({ type: 'json' }) - public payouts: EscrowPayout[]; + payouts: EscrowPayout[]; @Column({ type: 'varchar' }) - public payoutsHash: string; + payoutsHash: string; @Column({ type: 'int', nullable: true }) - public txNonce?: number; + txNonce: number | null; } diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.repository.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.repository.ts index a8d57bbbe8..6c30613ff9 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-payouts-batch.repository.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { DataSource } from 'typeorm'; import { EscrowPayoutsBatchEntity } from './escrow-payouts-batch.entity'; @Injectable() export class EscrowPayoutsBatchRepository extends BaseRepository { - constructor(private dataSource: DataSource) { + constructor(dataSource: DataSource) { super(EscrowPayoutsBatchEntity, dataSource); } - public async findForEscrowCompletionTracking( + async findForEscrowCompletionTracking( escrowCompletionTrackingId: number, ): Promise { return this.find({ diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/cvat.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/cvat.ts new file mode 100644 index 0000000000..de660eae41 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/cvat.ts @@ -0,0 +1,16 @@ +import { faker } from '@faker-js/faker'; + +import { CvatJobType } from '../../../common/enums'; +import { CvatManifest } from '../../../common/types'; + +export function generateCvatManifest(): CvatManifest { + return { + annotation: { + type: faker.helpers.arrayElement(Object.values(CvatJobType)), + }, + validation: { + min_quality: faker.number.float({ min: 0.1, max: 0.9 }), + }, + job_bounty: faker.finance.amount({ max: 42 }), + }; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/escrow-completion.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/escrow-completion.ts new file mode 100644 index 0000000000..082da5e20d --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/escrow-completion.ts @@ -0,0 +1,37 @@ +import { faker } from '@faker-js/faker'; + +import { generateTestnetChainId } from '../../web3/fixtures'; + +import { EscrowCompletionStatus } from '../constants'; +import { EscrowCompletionEntity } from '../escrow-completion.entity'; +import { EscrowPayoutsBatchEntity } from '../escrow-payouts-batch.entity'; + +export function generateEscrowCompletion( + status: EscrowCompletionStatus, +): EscrowCompletionEntity { + return { + id: faker.number.int(), + chainId: generateTestnetChainId(), + status, + retriesCount: 0, + waitUntil: new Date(), + failureDetail: null, + finalResultsUrl: null, + finalResultsHash: null, + escrowAddress: faker.finance.ethereumAddress(), + createdAt: faker.date.recent(), + updatedAt: new Date(), + }; +} + +export function generateEscrowPayoutsBatch(): EscrowPayoutsBatchEntity { + return { + id: faker.number.int(), + escrowCompletionTrackingId: faker.number.int(), + payouts: [], + payoutsHash: '', + txNonce: null, + createdAt: faker.date.recent(), + updatedAt: new Date(), + }; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/fortune.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/fortune.ts new file mode 100644 index 0000000000..cf70a76a07 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/fortune.ts @@ -0,0 +1,20 @@ +import { faker } from '@faker-js/faker'; + +import { FortuneJobType } from '../../../common/enums'; +import { FortuneFinalResult, FortuneManifest } from '../../../common/types'; + +export function generateFortuneManifest(): FortuneManifest { + return { + requestType: FortuneJobType.FORTUNE, + fundAmount: Number(faker.finance.amount()), + submissionsRequired: faker.number.int({ min: 2, max: 5 }), + }; +} + +export function generateFortuneSolution(error?: string): FortuneFinalResult { + return { + workerAddress: faker.finance.ethereumAddress(), + solution: faker.string.sample(), + error: error as FortuneFinalResult['error'], + }; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/index.ts new file mode 100644 index 0000000000..912b0121d6 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/fixtures/index.ts @@ -0,0 +1,2 @@ +export * from './fortune'; +export * from './cvat'; diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/index.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/index.ts new file mode 100644 index 0000000000..1db9914762 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/index.ts @@ -0,0 +1,2 @@ +export { EscrowCompletionModule } from './escrow-completion.module'; +export { EscrowCompletionService } from './escrow-completion.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/audino-payouts-calculator.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/audino-payouts-calculator.ts new file mode 100644 index 0000000000..0c6921ed16 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/audino-payouts-calculator.ts @@ -0,0 +1,79 @@ +import { EscrowClient } from '@human-protocol/sdk'; +import { Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; +import type { OverrideProperties } from 'type-fest'; + +import { AUDINO_RESULTS_ANNOTATIONS_FILENAME } from '../../../common/constants'; +import { AudinoAnnotationMeta, AudinoManifest } from '../../../common/types'; + +import { StorageService } from '../../storage'; +import { Web3Service } from '../../web3'; + +import { + CalclulatePayoutsInput, + CalculatedPayout, + EscrowPayoutsCalculator, +} from './types'; + +type CalculateAudinoPayoutsInput = OverrideProperties< + CalclulatePayoutsInput, + { manifest: AudinoManifest } +>; + +@Injectable() +export class AudinoPayoutsCalculator implements EscrowPayoutsCalculator { + constructor( + private readonly storageService: StorageService, + private readonly web3Service: Web3Service, + ) {} + + async calculate({ + manifest, + chainId, + escrowAddress, + }: CalculateAudinoPayoutsInput): Promise { + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + + const intermediateResultsUrl = + await escrowClient.getIntermediateResultsUrl(escrowAddress); + + const annotations = + await this.storageService.downloadJsonLikeData( + `${intermediateResultsUrl}/${AUDINO_RESULTS_ANNOTATIONS_FILENAME}`, + ); + + if (annotations.jobs.length === 0 || annotations.results.length === 0) { + throw new Error('Invalid annotation meta'); + } + + const jobBountyValue = ethers.parseUnits(manifest.job_bounty, 18); + const workersBounties = new Map(); + + for (const job of annotations.jobs) { + const jobFinalResult = annotations.results.find( + (result) => result.id === job.final_result_id, + ); + if ( + jobFinalResult + // && jobFinalResult.annotation_quality >= manifest.validation.min_quality + ) { + const workerAddress = jobFinalResult.annotator_wallet_address; + + const currentWorkerBounty = workersBounties.get(workerAddress) || 0n; + + workersBounties.set( + workerAddress, + currentWorkerBounty + jobBountyValue, + ); + } + } + + return Array.from(workersBounties.entries()).map( + ([workerAddress, bountyAmount]) => ({ + address: workerAddress, + amount: bountyAmount, + }), + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.spec.ts new file mode 100644 index 0000000000..283bfdfbf5 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.spec.ts @@ -0,0 +1,152 @@ +jest.mock('@human-protocol/sdk'); + +import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; +import { EscrowClient } from '@human-protocol/sdk'; +import { Test } from '@nestjs/testing'; +import { ethers } from 'ethers'; +import _ from 'lodash'; + +import { CvatAnnotationMeta } from '../../../common/types'; + +import { StorageService } from '../../storage'; +import { generateTestnetChainId } from '../../web3/fixtures'; +import { Web3Service } from '../../web3'; + +import { CvatPayoutsCalculator } from './cvat-payouts-calculator'; +import { generateCvatManifest } from '../fixtures'; + +const mockedStorageService = createMock(); +const mockedWeb3Service = createMock(); + +const mockedEscrowClient = jest.mocked(EscrowClient); + +describe('CvatPayoutsCalculator', () => { + let calculator: CvatPayoutsCalculator; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + CvatPayoutsCalculator, + { + provide: StorageService, + useValue: mockedStorageService, + }, + { + provide: Web3Service, + useValue: mockedWeb3Service, + }, + ], + }).compile(); + + calculator = moduleRef.get(CvatPayoutsCalculator); + }); + + describe('calculate', () => { + const chainId = generateTestnetChainId(); + const escrowAddress = faker.finance.ethereumAddress(); + + const mockedGetIntermediateResultsUrl = jest + .fn() + .mockImplementation(async () => faker.internet.url()); + + beforeAll(() => { + mockedEscrowClient.build.mockResolvedValue({ + getIntermediateResultsUrl: mockedGetIntermediateResultsUrl, + } as unknown as EscrowClient); + }); + + it('throws when invalid annotation meta downloaded from valid url', async () => { + const intermediateResultsUrl = faker.internet.url(); + mockedGetIntermediateResultsUrl.mockResolvedValueOnce( + intermediateResultsUrl, + ); + + mockedStorageService.downloadJsonLikeData.mockResolvedValueOnce({ + jobs: [], + results: [], + } as CvatAnnotationMeta); + + await expect( + calculator.calculate({ + finalResultsUrl: faker.internet.url(), + chainId, + escrowAddress, + manifest: generateCvatManifest(), + }), + ).rejects.toThrow('Invalid annotation meta'); + + expect(mockedStorageService.downloadJsonLikeData).toHaveBeenCalledWith( + `${intermediateResultsUrl}/validation_meta.json`, + ); + }); + + it('should properly calculate workers bounties', async () => { + const annotators = [ + faker.finance.ethereumAddress(), + faker.finance.ethereumAddress(), + ]; + + const jobsPerAnnotator = faker.number.int({ min: 1, max: 3 }); + + const annotationsMeta: CvatAnnotationMeta = { + jobs: Array.from( + { length: jobsPerAnnotator * annotators.length }, + (_v, index: number) => ({ + job_id: index, + final_result_id: faker.number.int(), + }), + ), + results: [], + }; + for (const job of annotationsMeta.jobs) { + const annotatorIndex = job.job_id % annotators.length; + + annotationsMeta.results.push({ + id: job.final_result_id, + job_id: job.job_id, + annotator_wallet_address: annotators[annotatorIndex], + annotation_quality: faker.number.float(), + }); + } + + // imitate weird case: job w/o result + annotationsMeta.jobs.push({ + job_id: faker.number.int(), + final_result_id: faker.number.int(), + }); + // imitate weird case: result w/o job + annotationsMeta.results.push({ + id: faker.number.int(), + job_id: faker.number.int(), + annotator_wallet_address: faker.helpers.arrayElement(annotators), + annotation_quality: faker.number.float(), + }); + + mockedStorageService.downloadJsonLikeData.mockResolvedValueOnce( + annotationsMeta, + ); + + const manifest = generateCvatManifest(); + + const payouts = await calculator.calculate({ + chainId, + escrowAddress, + manifest, + finalResultsUrl: faker.internet.url(), + }); + + const expectedAmountPerAnnotator = + BigInt(jobsPerAnnotator) * ethers.parseUnits(manifest.job_bounty, 18); + + const expectedPayouts = annotators.map((address) => ({ + address, + amount: expectedAmountPerAnnotator, + })); + + expect(_.sortBy(payouts, 'address')).toEqual( + _.sortBy(expectedPayouts, 'address'), + ); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.ts new file mode 100644 index 0000000000..3131c5a147 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/cvat-payouts-calculator.ts @@ -0,0 +1,80 @@ +import { EscrowClient } from '@human-protocol/sdk'; +import { Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; +import type { OverrideProperties } from 'type-fest'; + +import { CVAT_VALIDATION_META_FILENAME } from '../../../common/constants'; +import { CvatAnnotationMeta, CvatManifest } from '../../../common/types'; + +import { StorageService } from '../../storage'; +import { Web3Service } from '../../web3'; + +import { + CalclulatePayoutsInput, + CalculatedPayout, + EscrowPayoutsCalculator, +} from './types'; + +type CalculateCvatPayoutsInput = OverrideProperties< + CalclulatePayoutsInput, + { manifest: CvatManifest } +>; + +@Injectable() +export class CvatPayoutsCalculator implements EscrowPayoutsCalculator { + constructor( + private readonly storageService: StorageService, + private readonly web3Service: Web3Service, + ) {} + + async calculate({ + manifest, + chainId, + escrowAddress, + }: CalculateCvatPayoutsInput): Promise { + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + + const intermediateResultsUrl = + await escrowClient.getIntermediateResultsUrl(escrowAddress); + + const annotations = + await this.storageService.downloadJsonLikeData( + `${intermediateResultsUrl}/${CVAT_VALIDATION_META_FILENAME}`, + ); + + if (!annotations.jobs.length || !annotations.results.length) { + throw new Error('Invalid annotation meta'); + } + + const jobBountyValue = ethers.parseUnits(manifest.job_bounty, 18); + const workersBounties = new Map(); + + for (const job of annotations.jobs) { + const jobFinalResult = annotations.results.find( + (result) => result.id === job.final_result_id, + ); + // TODO: enable annotation quality validation when ready + if ( + jobFinalResult + // && jobFinalResult.annotation_quality >= manifest.validation.min_quality + ) { + const workerAddress = jobFinalResult.annotator_wallet_address; + + const currentWorkerBounty = workersBounties.get(workerAddress) || 0n; + + workersBounties.set( + workerAddress, + currentWorkerBounty + jobBountyValue, + ); + } + } + + return Array.from(workersBounties.entries()).map( + ([workerAddress, bountyAmount]) => ({ + address: workerAddress, + amount: bountyAmount, + }), + ); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.spec.ts new file mode 100644 index 0000000000..08f8e6fe25 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.spec.ts @@ -0,0 +1,72 @@ +import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; +import { Test } from '@nestjs/testing'; +import { ethers } from 'ethers'; +import _ from 'lodash'; + +import { StorageService } from '../../storage'; + +import { generateFortuneManifest, generateFortuneSolution } from '../fixtures'; +import { FortunePayoutsCalculator } from './fortune-payouts-calculator'; + +const mockedStorageService = createMock(); + +describe('FortunePayoutsCalculator', () => { + let calculator: FortunePayoutsCalculator; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + FortunePayoutsCalculator, + { + provide: StorageService, + useValue: mockedStorageService, + }, + ], + }).compile(); + + calculator = moduleRef.get( + FortunePayoutsCalculator, + ); + }); + + describe('calculate', () => { + it('should properly calculate payouts', async () => { + const validSolutions = [ + generateFortuneSolution(), + generateFortuneSolution(), + ]; + const results = faker.helpers.shuffle([ + ...validSolutions, + generateFortuneSolution('curse_word'), + generateFortuneSolution('duplicated'), + generateFortuneSolution(faker.string.sample()), + ]); + mockedStorageService.downloadJsonLikeData.mockResolvedValueOnce(results); + const resultsUrl = faker.internet.url(); + const manifest = generateFortuneManifest(); + + const payouts = await calculator.calculate({ + chainId: faker.number.int(), + escrowAddress: faker.finance.ethereumAddress(), + finalResultsUrl: resultsUrl, + manifest, + }); + + const expectedPayouts = validSolutions.map((s) => ({ + address: s.workerAddress, + amount: + BigInt(ethers.parseUnits(manifest.fundAmount.toString(), 'ether')) / + BigInt(validSolutions.length), + })); + + expect(_.sortBy(payouts, 'address')).toEqual( + _.sortBy(expectedPayouts, 'address'), + ); + + expect(mockedStorageService.downloadJsonLikeData).toHaveBeenCalledWith( + resultsUrl, + ); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.ts new file mode 100644 index 0000000000..ffeb96261c --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/fortune-payouts-calculator.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; +import type { OverrideProperties } from 'type-fest'; + +import { FortuneFinalResult, FortuneManifest } from '../../../common/types'; + +import { StorageService } from '../../storage'; + +import { + CalclulatePayoutsInput, + CalculatedPayout, + EscrowPayoutsCalculator, +} from './types'; + +type CalculateFortunePayoutsInput = OverrideProperties< + CalclulatePayoutsInput, + { manifest: FortuneManifest } +>; + +@Injectable() +export class FortunePayoutsCalculator implements EscrowPayoutsCalculator { + constructor(private readonly storageService: StorageService) {} + + async calculate({ + manifest, + finalResultsUrl, + }: CalculateFortunePayoutsInput): Promise { + const finalResults = + await this.storageService.downloadJsonLikeData( + finalResultsUrl, + ); + + const recipients = finalResults + .filter((result) => !result.error) + .map((item) => item.workerAddress); + + const payoutAmount = + BigInt(ethers.parseUnits(manifest.fundAmount.toString(), 'ether')) / + BigInt(recipients.length); + + return recipients.map((recipient) => ({ + address: recipient, + amount: payoutAmount, + })); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/index.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/index.ts new file mode 100644 index 0000000000..868e920212 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/index.ts @@ -0,0 +1,4 @@ +export { AudinoPayoutsCalculator } from './audino-payouts-calculator'; +export { CvatPayoutsCalculator } from './cvat-payouts-calculator'; +export { FortunePayoutsCalculator } from './fortune-payouts-calculator'; +export * from './types'; diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/module.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/module.ts new file mode 100644 index 0000000000..7080cb32ae --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; + +import { StorageModule } from '../../storage'; +import { Web3Module } from '../../web3'; + +import { AudinoPayoutsCalculator } from './audino-payouts-calculator'; +import { CvatPayoutsCalculator } from './cvat-payouts-calculator'; +import { FortunePayoutsCalculator } from './fortune-payouts-calculator'; + +@Module({ + imports: [StorageModule, Web3Module], + providers: [ + AudinoPayoutsCalculator, + CvatPayoutsCalculator, + FortunePayoutsCalculator, + ], + exports: [ + AudinoPayoutsCalculator, + CvatPayoutsCalculator, + FortunePayoutsCalculator, + ], +}) +export class EscrowPayoutsCalculationModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/types.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/types.ts new file mode 100644 index 0000000000..faaa20853e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/payouts-calculation/types.ts @@ -0,0 +1,18 @@ +import { ChainId } from '@human-protocol/sdk'; +import { JobManifest } from '../../../common/types'; + +export type CalculatedPayout = { + address: string; + amount: bigint; +}; + +export type CalclulatePayoutsInput = { + manifest: JobManifest; + chainId: ChainId; + escrowAddress: string; + finalResultsUrl: string; +}; + +export interface EscrowPayoutsCalculator { + calculate(input: CalclulatePayoutsInput): Promise; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/audino-results-processor.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/audino-results-processor.ts new file mode 100644 index 0000000000..7d4a489498 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/audino-results-processor.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; + +import { AUDINO_RESULTS_ANNOTATIONS_FILENAME } from '../../../common/constants'; +import { AudinoManifest } from '../../../common/types'; + +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; + +@Injectable() +export class AudinoResultsProcessor extends BaseEscrowResultsProcessor { + constructIntermediateResultsUrl(baseUrl: string): string { + return `${baseUrl}/${AUDINO_RESULTS_ANNOTATIONS_FILENAME}`; + } + + async assertResultsComplete(): Promise { + return; + } + + getFinalResultsFileName(hash: string): string { + return `${hash}.zip`; + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.spec.ts new file mode 100644 index 0000000000..7b07431d43 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.spec.ts @@ -0,0 +1,70 @@ +import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; +import { Test } from '@nestjs/testing'; + +import { PgpEncryptionService } from '../../encryption'; +import { StorageService } from '../../storage'; +import { Web3Service } from '../../web3'; + +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; +import { CvatResultsProcessor } from './cvat-results-processor'; + +const mockedStorageService = createMock(); +const mockedPgpEncryptionService = createMock(); +const mockedWeb3Service = createMock(); + +describe('CvatResultsProcessor', () => { + let processor: CvatResultsProcessor; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + CvatResultsProcessor, + { + provide: StorageService, + useValue: mockedStorageService, + }, + { + provide: PgpEncryptionService, + useValue: mockedPgpEncryptionService, + }, + { + provide: Web3Service, + useValue: mockedWeb3Service, + }, + ], + }).compile(); + + processor = moduleRef.get(CvatResultsProcessor); + }); + + it('should be properly initialized', () => { + expect(processor).toBeDefined(); + expect(processor).toBeInstanceOf(BaseEscrowResultsProcessor); + }); + + describe('constructIntermediateResultsUrl', () => { + it('should return annotations file url', () => { + const baseUrl = faker.internet.url(); + const url = processor['constructIntermediateResultsUrl'](baseUrl); + + expect(url).toBe(`${baseUrl}/resulting_annotations.zip`); + }); + }); + + describe('assertResultsComplete', () => { + it('always passes', async () => { + await expect(processor['assertResultsComplete']()).resolves.not.toThrow(); + }); + }); + + describe('getFinalResultsFileName', () => { + it('should return hash with extension', () => { + const hash = faker.string.hexadecimal({ prefix: '', length: 40 }); + + const name = processor['getFinalResultsFileName'](hash); + + expect(name).toBe(`${hash}.zip`); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.ts new file mode 100644 index 0000000000..25d82a7a7c --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/cvat-results-processor.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; + +import { CVAT_RESULTS_ANNOTATIONS_FILENAME } from '../../../common/constants'; +import { CvatManifest } from '../../../common/types'; + +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; + +@Injectable() +export class CvatResultsProcessor extends BaseEscrowResultsProcessor { + constructIntermediateResultsUrl(baseUrl: string): string { + return `${baseUrl}/${CVAT_RESULTS_ANNOTATIONS_FILENAME}`; + } + + async assertResultsComplete(): Promise { + return; + } + + getFinalResultsFileName(hash: string): string { + return `${hash}.zip`; + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.spec.ts new file mode 100644 index 0000000000..0895ed9e97 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.spec.ts @@ -0,0 +1,180 @@ +jest.mock('@human-protocol/sdk'); + +import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; +import { EscrowClient, EscrowUtils } from '@human-protocol/sdk'; +import { Test } from '@nestjs/testing'; +import * as crypto from 'crypto'; + +import { PgpEncryptionService } from '../../encryption'; +import { StorageService } from '../../storage'; +import { generateTestnetChainId } from '../../web3/fixtures'; +import { Web3Service } from '../../web3'; + +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; + +class TestEscrowResultsProcessor extends BaseEscrowResultsProcessor { + override constructIntermediateResultsUrl = jest.fn(); + + override assertResultsComplete = jest.fn(); + + override getFinalResultsFileName = jest.fn(); +} + +const mockedStorageService = createMock(); +const mockedPgpEncryptionService = createMock(); +const mockedWeb3Service = createMock(); + +const mockedEscrowClient = jest.mocked(EscrowClient); +const mockedEscrowUtils = jest.mocked(EscrowUtils); + +describe('BaseEscrowResultsProcessor', () => { + let processor: TestEscrowResultsProcessor; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + TestEscrowResultsProcessor, + { + provide: StorageService, + useValue: mockedStorageService, + }, + { + provide: PgpEncryptionService, + useValue: mockedPgpEncryptionService, + }, + { + provide: Web3Service, + useValue: mockedWeb3Service, + }, + ], + }).compile(); + + processor = moduleRef.get( + TestEscrowResultsProcessor, + ); + }); + + describe('storeResults', () => { + const mockedGetIntermediateResultsUrl = jest + .fn() + .mockImplementation(async () => faker.internet.url()); + + beforeAll(() => { + mockedEscrowClient.build.mockResolvedValue({ + getIntermediateResultsUrl: mockedGetIntermediateResultsUrl, + } as unknown as EscrowClient); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + it('should fail if downloaded results not complete', async () => { + const testError = new Error(`Error: ${faker.string.ulid()}`); + + processor.assertResultsComplete.mockRejectedValueOnce(testError); + + await expect( + processor.storeResults( + faker.number.int(), + faker.finance.ethereumAddress(), + {}, + ), + ).rejects.toThrow(testError); + }); + + it('should store results as per workflow', async () => { + /** ARRANGE */ + const chainId = generateTestnetChainId(); + const escrowAddress = faker.finance.ethereumAddress(); + + const baseResultsUrl = faker.internet.url(); + mockedGetIntermediateResultsUrl.mockResolvedValueOnce(baseResultsUrl); + + const resultsUrl = `${baseResultsUrl}/${faker.system.fileName()}`; + processor.constructIntermediateResultsUrl.mockReturnValueOnce(resultsUrl); + + const jobResult = faker.number.int(); + const resultsFileContent = Buffer.from(jobResult.toString()); + mockedStorageService.downloadFile.mockResolvedValueOnce( + resultsFileContent, + ); + + processor.assertResultsComplete.mockResolvedValueOnce(undefined); + + processor.assertResultsComplete.mockImplementationOnce( + async (result, manifest) => { + if (Number(result) !== manifest.resultToAssert) { + throw new Error('Incomplete test result'); + } + }, + ); + + const jobLauncherAddress = faker.finance.ethereumAddress(); + mockedEscrowUtils.getEscrow.mockResolvedValueOnce({ + launcher: jobLauncherAddress, + } as any); + + const encryptedResult = faker.string.ulid(); + mockedPgpEncryptionService.encrypt.mockResolvedValueOnce(encryptedResult); + + const encryptedResultHash = crypto + .createHash('sha256') + .update(encryptedResult) + .digest('hex'); + const storedResultsFileName = `${encryptedResultHash}.${faker.system.fileExt()}`; + processor.getFinalResultsFileName.mockReturnValueOnce( + storedResultsFileName, + ); + + const storedResultsUrl = faker.internet.url(); + mockedStorageService.uploadData.mockResolvedValueOnce(storedResultsUrl); + + /** ACT */ + const manifest = { resultToAssert: jobResult }; + const storedResultMeta = await processor.storeResults( + chainId, + escrowAddress, + manifest, + ); + + /** ASSERT */ + expect(storedResultMeta.url).toBe(storedResultsUrl); + expect(storedResultMeta.hash).toBe(encryptedResultHash); + + expect(mockedGetIntermediateResultsUrl).toHaveBeenCalledWith( + escrowAddress, + ); + expect(processor.constructIntermediateResultsUrl).toHaveBeenCalledWith( + baseResultsUrl, + ); + expect(mockedStorageService.downloadFile).toHaveBeenCalledWith( + resultsUrl, + ); + expect(mockedEscrowUtils.getEscrow).toHaveBeenCalledWith( + chainId, + escrowAddress, + ); + + expect(mockedPgpEncryptionService.encrypt).toHaveBeenCalledTimes(1); + expect(mockedPgpEncryptionService.encrypt).toHaveBeenCalledWith( + resultsFileContent, + chainId, + [jobLauncherAddress], + ); + + expect(processor.getFinalResultsFileName).toHaveBeenCalledTimes(1); + expect(processor.getFinalResultsFileName).toHaveBeenCalledWith( + encryptedResultHash, + ); + + expect(mockedStorageService.uploadData).toHaveBeenCalledTimes(1); + expect(mockedStorageService.uploadData).toHaveBeenCalledWith( + encryptedResult, + storedResultsFileName, + 'text/plain', + ); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.ts new file mode 100644 index 0000000000..fa29f5cd88 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/escrow-results-processor.ts @@ -0,0 +1,89 @@ +import { ChainId, EscrowClient, EscrowUtils } from '@human-protocol/sdk'; +import { Injectable } from '@nestjs/common'; +import crypto from 'crypto'; + +import { ContentType } from '../../../common/enums'; +import { JobManifest } from '../../../common/types'; + +import { PgpEncryptionService } from '../../encryption'; +import { StorageService } from '../../storage'; +import { Web3Service } from '../../web3'; + +type EscrowFinalResultsDetails = { + url: string; + hash: string; +}; + +export interface EscrowResultsProcessor { + storeResults( + chainId: ChainId, + escrowAddress: string, + manifest: JobManifest, + ): Promise; +} + +@Injectable() +export abstract class BaseEscrowResultsProcessor + implements EscrowResultsProcessor +{ + constructor( + private readonly storageService: StorageService, + private readonly pgpEncryptionService: PgpEncryptionService, + private readonly web3Service: Web3Service, + ) {} + + async storeResults( + chainId: ChainId, + escrowAddress: string, + manifest: TManifest, + ): Promise { + const signer = this.web3Service.getSigner(chainId); + const escrowClient = await EscrowClient.build(signer); + + /** + * For some job types it's direct url, + * but for some it's url to bucket with files + */ + const baseUrl = await escrowClient.getIntermediateResultsUrl(escrowAddress); + const intermediateResultsUrl = + this.constructIntermediateResultsUrl(baseUrl); + + const fileContent = await this.storageService.downloadFile( + intermediateResultsUrl, + ); + + await this.assertResultsComplete(fileContent, manifest); + + const escrowData = await EscrowUtils.getEscrow(chainId, escrowAddress); + + const encryptedResults = await this.pgpEncryptionService.encrypt( + fileContent, + chainId, + [escrowData.launcher as string], + ); + + const hash = crypto + .createHash('sha256') + .update(encryptedResults) + .digest('hex'); + + const fileName = this.getFinalResultsFileName(hash); + + const url = await this.storageService.uploadData( + encryptedResults, + fileName, + ContentType.PLAIN_TEXT, + ); + + return { url, hash }; + } + + protected abstract constructIntermediateResultsUrl(baseUrl: string): string; + + protected abstract assertResultsComplete( + resultsFileContent: Buffer, + manifest: TManifest, + ): Promise; + + protected abstract getFinalResultsFileName(hash: string): string; +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.spec.ts new file mode 100644 index 0000000000..7cdb95f496 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.spec.ts @@ -0,0 +1,131 @@ +import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; +import { Test } from '@nestjs/testing'; + +import { FortuneFinalResult } from '../../../common/types'; + +import { PgpEncryptionService } from '../../encryption'; +import { StorageService } from '../../storage'; +import { Web3Service } from '../../web3'; + +import { generateFortuneManifest, generateFortuneSolution } from '../fixtures'; +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; +import { FortuneResultsProcessor } from './fortune-results-processor'; + +const mockedStorageService = createMock(); +const mockedPgpEncryptionService = createMock(); +const mockedWeb3Service = createMock(); + +describe('FortuneResultsProcessor', () => { + let processor: FortuneResultsProcessor; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + FortuneResultsProcessor, + { + provide: StorageService, + useValue: mockedStorageService, + }, + { + provide: PgpEncryptionService, + useValue: mockedPgpEncryptionService, + }, + { + provide: Web3Service, + useValue: mockedWeb3Service, + }, + ], + }).compile(); + + processor = moduleRef.get(FortuneResultsProcessor); + }); + + it('should be properly initialized', () => { + expect(processor).toBeDefined(); + expect(processor).toBeInstanceOf(BaseEscrowResultsProcessor); + }); + + describe('constructIntermediateResultsUrl', () => { + it('should return intermediate results url as is', () => { + const baseUrl = faker.internet.url(); + const url = processor['constructIntermediateResultsUrl'](baseUrl); + + expect(url).toBe(baseUrl); + }); + }); + + describe('assertResultsComplete', () => { + const testManifest = generateFortuneManifest(); + + it('throws if results is not json', async () => { + await expect( + processor['assertResultsComplete']( + Buffer.from(faker.lorem.words()), + testManifest, + ), + ).rejects.toThrow('Failed to parse results data'); + }); + + it('throws if results is not array', async () => { + await expect( + processor['assertResultsComplete']( + Buffer.from(JSON.stringify({})), + testManifest, + ), + ).rejects.toThrow('No intermediate results found'); + }); + + it('throws if results is empty array', async () => { + await expect( + processor['assertResultsComplete']( + Buffer.from(JSON.stringify([])), + testManifest, + ), + ).rejects.toThrow('No intermediate results found'); + }); + + it('throws if not all submissions sent', async () => { + const solutions: FortuneFinalResult[] = Array.from( + { length: testManifest.submissionsRequired }, + () => generateFortuneSolution(), + ); + solutions.pop(); + solutions.push(generateFortuneSolution(faker.string.sample())); + + await expect( + processor['assertResultsComplete']( + Buffer.from(JSON.stringify(solutions)), + testManifest, + ), + ).rejects.toThrow('Not all required solutions have been sent'); + }); + + it('passes when all solutions sent', async () => { + const solutions: FortuneFinalResult[] = Array.from( + { length: testManifest.submissionsRequired * 2 }, + (i: number) => + generateFortuneSolution( + i % 2 === 0 ? faker.string.sample() : undefined, + ), + ); + + await expect( + processor['assertResultsComplete']( + Buffer.from(JSON.stringify(solutions)), + testManifest, + ), + ).resolves.not.toThrow(); + }); + }); + + describe('getFinalResultsFileName', () => { + it('should return hash with extension', () => { + const hash = faker.string.hexadecimal({ prefix: '', length: 40 }); + + const name = processor['getFinalResultsFileName'](hash); + + expect(name).toBe(`${hash}.json`); + }); + }); +}); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.ts new file mode 100644 index 0000000000..92ff8d81ef --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/fortune-results-processor.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; + +import { FortuneFinalResult, FortuneManifest } from '../../../common/types'; + +import { BaseEscrowResultsProcessor } from './escrow-results-processor'; + +@Injectable() +export class FortuneResultsProcessor extends BaseEscrowResultsProcessor { + protected constructIntermediateResultsUrl(baseUrl: string): string { + return baseUrl; + } + + protected async assertResultsComplete( + resultsFileContent: Buffer, + manifest: FortuneManifest, + ): Promise { + let intermediateResults: FortuneFinalResult[]; + try { + intermediateResults = JSON.parse(resultsFileContent.toString()); + } catch (_error) { + throw new Error('Failed to parse results data'); + } + + if (!intermediateResults.length) { + throw new Error('No intermediate results found'); + } + + const validResults = intermediateResults.filter((result) => !result.error); + if (validResults.length < manifest.submissionsRequired) { + throw new Error('Not all required solutions have been sent'); + } + } + + protected getFinalResultsFileName(hash: string): string { + return `${hash}.json`; + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/index.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/index.ts new file mode 100644 index 0000000000..0083a791b7 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/index.ts @@ -0,0 +1,4 @@ +export { AudinoResultsProcessor } from './audino-results-processor'; +export { CvatResultsProcessor } from './cvat-results-processor'; +export { EscrowResultsProcessor } from './escrow-results-processor'; +export { FortuneResultsProcessor } from './fortune-results-processor'; diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/module.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/module.ts new file mode 100644 index 0000000000..50f88845b2 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/results-processing/module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; + +import { EncryptionModule } from '../../encryption'; +import { StorageModule } from '../../storage'; +import { Web3Module } from '../../web3'; + +import { AudinoResultsProcessor } from './audino-results-processor'; +import { CvatResultsProcessor } from './cvat-results-processor'; +import { FortuneResultsProcessor } from './fortune-results-processor'; + +@Module({ + imports: [EncryptionModule, StorageModule, Web3Module], + providers: [ + AudinoResultsProcessor, + CvatResultsProcessor, + FortuneResultsProcessor, + ], + exports: [ + AudinoResultsProcessor, + CvatResultsProcessor, + FortuneResultsProcessor, + ], +}) +export class EscrowResultsProcessingModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/health/health.controller.spec.ts b/packages/apps/reputation-oracle/server/src/modules/health/health.controller.spec.ts index 6bfa01ba01..cb4d847134 100644 --- a/packages/apps/reputation-oracle/server/src/modules/health/health.controller.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/health/health.controller.spec.ts @@ -8,7 +8,7 @@ import { TypeOrmHealthIndicator, } from '@nestjs/terminus'; import { nestLoggerOverride } from '../../logger'; -import { ServerConfigService } from '../../config/server-config.service'; +import { ServerConfigService } from '../../config'; import { HealthController } from './health.controller'; const mockServerConfigService = { diff --git a/packages/apps/reputation-oracle/server/src/modules/health/health.controller.ts b/packages/apps/reputation-oracle/server/src/modules/health/health.controller.ts index 57dc057ab6..02e401e981 100644 --- a/packages/apps/reputation-oracle/server/src/modules/health/health.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/health/health.controller.ts @@ -9,9 +9,9 @@ import { } from '@nestjs/terminus'; import packageJson from '../../../package.json'; import { Public } from '../../common/decorators'; -import { ServerConfigService } from '../../config/server-config.service'; -import { PingResponseDto } from './dto/ping-response.dto'; +import { ServerConfigService } from '../../config'; import Environment from '../../utils/environment'; +import { PingResponseDto } from './dto/ping-response.dto'; @Public() @ApiTags('Health') diff --git a/packages/apps/reputation-oracle/server/src/modules/health/index.ts b/packages/apps/reputation-oracle/server/src/modules/health/index.ts new file mode 100644 index 0000000000..1f435cb57d --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/health/index.ts @@ -0,0 +1 @@ +export { HealthModule } from './health.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/fixtures/index.ts similarity index 82% rename from packages/apps/reputation-oracle/server/src/modules/kyc/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/kyc/fixtures/index.ts index a153c62cbe..88c8f2fefc 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/fixtures/index.ts @@ -1,8 +1,8 @@ import { faker } from '@faker-js/faker'; -import { KycConfigService } from '../../config/kyc-config.service'; -import { KycEntity } from './kyc.entity'; -import { KycStatus } from './constants'; +import { KycConfigService } from '../../../config'; +import { KycEntity } from '../kyc.entity'; +import { KycStatus } from '../constants'; export const mockKycConfigService: Omit = { apiPrivateKey: faker.string.alphanumeric(), diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/index.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/index.ts new file mode 100644 index 0000000000..5ea2ee9fe2 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/index.ts @@ -0,0 +1 @@ +export { KycModule } from './kyc.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts index d994e680c8..cdabe472c1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc-webhook-auth.guard.ts @@ -8,7 +8,7 @@ import { import { createHmac } from 'crypto'; import { Request } from 'express'; -import { KycConfigService } from '../../config/kyc-config.service'; +import { KycConfigService } from '../../config'; @Injectable() export class KycWebhookAuthGuard implements CanActivate { diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts index 3eea134de4..fc6d4e928a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.controller.ts @@ -18,13 +18,13 @@ import { } from '@nestjs/swagger'; import { Public } from '../../common/decorators'; -import { RequestWithUser } from '../../common/interfaces/request'; +import { RequestWithUser } from '../../common/types'; import { StartSessionResponseDto, KycSignedAddressDto, UpdateKycStatusDto, } from './kyc.dto'; -import { KycErrorFilter } from './kyc.error.filter'; +import { KycErrorFilter } from './kyc.error-filter'; import { KycService } from './kyc.service'; import { KycWebhookAuthGuard } from './kyc-webhook-auth.guard'; @@ -49,7 +49,7 @@ export class KycController { async startKyc( @Req() request: RequestWithUser, ): Promise { - return await this.kycService.initSession(request.user); + return await this.kycService.initSession(request.user.id); } @ApiOperation({ @@ -101,7 +101,7 @@ export class KycController { @Req() request: RequestWithUser, ): Promise { const signedAddressData = await this.kycService.getSignedAddress( - request.user, + request.user.id, ); return signedAddressData; } diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts index ee2eaff5f7..9689a9af7b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, JoinColumn, OneToOne } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import { KycStatus } from './constants'; import type { UserEntity } from '../user'; diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.error.filter.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.error-filter.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/kyc/kyc.error.filter.ts rename to packages/apps/reputation-oracle/server/src/modules/kyc/kyc.error-filter.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts index 574ac715ff..0892c9b8a8 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.module.ts @@ -2,7 +2,7 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { UserModule } from '../user'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { KycController } from './kyc.controller'; import { KycService } from './kyc.service'; import { KycRepository } from './kyc.repository'; diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.repository.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.repository.ts index 744bd6e69b..b34ccddefc 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { KycEntity } from './kyc.entity'; @Injectable() @@ -10,10 +10,16 @@ export class KycRepository extends BaseRepository { super(KycEntity, dataSource); } + async findOneByUserId(userId: number): Promise { + const kycEntity = await this.findOne({ where: { userId } }); + + return kycEntity; + } + async findOneBySessionId(sessionId: string): Promise { const kycEntity = await this.findOne({ where: { - sessionId: sessionId, + sessionId, }, }); diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts index 23b11e68e3..b04dcf9548 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.spec.ts @@ -4,20 +4,17 @@ import { HttpService } from '@nestjs/axios'; import { Test } from '@nestjs/testing'; import { ethers } from 'ethers'; -import { KycConfigService } from '../../config/kyc-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; - import { generateEthWallet } from '../../../test/fixtures/web3'; import { createHttpServiceMock, createHttpServiceResponse, } from '../../../test/mock-creators/nest'; - +import { KycConfigService, Web3ConfigService } from '../../config'; import { KycStatus } from '../kyc/constants'; -import { UserEntity } from '../user'; +import { UserRepository } from '../user'; import { generateWorkerUser } from '../user/fixtures'; import { mockWeb3ConfigService } from '../web3/fixtures'; -import { Web3Service } from '../web3/web3.service'; +import { Web3Service } from '../web3'; import { UpdateKycStatusDto } from './kyc.dto'; import { KycEntity } from './kyc.entity'; @@ -29,6 +26,7 @@ import { generateKycEntity, mockKycConfigService } from './fixtures'; const mockHttpService = createHttpServiceMock(); const mockKycRepository = createMock(); +const mockUserRepository = createMock(); describe('Kyc Service', () => { let kycService: KycService; @@ -42,6 +40,7 @@ describe('Kyc Service', () => { { provide: HttpService, useValue: mockHttpService }, { provide: KycConfigService, useValue: mockKycConfigService }, { provide: KycRepository, useValue: mockKycRepository }, + { provide: UserRepository, useValue: mockUserRepository }, { provide: Web3ConfigService, useValue: mockWeb3ConfigService, @@ -62,63 +61,59 @@ describe('Kyc Service', () => { describe('initSession', () => { describe('Should return existing session url if user already has an active Kyc session, and is waiting for user to make an action', () => { it('status is none', async () => { - const mockUserEntity = generateWorkerUser(); - mockUserEntity.kyc = generateKycEntity( - mockUserEntity.id, + const mockKycEntity = generateKycEntity( + faker.number.int(), KycStatus.NONE, ); + mockKycRepository.findOneByUserId.mockResolvedValueOnce(mockKycEntity); - const result = await kycService.initSession( - mockUserEntity as UserEntity, - ); + const result = await kycService.initSession(mockKycEntity.userId); expect(result).toEqual({ - url: mockUserEntity.kyc.url, + url: mockKycEntity.url, }); }); it('status is resubmission_requested', async () => { - const mockUserEntity = generateWorkerUser(); - mockUserEntity.kyc = generateKycEntity( - mockUserEntity.id, + const mockKycEntity = generateKycEntity( + faker.number.int(), KycStatus.RESUBMISSION_REQUESTED, ); + mockKycRepository.findOneByUserId.mockResolvedValueOnce(mockKycEntity); - const result = await kycService.initSession( - mockUserEntity as UserEntity, - ); + const result = await kycService.initSession(mockKycEntity.userId); expect(result).toEqual({ - url: mockUserEntity.kyc.url, + url: mockKycEntity.url, }); }); }); it('Should throw an error if user already has an active Kyc session, but is approved already', async () => { - const mockUserEntity = generateWorkerUser(); - mockUserEntity.kyc = generateKycEntity( - mockUserEntity.id, + const mockKycEntity = generateKycEntity( + faker.number.int(), KycStatus.APPROVED, ); + mockKycRepository.findOneByUserId.mockResolvedValueOnce(mockKycEntity); await expect( - kycService.initSession(mockUserEntity as any), + kycService.initSession(mockKycEntity.userId), ).rejects.toThrow( - new KycError(KycErrorMessage.ALREADY_APPROVED, mockUserEntity.id), + new KycError(KycErrorMessage.ALREADY_APPROVED, mockKycEntity.userId), ); }); it("Should throw an error if user already has an active Kyc session, but it's declined", async () => { - const mockUserEntity = generateWorkerUser(); - mockUserEntity.kyc = generateKycEntity( - mockUserEntity.id, + const mockKycEntity = generateKycEntity( + faker.number.int(), KycStatus.DECLINED, ); + mockKycRepository.findOneByUserId.mockResolvedValueOnce(mockKycEntity); await expect( - kycService.initSession(mockUserEntity as any), + kycService.initSession(mockKycEntity.userId), ).rejects.toThrow( - new KycError(KycErrorMessage.DECLINED, mockUserEntity.id), + new KycError(KycErrorMessage.DECLINED, mockKycEntity.userId), ); }); @@ -136,8 +131,10 @@ describe('Kyc Service', () => { createHttpServiceResponse(200, mockPostKycRespose), ); + mockKycRepository.findOneByUserId.mockResolvedValueOnce(null); mockKycRepository.createUnique.mockResolvedValueOnce({} as KycEntity); - const result = await kycService.initSession(mockUserEntity); + + const result = await kycService.initSession(mockUserEntity.id); expect(result).toEqual({ url: mockPostKycRespose.verification.url, @@ -235,9 +232,10 @@ describe('Kyc Service', () => { describe('getSignedAddress', () => { it('Should throw an error if the user has no wallet address registered', async () => { const mockUserEntity = generateWorkerUser(); + mockUserRepository.findOneById.mockResolvedValueOnce(mockUserEntity); await expect( - kycService.getSignedAddress(mockUserEntity as UserEntity), + kycService.getSignedAddress(mockUserEntity.id), ).rejects.toThrow( new KycError( KycErrorMessage.NO_WALLET_ADDRESS_REGISTERED, @@ -251,9 +249,10 @@ describe('Kyc Service', () => { privateKey: generateEthWallet().privateKey, }); mockUserEntity.kyc = generateKycEntity(mockUserEntity.id, KycStatus.NONE); + mockUserRepository.findOneById.mockResolvedValueOnce(mockUserEntity); await expect( - kycService.getSignedAddress(mockUserEntity as UserEntity), + kycService.getSignedAddress(mockUserEntity.id), ).rejects.toThrow( new KycError(KycErrorMessage.KYC_NOT_APPROVED, mockUserEntity.id), ); @@ -267,10 +266,9 @@ describe('Kyc Service', () => { mockUserEntity.id, KycStatus.APPROVED, ); + mockUserRepository.findOneById.mockResolvedValueOnce(mockUserEntity); - const result = await kycService.getSignedAddress( - mockUserEntity as UserEntity, - ); + const result = await kycService.getSignedAddress(mockUserEntity.id); const wallet = new ethers.Wallet(mockWeb3ConfigService.privateKey); const signedUserAddressWithOperatorPrivateKey = await wallet.signMessage( diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts index 48ee4dde8f..3f53f928c4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/kyc.service.ts @@ -3,26 +3,18 @@ import { Injectable } from '@nestjs/common'; import { AxiosError } from 'axios'; import { catchError, firstValueFrom } from 'rxjs'; -import { KycConfigService } from '../../config/kyc-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { KycConfigService, Web3ConfigService } from '../../config'; import logger from '../../logger'; import * as httpUtils from '../../utils/http'; -import { UserEntity } from '../user'; -import { Web3Service } from '../web3/web3.service'; +import { UserNotFoundError, UserRepository } from '../user'; +import { Web3Service } from '../web3'; import { KycStatus } from './constants'; import { KycSignedAddressDto, UpdateKycStatusDto } from './kyc.dto'; import { KycEntity } from './kyc.entity'; import { KycErrorMessage, KycError } from './kyc.error'; import { KycRepository } from './kyc.repository'; - -type VeriffCreateSessionResponse = { - status: string; - verification: { - id: string; - url: string; - }; -}; +import type { VeriffCreateSessionResponse } from './types'; @Injectable() export class KycService { @@ -32,35 +24,35 @@ export class KycService { private readonly kycRepository: KycRepository, private readonly httpService: HttpService, private readonly kycConfigService: KycConfigService, + private readonly userRepository: UserRepository, private readonly web3Service: Web3Service, private readonly web3ConfigService: Web3ConfigService, ) {} - async initSession(userEntity: UserEntity): Promise<{ url: string }> { - if (userEntity.kyc?.sessionId) { - if (userEntity.kyc.status === KycStatus.APPROVED) { - throw new KycError(KycErrorMessage.ALREADY_APPROVED, userEntity.id); + async initSession(userId: number): Promise<{ url: string }> { + const existingKycEntity = await this.kycRepository.findOneByUserId(userId); + + if (existingKycEntity) { + if (existingKycEntity.status === KycStatus.APPROVED) { + throw new KycError(KycErrorMessage.ALREADY_APPROVED, userId); } - if (userEntity.kyc.status === KycStatus.REVIEW) { - throw new KycError( - KycErrorMessage.VERIFICATION_IN_PROGRESS, - userEntity.id, - ); + if (existingKycEntity.status === KycStatus.REVIEW) { + throw new KycError(KycErrorMessage.VERIFICATION_IN_PROGRESS, userId); } - if (userEntity.kyc.status === KycStatus.DECLINED) { - throw new KycError(KycErrorMessage.DECLINED, userEntity.id); + if (existingKycEntity.status === KycStatus.DECLINED) { + throw new KycError(KycErrorMessage.DECLINED, userId); } return { - url: userEntity.kyc.url, + url: existingKycEntity.url, }; } const body = { verification: { - vendorData: `${userEntity.id}`, + vendorData: `${userId}`, }, }; @@ -82,7 +74,7 @@ export class KycService { 'Error occurred while initializing KYC session'; this.logger.error(errorMessage, { error: formattedError, - userId: userEntity.id, + userId, }); throw new Error(errorMessage); }), @@ -92,14 +84,14 @@ export class KycService { if (data?.status !== 'success' || !data?.verification?.url) { throw new KycError( KycErrorMessage.INVALID_KYC_PROVIDER_API_RESPONSE, - userEntity.id, + userId, ); } const kycEntity = new KycEntity(); kycEntity.sessionId = data.verification.id; kycEntity.status = KycStatus.NONE; - kycEntity.userId = userEntity.id; + kycEntity.userId = userId; kycEntity.url = data.verification.url; await this.kycRepository.createUnique(kycEntity); @@ -148,12 +140,20 @@ export class KycService { await this.kycRepository.updateOne(kycEntity); } - async getSignedAddress(user: UserEntity): Promise { - if (!user.evmAddress) - throw new KycError(KycErrorMessage.NO_WALLET_ADDRESS_REGISTERED, user.id); + async getSignedAddress(userId: number): Promise { + const user = await this.userRepository.findOneById(userId, { + relations: { kyc: true }, + }); + if (!user) { + throw new UserNotFoundError(userId); + } + if (!user.evmAddress) { + throw new KycError(KycErrorMessage.NO_WALLET_ADDRESS_REGISTERED, userId); + } - if (user.kyc?.status !== KycStatus.APPROVED) - throw new KycError(KycErrorMessage.KYC_NOT_APPROVED, user.id); + if (user.kyc?.status !== KycStatus.APPROVED) { + throw new KycError(KycErrorMessage.KYC_NOT_APPROVED, userId); + } const address = user.evmAddress.toLowerCase(); const signature = await this.web3Service diff --git a/packages/apps/reputation-oracle/server/src/modules/kyc/types.ts b/packages/apps/reputation-oracle/server/src/modules/kyc/types.ts new file mode 100644 index 0000000000..aaa4f472cc --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/kyc/types.ts @@ -0,0 +1,7 @@ +export type VeriffCreateSessionResponse = { + status: string; + verification: { + id: string; + url: string; + }; +}; diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/nda/fixtures/index.ts similarity index 70% rename from packages/apps/reputation-oracle/server/src/modules/nda/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/nda/fixtures/index.ts index 17f125fc2f..a6a8b5fbc0 100644 --- a/packages/apps/reputation-oracle/server/src/modules/nda/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/nda/fixtures/index.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; -import { NDAConfigService } from '../../config/nda-config.service'; +import { NDAConfigService } from '../../../config'; export const mockNdaConfigService: Omit = { latestNdaUrl: faker.internet.url(), diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/index.ts b/packages/apps/reputation-oracle/server/src/modules/nda/index.ts new file mode 100644 index 0000000000..2bf1fba31c --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/nda/index.ts @@ -0,0 +1 @@ +export { NDAModule } from './nda.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/nda.controller.ts b/packages/apps/reputation-oracle/server/src/modules/nda/nda.controller.ts index 8d7e6534c9..d802f8ea3d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/nda/nda.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/nda/nda.controller.ts @@ -15,11 +15,11 @@ import { ApiTags, } from '@nestjs/swagger'; -import { RequestWithUser } from '../../common/interfaces/request'; -import { NDAConfigService } from '../../config/nda-config.service'; +import { RequestWithUser } from '../../common/types'; +import { NDAConfigService } from '../../config'; import { NDASignatureDto } from './nda.dto'; -import { NDAErrorFilter } from './nda.error.filter'; +import { NDAErrorFilter } from './nda.error-filter'; import { NDAService } from './nda.service'; @ApiTags('NDA') @@ -65,7 +65,7 @@ export class NDAController { }) @Post('sign') async signNDA(@Req() request: RequestWithUser, @Body() nda: NDASignatureDto) { - await this.ndaService.signNDA(request.user, nda); + await this.ndaService.signNDA(request.user.id, nda); return { message: 'NDA signed successfully' }; } } diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/nda.error.filter.ts b/packages/apps/reputation-oracle/server/src/modules/nda/nda.error-filter.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/nda/nda.error.filter.ts rename to packages/apps/reputation-oracle/server/src/modules/nda/nda.error-filter.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.spec.ts index 1c35f41141..30a61da25d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.spec.ts @@ -2,10 +2,9 @@ import { createMock } from '@golevelup/ts-jest'; import { faker } from '@faker-js/faker'; import { Test, TestingModule } from '@nestjs/testing'; -import { NDAConfigService } from '../../config/nda-config.service'; - +import { NDAConfigService } from '../../config'; import { generateWorkerUser } from '../user/fixtures'; -import { UserEntity, UserRepository } from '../user'; +import { UserRepository } from '../user'; import { NDASignatureDto } from './nda.dto'; import { NDAError, NDAErrorMessage } from './nda.error'; @@ -40,8 +39,9 @@ describe('NDAService', () => { const nda: NDASignatureDto = { url: mockNdaConfigService.latestNdaUrl, }; + mockUserRepository.findOneById.mockResolvedValue(user); - await service.signNDA(user, nda); + await service.signNDA(user.id, nda); expect(user.ndaSignedUrl).toBe(mockNdaConfigService.latestNdaUrl); expect(mockUserRepository.updateOne).toHaveBeenCalledWith(user); @@ -53,10 +53,11 @@ describe('NDAService', () => { const invalidNda: NDASignatureDto = { url: faker.internet.url(), }; + mockUserRepository.findOneById.mockResolvedValue(user); - await expect( - service.signNDA(user as UserEntity, invalidNda), - ).rejects.toThrow(new NDAError(NDAErrorMessage.INVALID_NDA, user.id)); + await expect(service.signNDA(user.id, invalidNda)).rejects.toThrow( + new NDAError(NDAErrorMessage.INVALID_NDA, user.id), + ); }); it('should return ok if the user has already signed the NDA', async () => { @@ -66,8 +67,9 @@ describe('NDAService', () => { const nda: NDASignatureDto = { url: mockNdaConfigService.latestNdaUrl, }; + mockUserRepository.findOneById.mockResolvedValue(user); - await service.signNDA(user as UserEntity, nda); + await service.signNDA(user.id, nda); expect(mockUserRepository.updateOne).not.toHaveBeenCalled(); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.ts b/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.ts index 0fbbbe7c3d..3b3d7b540a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/nda/nda.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { UserEntity, UserRepository } from '../user'; -import { NDAConfigService } from '../../config/nda-config.service'; +import { UserNotFoundError, UserRepository } from '../user'; +import { NDAConfigService } from '../../config'; import { NDASignatureDto } from './nda.dto'; import { NDAError, NDAErrorMessage } from './nda.error'; @@ -12,10 +12,15 @@ export class NDAService { private readonly ndaConfigService: NDAConfigService, ) {} - async signNDA(user: UserEntity, nda: NDASignatureDto) { + async signNDA(userId: number, nda: NDASignatureDto) { + const user = await this.userRepository.findOneById(userId); + if (!user) { + throw new UserNotFoundError(userId); + } + const latestNdaUrl = this.ndaConfigService.latestNdaUrl; if (nda.url !== latestNdaUrl) { - throw new NDAError(NDAErrorMessage.INVALID_NDA, user.id); + throw new NDAError(NDAErrorMessage.INVALID_NDA, userId); } if (user.ndaSignedUrl === latestNdaUrl) { return; diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.interface.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.interface.ts deleted file mode 100644 index 56ecb6e6a1..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.interface.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ChainId } from '@human-protocol/sdk'; -import { - AudinoManifest, - CvatManifest, - FortuneManifest, -} from '../../common/interfaces/manifest'; - -export class CalculatePayoutsInput { - chainId: ChainId; - - escrowAddress: string; - - finalResultsUrl: string; -} - -export class SaveResultDto { - /** - * URL to the stored results. - */ - url: string; - - /** - * Hash of the stored results. - */ - hash: string; -} - -export type CalculatedPayout = { - address: string; - amount: bigint; -}; - -export interface RequestAction { - calculatePayouts: ( - manifest: FortuneManifest | CvatManifest | AudinoManifest, - data: CalculatePayoutsInput, - ) => Promise; - saveResults: ( - chainId: ChainId, - escrowAddress: string, - manifest?: FortuneManifest, - ) => Promise; -} diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts deleted file mode 100644 index c770aeb57c..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { EncryptionModule } from '../encryption/encryption.module'; -import { ReputationModule } from '../reputation/reputation.module'; -import { Web3Module } from '../web3/web3.module'; -import { StorageModule } from '../storage/storage.module'; - -import { PayoutService } from './payout.service'; - -@Module({ - imports: [EncryptionModule, ReputationModule, Web3Module, StorageModule], - providers: [PayoutService], - exports: [PayoutService], -}) -export class PayoutModule {} diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts deleted file mode 100644 index 5cc014da40..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.spec.ts +++ /dev/null @@ -1,562 +0,0 @@ -import { createMock } from '@golevelup/ts-jest'; -import { Test } from '@nestjs/testing'; -import { ChainId, EscrowClient } from '@human-protocol/sdk'; -import { - MOCK_ADDRESS, - MOCK_FILE_HASH, - MOCK_FILE_URL, - MOCK_REQUESTER_DESCRIPTION, - MOCK_REQUESTER_TITLE, -} from '../../../test/constants'; -import { JobRequestType } from '../../common/enums'; -import { Web3Service } from '../web3/web3.service'; -import { StorageService } from '../storage/storage.service'; -import { PayoutService } from './payout.service'; -import { CvatManifest } from '../../common/interfaces/manifest'; -import { CvatAnnotationMeta } from '../../common/interfaces/job-result'; -import { CalculatedPayout, SaveResultDto } from './payout.interface'; -import { MissingManifestUrlError } from '../../common/errors/manifest'; -import { PgpEncryptionService } from '../encryption/pgp-encryption.service'; -import * as httpUtils from '../../utils/http'; - -jest.mock('@human-protocol/sdk', () => ({ - ...jest.requireActual('@human-protocol/sdk'), - EscrowClient: { - build: jest.fn().mockImplementation(() => ({ - getIntermediateResultsUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - getManifestUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - bulkPayOut: jest.fn().mockResolvedValue(true), - })), - }, -})); - -describe('PayoutService', () => { - let payoutService: PayoutService, storageService: StorageService; - - const signerMock = { - address: MOCK_ADDRESS, - getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), - }; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [ - { - provide: Web3Service, - useValue: { - getSigner: jest.fn().mockReturnValue(signerMock), - }, - }, - { provide: StorageService, useValue: createMock() }, - { - provide: PgpEncryptionService, - useValue: createMock(), - }, - PayoutService, - ], - }).compile(); - - storageService = moduleRef.get(StorageService); - payoutService = moduleRef.get(PayoutService); - payoutService.createPayoutSpecificActions = { - [JobRequestType.FORTUNE]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.IMAGE_BOXES]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.IMAGE_POINTS]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.IMAGE_POLYGONS]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { - calculatePayouts: jest.fn(), - saveResults: jest.fn(), - }, - }; - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe('processResults', () => { - const results: SaveResultDto = { - url: MOCK_FILE_URL, - hash: MOCK_FILE_HASH, - }; - - beforeEach(() => { - EscrowClient.build = jest.fn().mockResolvedValue({ - getManifestUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - }); - - payoutService.createPayoutSpecificActions[JobRequestType.IMAGE_BOXES][ - 'saveResults' - ] = jest.fn().mockResolvedValue(results); - }); - - it('should successfully save results for CVAT', async () => { - const manifest: CvatManifest = { - data: { - data_url: MOCK_FILE_URL, - }, - annotation: { - labels: [{ name: 'cat' }, { name: 'dog' }], - description: 'Description', - type: JobRequestType.IMAGE_BOXES, - job_size: 10, - max_time: 10, - }, - validation: { - min_quality: 0.95, - val_size: 10, - gt_url: MOCK_FILE_URL, - }, - job_bounty: '10', - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(manifest); - - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const result = await payoutService.processResults(chainId, escrowAddress); - expect(result).toEqual(results); - }); - - it('should successfully save results for Fortune', async () => { - const manifest = { - submissionsRequired: 1, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(manifest); - - const results: SaveResultDto = { - url: MOCK_FILE_URL, - hash: MOCK_FILE_HASH, - }; - - payoutService.createPayoutSpecificActions[JobRequestType.FORTUNE][ - 'saveResults' - ] = jest.fn().mockResolvedValue(results); - - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const result = await payoutService.processResults(chainId, escrowAddress); - expect(result).toEqual(results); - }); - - it('should throw an error if the manifest url does not exist', async () => { - EscrowClient.build = jest.fn().mockResolvedValue({ - getManifestUrl: jest.fn().mockResolvedValue(null), - }); - - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - await expect( - payoutService.processResults(chainId, escrowAddress), - ).rejects.toThrow(new MissingManifestUrlError(MOCK_ADDRESS)); - }); - - it('should throw an error for unsupported request types', async () => { - const mockManifest = { requestType: 'unsupportedType' }; - - jest.spyOn(EscrowClient, 'build').mockResolvedValue({ - getManifestUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - } as any); - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(mockManifest); - - await expect( - payoutService.processResults(ChainId.LOCALHOST, MOCK_ADDRESS), - ).rejects.toThrow( - `Unsupported request type: ${mockManifest.requestType.toLowerCase()}`, - ); - }); - }); - - describe('calculatePayouts', () => { - const results: CalculatedPayout[] = [ - { - address: MOCK_ADDRESS, - amount: 1n, - }, - ]; - let escrowClientMock: any; - - beforeEach(() => { - escrowClientMock = { - getManifestUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - }; - EscrowClient.build = jest.fn().mockImplementation(() => escrowClientMock); - - payoutService.createPayoutSpecificActions[JobRequestType.IMAGE_BOXES][ - 'calculatePayouts' - ] = jest.fn().mockResolvedValue(results); - }); - - it('should successfully calculate payouts for CVAT', async () => { - const manifest: CvatManifest = { - data: { - data_url: MOCK_FILE_URL, - }, - annotation: { - labels: [{ name: 'cat' }, { name: 'dog' }], - description: 'Description', - type: JobRequestType.IMAGE_BOXES, - job_size: 10, - max_time: 10, - }, - validation: { - min_quality: 0.95, - val_size: 10, - gt_url: MOCK_FILE_URL, - }, - job_bounty: '10', - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(manifest); - - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const payouts = await payoutService.calculatePayouts( - chainId, - escrowAddress, - MOCK_FILE_URL, - ); - - expect(payouts).toEqual(results); - - expect(escrowClientMock.getManifestUrl).toHaveBeenCalledTimes(1); - }); - - it('should successfully performs payouts for Fortune', async () => { - const manifest = { - submissionsRequired: 1, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(manifest); - - const results: CalculatedPayout[] = [ - { - address: MOCK_ADDRESS, - amount: 1n, - }, - ]; - - payoutService.createPayoutSpecificActions[JobRequestType.FORTUNE][ - 'calculatePayouts' - ] = jest.fn().mockResolvedValue(results); - - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const payouts = await payoutService.calculatePayouts( - chainId, - escrowAddress, - MOCK_FILE_URL, - ); - - expect(payouts).toEqual(results); - - expect(escrowClientMock.getManifestUrl).toHaveBeenCalledTimes(1); - }); - }); - - describe('saveResultsFortune', () => { - beforeEach(() => { - EscrowClient.build = jest.fn().mockResolvedValue({ - getIntermediateResultsUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - }); - }); - - it('should successfully save results values', async () => { - const chainId = ChainId.LOCALHOST; - const escrowAddress = MOCK_ADDRESS; - - const manifest = { - submissionsRequired: 1, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - const intermediateResults = [ - { - workerAddress: 'string', - solution: 'string', - }, - ]; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(intermediateResults); - - jest.spyOn(payoutService, 'uploadJobResults').mockResolvedValue({ - url: MOCK_FILE_URL, - hash: MOCK_FILE_HASH, - }); - - const result = await payoutService.saveResultsFortune( - manifest, - chainId, - escrowAddress, - ); - expect(result.url).toEqual(expect.any(String)); - expect(result.hash).toEqual(expect.any(String)); - }); - - it('should throw an error if no intermediate results found', async () => { - const chainId = ChainId.LOCALHOST; - const escrowAddress = MOCK_ADDRESS; - - const manifest = { - submissionsRequired: 2, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue([] as any); - - await expect( - payoutService.saveResultsFortune(manifest, chainId, escrowAddress), - ).rejects.toThrow(new Error('No intermediate results found')); - }); - - it('should throw an error if the number of solutions is less than solutions required', async () => { - const chainId = ChainId.LOCALHOST; - const escrowAddress = MOCK_ADDRESS; - - const manifest = { - submissionsRequired: 2, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - const intermediateResults = [ - { - workerAddress: 'string', - solution: 'string', - }, - ]; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(intermediateResults); - - await expect( - payoutService.saveResultsFortune(manifest, chainId, escrowAddress), - ).rejects.toThrow(new Error('Not all required solutions have been sent')); - }); - }); - - describe('calculatePayoutsFortune', () => { - it('should successfully calculate results and return correct result values', async () => { - const manifest = { - submissionsRequired: 1, - requesterTitle: MOCK_REQUESTER_TITLE, - requesterDescription: MOCK_REQUESTER_DESCRIPTION, - fundAmount: 10, - requestType: JobRequestType.FORTUNE, - }; - - const intermediateResults = [ - { - workerAddress: 'string', - solution: 'string', - }, - ]; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(intermediateResults); - - const result = await payoutService.calculatePayoutsFortune( - manifest, - MOCK_FILE_URL, - ); - expect(result).toEqual(expect.any(Array)); - }); - }); - - describe('saveResultsCvat', () => { - beforeEach(() => { - EscrowClient.build = jest.fn().mockResolvedValue({ - getIntermediateResultsUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - }); - }); - - it('should successfully process and return correct result values', async () => { - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const results: CvatAnnotationMeta = { - jobs: [ - { - job_id: 1, - final_result_id: 3, - }, - ], - results: [ - { - id: 2, - job_id: 1, - annotator_wallet_address: MOCK_ADDRESS, - annotation_quality: 0.6, - }, - { - id: 3, - job_id: 1, - annotator_wallet_address: MOCK_ADDRESS, - annotation_quality: 0.96, - }, - ], - }; - - jest.spyOn(httpUtils, 'downloadFile').mockResolvedValue(results); - - jest.spyOn(payoutService, 'uploadJobResults').mockResolvedValue({ - url: MOCK_FILE_URL, - hash: MOCK_FILE_HASH, - }); - - const result = await payoutService.saveResultsCvat( - chainId, - escrowAddress, - ); - expect(result.url).toEqual(expect.any(String)); - expect(result.hash).toEqual(expect.any(String)); - }); - }); - - describe('calculatePayoutsCvat', () => { - const manifest = { - data: { - data_url: MOCK_FILE_URL, - }, - annotation: { - labels: [{ name: 'cat' }, { name: 'dog' }], - description: 'Description', - type: JobRequestType.IMAGE_BOXES, - job_size: 10, - max_time: 10, - }, - validation: { - min_quality: 0.95, - val_size: 10, - gt_url: MOCK_FILE_URL, - }, - job_bounty: '10', - }; - - beforeEach(() => { - EscrowClient.build = jest.fn().mockResolvedValue({ - getIntermediateResultsUrl: jest.fn().mockResolvedValue(MOCK_FILE_URL), - }); - }); - - it('should successfully process and return correct result values', async () => { - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const results: CvatAnnotationMeta = { - jobs: [ - { - job_id: 1, - final_result_id: 3, - }, - ], - results: [ - { - id: 2, - job_id: 1, - annotator_wallet_address: MOCK_ADDRESS, - annotation_quality: 0.6, - }, - { - id: 3, - job_id: 1, - annotator_wallet_address: MOCK_ADDRESS, - annotation_quality: 0.96, - }, - ], - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(results); - - const result = await payoutService.calculatePayoutsCvat( - manifest as any, - chainId, - escrowAddress, - ); - expect(result.length).toEqual(1); - }); - - it('should throw an error if no annotations meta found', async () => { - const escrowAddress = MOCK_ADDRESS; - const chainId = ChainId.LOCALHOST; - - const results: CvatAnnotationMeta = { - jobs: [], - results: [], - }; - - jest - .spyOn(storageService, 'downloadJsonLikeData') - .mockResolvedValue(results); - - await expect( - payoutService.calculatePayoutsCvat( - manifest as any, - chainId, - escrowAddress, - ), - ).rejects.toThrow(new Error('No annotations meta found')); - }); - }); -}); diff --git a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts b/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts deleted file mode 100644 index 4365a245de..0000000000 --- a/packages/apps/reputation-oracle/server/src/modules/payout/payout.service.ts +++ /dev/null @@ -1,510 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { Injectable } from '@nestjs/common'; -import { ChainId, EscrowClient } from '@human-protocol/sdk'; - -import crypto from 'crypto'; -import { ethers } from 'ethers'; - -import { - AUDINO_RESULTS_ANNOTATIONS_FILENAME, - AUDINO_VALIDATION_META_FILENAME, - CVAT_RESULTS_ANNOTATIONS_FILENAME, - CVAT_VALIDATION_META_FILENAME, -} from '../../common/constants'; -import { PgpEncryptionService } from '../encryption/pgp-encryption.service'; -import { Web3Service } from '../web3/web3.service'; -import { ContentType, JobRequestType } from '../../common/enums'; -import { StorageService } from '../storage/storage.service'; -import { - JobManifest, - AudinoManifest, - CvatManifest, - FortuneManifest, -} from '../../common/interfaces/manifest'; -import { - AudinoAnnotationMeta, - CvatAnnotationMeta, - FortuneFinalResult, -} from '../../common/interfaces/job-result'; -import { - CalculatedPayout, - CalculatePayoutsInput, - RequestAction, - SaveResultDto, -} from './payout.interface'; -import * as httpUtils from '../../utils/http'; -import { getJobRequestType } from '../../utils/manifest'; -import { assertValidJobRequestType } from '../../utils/type-guards'; -import { MissingManifestUrlError } from '../../common/errors/manifest'; - -@Injectable() -export class PayoutService { - constructor( - private readonly storageService: StorageService, - private readonly web3Service: Web3Service, - private readonly pgpEncryptionService: PgpEncryptionService, - ) {} - - /** - * Saves the final calculated results to be used for worker payouts. - * Retrieves the manifest URL and downloads the manifest data to determine - * the request type, then invokes the appropriate payout action for that type. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} The URL and hash for the stored results. - */ - public async processResults( - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - const escrowClient = await EscrowClient.build(signer); - - const manifestUrl = await escrowClient.getManifestUrl(escrowAddress); - if (!manifestUrl) { - throw new MissingManifestUrlError(escrowAddress); - } - - const manifest = - await this.storageService.downloadJsonLikeData(manifestUrl); - - const requestType = getJobRequestType(manifest).toLowerCase(); - - assertValidJobRequestType(requestType); - - const { saveResults } = this.createPayoutSpecificActions[requestType]; - - const results = await saveResults(chainId, escrowAddress, manifest as any); - - return results; - } - - /** - * Executes payouts to workers based on final result calculations. - * Retrieves the manifest, calculates results, and processes bulk payouts - * using the escrow client, providing transaction options for gas price. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @param url The URL containing the final results. - * @param hash The hash of the final results. - * @returns {Promise} - */ - public async calculatePayouts( - chainId: ChainId, - escrowAddress: string, - finalResultsUrl: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - const escrowClient = await EscrowClient.build(signer); - - const manifestUrl = await escrowClient.getManifestUrl(escrowAddress); - if (!manifestUrl) { - throw new MissingManifestUrlError(escrowAddress); - } - - const manifest = - await this.storageService.downloadJsonLikeData(manifestUrl); - - const requestType = getJobRequestType(manifest).toLowerCase(); - - assertValidJobRequestType(requestType); - - const { calculatePayouts } = this.createPayoutSpecificActions[requestType]; - - const data: CalculatePayoutsInput = { - chainId, - escrowAddress, - finalResultsUrl, - }; - - return await calculatePayouts(manifest, data); - } - - public createPayoutSpecificActions: Record = { - [JobRequestType.FORTUNE]: { - calculatePayouts: async ( - manifest: FortuneManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsFortune(manifest, data.finalResultsUrl), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - manifest: FortuneManifest, - ): Promise => - this.saveResultsFortune(manifest, chainId, escrowAddress), - }, - [JobRequestType.IMAGE_BOXES]: { - calculatePayouts: async ( - manifest: CvatManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsCvat(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => this.saveResultsCvat(chainId, escrowAddress), - }, - [JobRequestType.IMAGE_POINTS]: { - calculatePayouts: async ( - manifest: CvatManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsCvat(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => this.saveResultsCvat(chainId, escrowAddress), - }, - [JobRequestType.IMAGE_BOXES_FROM_POINTS]: { - calculatePayouts: async ( - manifest: CvatManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsCvat(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => this.saveResultsCvat(chainId, escrowAddress), - }, - [JobRequestType.IMAGE_SKELETONS_FROM_BOXES]: { - calculatePayouts: async ( - manifest: CvatManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsCvat(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => this.saveResultsCvat(chainId, escrowAddress), - }, - [JobRequestType.IMAGE_POLYGONS]: { - calculatePayouts: async ( - manifest: CvatManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsCvat(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => this.saveResultsCvat(chainId, escrowAddress), - }, - [JobRequestType.AUDIO_TRANSCRIPTION]: { - calculatePayouts: async ( - manifest: AudinoManifest, - data: CalculatePayoutsInput, - ): Promise => - this.calculatePayoutsAudino(manifest, data.chainId, data.escrowAddress), - saveResults: async ( - chainId: ChainId, - escrowAddress: string, - ): Promise => - this.saveResultsAudino(chainId, escrowAddress), - }, - }; - - /** - * Saves final results of a Fortune-type job, verifies intermediate results, - * and uploads the final job solutions. Throws an error if required submissions are not met. - * @param manifest The Fortune job manifest data. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} The URL and hash for the saved results. - */ - public async saveResultsFortune( - manifest: FortuneManifest, - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const intermediateResultsUrl = - await escrowClient.getIntermediateResultsUrl(escrowAddress); - - const intermediateResults = await this.storageService.downloadJsonLikeData< - FortuneFinalResult[] - >(intermediateResultsUrl); - - if (intermediateResults.length === 0) { - throw new Error('No intermediate results found'); - } - - const validResults = intermediateResults.filter((result) => !result.error); - if (validResults.length < manifest.submissionsRequired) { - throw new Error('Not all required solutions have been sent'); - } - - return this.uploadJobResults( - JSON.stringify(intermediateResults), - chainId, - escrowAddress, - { - extension: 'json', - }, - ); - } - - /** - * Saves final results of a CVAT-type job, using intermediate results for annotations. - * Retrieves intermediate results, copies files to storage, and returns the final results URL and hash. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} The URL and hash for the saved results. - */ - public async saveResultsCvat( - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const intermediateResultsUrl = - await escrowClient.getIntermediateResultsUrl(escrowAddress); - - let fileContent = await httpUtils.downloadFile( - `${intermediateResultsUrl}/${CVAT_RESULTS_ANNOTATIONS_FILENAME}`, - ); - fileContent = await this.pgpEncryptionService.maybeDecryptFile(fileContent); - - return this.uploadJobResults(fileContent, chainId, escrowAddress, { - prefix: 's3', - extension: 'zip', - }); - } - - /** - * Saves final results of a Audino-type job, using intermediate results for annotations. - * Retrieves intermediate results, copies files to storage, and returns the final results URL and hash. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} The URL and hash for the saved results. - */ - public async saveResultsAudino( - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const intermediateResultsUrl = - await escrowClient.getIntermediateResultsUrl(escrowAddress); - - let fileContent = await httpUtils.downloadFile( - `${intermediateResultsUrl}/${AUDINO_RESULTS_ANNOTATIONS_FILENAME}`, - ); - fileContent = await this.pgpEncryptionService.maybeDecryptFile(fileContent); - - return this.uploadJobResults(fileContent, chainId, escrowAddress, { - prefix: 's3', - extension: 'zip', - }); - } - - /** - * Calculates payment distributions for a Fortune-type job based on manifest data - * and final results. Distributes rewards proportionally to qualified recipients. - * @param manifest The Fortune manifest data. - * @param finalResultsUrl URL of the final results for this job. - * @returns {Promise} Recipients, amounts, and relevant storage data. - */ - public async calculatePayoutsFortune( - manifest: FortuneManifest, - finalResultsUrl: string, - ): Promise { - const finalResults = - await this.storageService.downloadJsonLikeData( - finalResultsUrl, - ); - - const recipients = finalResults - .filter((result) => !result.error) - .map((item) => item.workerAddress); - - const payoutAmount = - BigInt(ethers.parseUnits(manifest.fundAmount.toString(), 'ether')) / - BigInt(recipients.length); - - return recipients.map((recipient) => ({ - address: recipient, - amount: payoutAmount, - })); - } - - /** - * Calculates payment distributions for a CVAT-type job based on annotations data. - * Verifies annotation quality, accumulates bounties, and assigns payouts to qualified annotators. - * @param manifest The CVAT manifest data. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} Recipients, amounts, and relevant storage data. - */ - public async calculatePayoutsCvat( - manifest: CvatManifest, - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const intermediateResultsUrl = - await escrowClient.getIntermediateResultsUrl(escrowAddress); - - const annotations = - await this.storageService.downloadJsonLikeData( - `${intermediateResultsUrl}/${CVAT_VALIDATION_META_FILENAME}`, - ); - - // If annotation meta results does not exist - if ( - annotations && - annotations.results && - Array.isArray(annotations.results) && - annotations.results.length === 0 && - annotations.jobs && - Array.isArray(annotations.jobs) && - annotations.jobs.length === 0 - ) { - throw new Error('No annotations meta found'); - } - - const jobBountyValue = ethers.parseUnits(manifest.job_bounty, 18); - const workersBounties = new Map(); - - for (const job of annotations.jobs) { - const jobFinalResult = annotations.results.find( - (result) => result.id === job.final_result_id, - ); - if ( - jobFinalResult - // && jobFinalResult.annotation_quality >= manifest.validation.min_quality - ) { - const workerAddress = jobFinalResult.annotator_wallet_address; - - const currentWorkerBounty = workersBounties.get(workerAddress) || 0n; - - workersBounties.set( - workerAddress, - currentWorkerBounty + jobBountyValue, - ); - } - } - - return Array.from(workersBounties.entries()).map( - ([workerAddress, bountyAmount]) => ({ - address: workerAddress, - amount: bountyAmount, - }), - ); - } - - /** - * Calculates payment distributions for a Audino-type job based on annotations data. - * Verifies annotation quality, accumulates bounties, and assigns payouts to qualified annotators. - * @param manifest The Audino manifest data. - * @param chainId The blockchain chain ID. - * @param escrowAddress The escrow contract address. - * @returns {Promise} Recipients, amounts, and relevant storage data. - */ - public async calculatePayoutsAudino( - manifest: AudinoManifest, - chainId: ChainId, - escrowAddress: string, - ): Promise { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const intermediateResultsUrl = - await escrowClient.getIntermediateResultsUrl(escrowAddress); - - const annotations = - await this.storageService.downloadJsonLikeData( - `${intermediateResultsUrl}/${AUDINO_VALIDATION_META_FILENAME}`, - ); - - if ( - Array.isArray(annotations?.results) && - annotations.results.length === 0 && - Array.isArray(annotations?.jobs) && - annotations.jobs.length === 0 - ) { - throw new Error('No annotations meta found'); - } - - const jobBountyValue = ethers.parseUnits(manifest.job_bounty, 18); - const workersBounties = new Map(); - - for (const job of annotations.jobs) { - const jobFinalResult = annotations.results.find( - (result) => result.id === job.final_result_id, - ); - if ( - jobFinalResult - // && jobFinalResult.annotation_quality >= manifest.validation.min_quality - ) { - const workerAddress = jobFinalResult.annotator_wallet_address; - - const currentWorkerBounty = workersBounties.get(workerAddress) || 0n; - - workersBounties.set( - workerAddress, - currentWorkerBounty + jobBountyValue, - ); - } - } - - return Array.from(workersBounties.entries()).map( - ([workerAddress, bountyAmount]) => ({ - address: workerAddress, - amount: bountyAmount, - }), - ); - } - - /** - * Encrypts results w/ JL and RepO PGP - * and uploads them to RepO storage. - */ - public async uploadJobResults( - resultsData: string | Buffer, - chainId: ChainId, - escrowAddress: string, - fileNameOptions: { - prefix?: string; - extension: string; - }, - ): Promise<{ url: string; hash: string }> { - const signer = this.web3Service.getSigner(chainId); - - const escrowClient = await EscrowClient.build(signer); - - const jobLauncherAddress = - await escrowClient.getJobLauncherAddress(escrowAddress); - - const encryptedResults = await this.pgpEncryptionService.encrypt( - resultsData, - chainId, - [jobLauncherAddress], - ); - const hash = crypto - .createHash('sha1') - .update(encryptedResults) - .digest('hex'); - - const prefix = fileNameOptions.prefix || ''; - const fileName = `${prefix}${hash}.${fileNameOptions.extension}`; - - const url = await this.storageService.uploadData( - encryptedResults, - fileName, - ContentType.PLAIN_TEXT, - ); - - return { url, hash }; - } -} diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/index.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/index.ts new file mode 100644 index 0000000000..5c80613a7c --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/index.ts @@ -0,0 +1 @@ +export { QualificationModule } from './qualification.module'; diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.ts index 6a1d589059..c953357c4d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.controller.ts @@ -27,7 +27,7 @@ import { UnassignQualificationDto, UserQualificationOperationResponseDto, } from './qualification.dto'; -import { QualificationErrorFilter } from './qualification.error.filter'; +import { QualificationErrorFilter } from './qualification.error-filter'; import { QualificationService } from './qualification.service'; @ApiTags('Qualification') diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts index 767c1b4b3a..b8393c96d5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index, OneToMany } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import type { UserQualificationEntity } from './user-qualification.entity'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'qualifications' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.error.filter.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.error-filter.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/qualification/qualification.error.filter.ts rename to packages/apps/reputation-oracle/server/src/modules/qualification/qualification.error-filter.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.repository.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.repository.ts index c9dcd5e016..5569b198ac 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource, FindManyOptions, IsNull, MoreThan } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { QualificationEntity } from './qualification.entity'; type FindOptions = { diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.spec.ts index 8d521f68ed..db7321753b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.spec.ts @@ -4,7 +4,7 @@ import { ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; import { generateEthWallet } from '../../../test/fixtures/web3'; -import { ServerConfigService } from '../../config/server-config.service'; +import { ServerConfigService } from '../../config'; import { UserStatus, UserRepository } from '../user'; import { generateWorkerUser } from '../user/fixtures'; diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.ts index cacd799f8f..7f42c2b2b6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/qualification.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { v4 as uuidV4 } from 'uuid'; -import { ServerConfigService } from '../../config/server-config.service'; +import { ServerConfigService } from '../../config'; import logger from '../../logger'; import { UserRepository, UserStatus } from '../user'; @@ -11,16 +11,10 @@ import { QualificationErrorMessage, } from './qualification.error'; import { QualificationRepository } from './qualification.repository'; +import type { Qualification } from './types'; import { UserQualificationEntity } from './user-qualification.entity'; import { UserQualificationRepository } from './user-qualification.repository'; -type Qualification = { - reference: string; - title: string; - description: string; - expiresAt?: string; -}; - @Injectable() export class QualificationService { private readonly logger = logger.child({ diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/types.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/types.ts new file mode 100644 index 0000000000..8457a4c082 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/types.ts @@ -0,0 +1,6 @@ +export type Qualification = { + reference: string; + title: string; + description: string; + expiresAt?: string; +}; diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts index 5697d14232..63743184be 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, ManyToOne, Index } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import type { QualificationEntity } from '../qualification/qualification.entity'; import type { UserEntity } from '../user'; diff --git a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.repository.ts b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.repository.ts index b610be12bf..70311a558d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/qualification/user-qualification.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { UserQualificationEntity } from './user-qualification.entity'; @Injectable() diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/fixtures/index.ts similarity index 80% rename from packages/apps/reputation-oracle/server/src/modules/reputation/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/reputation/fixtures/index.ts index c5c6a40622..2078488bb5 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/fixtures/index.ts @@ -1,9 +1,9 @@ import { faker } from '@faker-js/faker'; -import { generateTestnetChainId } from '../web3/fixtures'; +import { generateTestnetChainId } from '../../web3/fixtures'; -import { ReputationEntityType } from './constants'; -import { ReputationEntity } from './reputation.entity'; +import { ReputationEntityType } from '../constants'; +import { ReputationEntity } from '../reputation.entity'; const REPUTATION_ENTITY_TYPES = Object.values(ReputationEntityType); export function generateReputationEntityType(): ReputationEntityType { diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/index.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/index.ts new file mode 100644 index 0000000000..ef9e41252e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/index.ts @@ -0,0 +1,2 @@ +export { ReputationModule } from './reputation.module'; +export { ReputationService } from './reputation.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.ts index ab488aafa9..974b91c0b1 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { Public } from '../../common/decorators'; +import { SortDirection } from '../../common/enums'; import { MAX_REPUTATION_ITEMS_PER_PAGE, ReputationOrderBy } from './constants'; import { @@ -10,7 +11,6 @@ import { ReputationResponseDto, } from './reputation.dto'; import { ReputationService } from './reputation.service'; -import { SortDirection } from 'src/common/enums'; function mapReputationOrderBy( queryOrderBy: GetReputationQueryOrderBy, diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts index 85073f2225..63b7596d37 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import { ReputationEntityType } from './constants'; diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts index 0149f84788..c30001a407 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.module.ts @@ -1,7 +1,7 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { ReputationService } from './reputation.service'; import { ReputationRepository } from './reputation.repository'; diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.repository.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.repository.ts index ed7310ef61..930776ce42 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.repository.ts @@ -3,16 +3,11 @@ import { Injectable } from '@nestjs/common'; import { DataSource, FindManyOptions, In } from 'typeorm'; import { SortDirection } from '../../common/enums'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { ReputationEntityType, ReputationOrderBy } from './constants'; import { ReputationEntity } from './reputation.entity'; - -export type ExclusiveReputationCriteria = { - chainId: number; - address: string; - type: ReputationEntityType; -}; +import type { ExclusiveReputationCriteria } from './types'; @Injectable() export class ReputationRepository extends BaseRepository { diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts index 5acbf3da73..b1abf2827d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.spec.ts @@ -5,14 +5,13 @@ import { faker } from '@faker-js/faker'; import { EscrowClient } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; -import { ReputationConfigService } from '../../config/reputation-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { ReputationConfigService, Web3ConfigService } from '../../config'; import { generateTestnetChainId, mockWeb3ConfigService, } from '../web3/fixtures'; -import { Web3Service } from '../web3/web3.service'; +import { Web3Service } from '../web3'; import { generateRandomScorePoints, @@ -325,11 +324,9 @@ describe('ReputationService', () => { let spyOnIncreaseReputation: jest.SpyInstance; beforeAll(() => { - spyOnIncreaseReputation = jest.spyOn(service, 'increaseReputation'); - }); - - beforeEach(() => { - spyOnIncreaseReputation.mockImplementation(); + spyOnIncreaseReputation = jest + .spyOn(service, 'increaseReputation') + .mockImplementation(); }); afterAll(() => { diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts index f40c5cf111..c3e92f1cd6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/reputation.service.ts @@ -2,11 +2,10 @@ import { Injectable } from '@nestjs/common'; import { ChainId, EscrowClient } from '@human-protocol/sdk'; import { SortDirection } from '../../common/enums'; -import { isDuplicatedError } from '../../common/errors/database'; -import { ReputationConfigService } from '../../config/reputation-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { isDuplicatedError } from '../../database'; +import { ReputationConfigService, Web3ConfigService } from '../../config'; -import { Web3Service } from '../web3/web3.service'; +import { Web3Service } from '../web3'; import { INITIAL_REPUTATION, @@ -15,17 +14,8 @@ import { ReputationOrderBy, } from './constants'; import { ReputationEntity } from './reputation.entity'; -import { - ReputationRepository, - type ExclusiveReputationCriteria, -} from './reputation.repository'; - -type ReputationData = { - chainId: ChainId; - address: string; - level: ReputationLevel; - role: ReputationEntityType; -}; +import { ReputationRepository } from './reputation.repository'; +import type { ExclusiveReputationCriteria, ReputationData } from './types'; function assertAdjustableReputationPoints(points: number) { if (points > 0 && Number.isInteger(points)) { diff --git a/packages/apps/reputation-oracle/server/src/modules/reputation/types.ts b/packages/apps/reputation-oracle/server/src/modules/reputation/types.ts new file mode 100644 index 0000000000..b40311a6e3 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/reputation/types.ts @@ -0,0 +1,16 @@ +import { ChainId } from '@human-protocol/sdk'; + +import { ReputationEntityType, ReputationLevel } from './constants'; + +export type ReputationData = { + chainId: ChainId; + address: string; + level: ReputationLevel; + role: ReputationEntityType; +}; + +export type ExclusiveReputationCriteria = { + chainId: number; + address: string; + type: ReputationEntityType; +}; diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/index.ts b/packages/apps/reputation-oracle/server/src/modules/storage/index.ts new file mode 100644 index 0000000000..487b0bc452 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/storage/index.ts @@ -0,0 +1,2 @@ +export { StorageModule } from './storage.module'; +export { StorageService } from './storage.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts index 22d4e94a5a..3ddb44fb84 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { EncryptionModule } from '../encryption/encryption.module'; +import { EncryptionModule } from '../encryption'; import { StorageService } from './storage.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts index 6f9523a70a..95d46abde7 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.spec.ts @@ -1,14 +1,15 @@ jest.mock('minio'); + import { createMock } from '@golevelup/ts-jest'; import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { Client as MinioClient } from 'minio'; import { ContentType } from '../../common/enums'; -import { S3ConfigService } from '../../config/s3-config.service'; +import { S3ConfigService } from '../../config'; import * as httpUtils from '../../utils/http'; -import { PgpEncryptionService } from '../encryption/pgp-encryption.service'; +import { PgpEncryptionService } from '../encryption'; import { MinioErrorCodes } from './minio.constants'; import { StorageService } from './storage.service'; @@ -145,11 +146,12 @@ describe('StorageService', () => { }); }); - describe('downloadJsonLikeData', () => { - const EXPECTED_DOWNLOAD_ERROR_MESSAGE = 'Error downloading json like data'; - const spyOnDownloadFile = jest.spyOn(httpUtils, 'downloadFile'); + describe('downloadFile', () => { + const EXPECTED_DOWNLOAD_ERROR_MESSAGE = 'Error downloading file'; + let spyOnDownloadFile: jest.SpyInstance; beforeAll(() => { + spyOnDownloadFile = jest.spyOn(httpUtils, 'downloadFile'); spyOnDownloadFile.mockImplementation(); }); @@ -161,7 +163,7 @@ describe('StorageService', () => { spyOnDownloadFile.mockRejectedValueOnce(new Error(faker.lorem.word())); await expect( - storageService.downloadJsonLikeData(faker.internet.url()), + storageService.downloadFile(faker.internet.url()), ).rejects.toThrow(EXPECTED_DOWNLOAD_ERROR_MESSAGE); }); @@ -172,16 +174,53 @@ describe('StorageService', () => { ); await expect( - storageService.downloadJsonLikeData(faker.internet.url()), + storageService.downloadFile(faker.internet.url()), ).rejects.toThrow(EXPECTED_DOWNLOAD_ERROR_MESSAGE); }); - it('should throw custom error when fails to parse data', async () => { - spyOnDownloadFile.mockResolvedValueOnce(Buffer.from(faker.lorem.words())); + it('should download file', async () => { + const data = { + string: faker.string.sample(), + number: faker.number.float(), + bool: false, + null: null, + }; + + const fileUrl = faker.internet.url(); + spyOnDownloadFile.mockImplementation(async (url) => { + if (url === fileUrl) { + return Buffer.from(JSON.stringify(data)); + } + + throw new Error('File not found'); + }); mockedPgpEncryptionService.maybeDecryptFile.mockImplementationOnce( async (c) => c, ); + const downloadedFile = await storageService.downloadFile(fileUrl); + const content = JSON.parse(downloadedFile.toString()); + + expect(content).toEqual(data); + }); + }); + + describe('downloadJsonLikeData', () => { + const EXPECTED_DOWNLOAD_ERROR_MESSAGE = 'Error downloading json like data'; + let spyOnDownloadFile: jest.SpyInstance; + + beforeAll(() => { + spyOnDownloadFile = jest.spyOn(storageService, 'downloadFile'); + spyOnDownloadFile.mockImplementation(); + }); + + afterAll(() => { + spyOnDownloadFile.mockRestore(); + }); + + it('should throw custom error when fails to load file', async () => { + spyOnDownloadFile.mockRejectedValueOnce(new Error(faker.lorem.word())); + await expect( storageService.downloadJsonLikeData(faker.internet.url()), ).rejects.toThrow(EXPECTED_DOWNLOAD_ERROR_MESSAGE); @@ -203,9 +242,6 @@ describe('StorageService', () => { throw new Error('File not found'); }); - mockedPgpEncryptionService.maybeDecryptFile.mockImplementationOnce( - async (c) => c, - ); const downloadedData = await storageService.downloadJsonLikeData(fileUrl); diff --git a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts index 50f9367076..8a83b70a73 100644 --- a/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/storage/storage.service.ts @@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common'; import * as Minio from 'minio'; import { ContentType } from '../../common/enums'; -import { S3ConfigService } from '../../config/s3-config.service'; +import { S3ConfigService } from '../../config'; import logger from '../../logger'; import * as httpUtils from '../../utils/http'; -import { PgpEncryptionService } from '../encryption/pgp-encryption.service'; +import { PgpEncryptionService } from '../encryption'; import { MinioErrorCodes } from './minio.constants'; @@ -51,13 +51,28 @@ export class StorageService { } } - async downloadJsonLikeData(url: string): Promise { + async downloadFile(url: string): Promise { try { let fileContent = await httpUtils.downloadFile(url); fileContent = await this.pgpEncryptionService.maybeDecryptFile(fileContent); + return fileContent; + } catch (error) { + const errorMessage = 'Error downloading file'; + this.logger.error(errorMessage, { + error, + url, + }); + throw new Error(errorMessage); + } + } + + async downloadJsonLikeData(url: string): Promise { + try { + const fileContent = await this.downloadFile(url); + return JSON.parse(fileContent.toString()); } catch (error) { const errorMessage = 'Error downloading json like data'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/index.ts b/packages/apps/reputation-oracle/server/src/modules/user/index.ts index 8f6d1bce07..ca741e834e 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/index.ts @@ -2,6 +2,7 @@ export { SiteKeyRepository } from './site-key.repository'; export { SiteKeyType } from './site-key.entity'; export type * from './types'; export { UserEntity, UserStatus, Role as UserRole } from './user.entity'; +export { UserNotFoundError } from './user.error'; export { UserModule } from './user.module'; export { UserRepository } from './user.repository'; export { UserService, OperatorStatus } from './user.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts b/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts index 386c0449f8..58fd6b07b6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/site-key.entity.ts @@ -1,7 +1,7 @@ import { Entity, Column, JoinColumn, ManyToOne, Unique } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import type { UserEntity } from './user.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/site-key.repository.ts b/packages/apps/reputation-oracle/server/src/modules/user/site-key.repository.ts index d2cc2d1f87..abeead2eaf 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/site-key.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/site-key.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { SiteKeyEntity, SiteKeyType } from './site-key.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts index ccdca718ce..92daefe48d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.controller.ts @@ -18,9 +18,9 @@ import { } from '@nestjs/common'; import { Public } from '../../common/decorators'; -import { SignatureType } from '../../common/enums/web3'; -import { RequestWithUser } from '../../common/interfaces/request'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { SignatureType } from '../../common/enums'; +import { RequestWithUser } from '../../common/types'; +import { Web3ConfigService } from '../../config'; import { HCaptchaGuard } from '../../integrations/hcaptcha/hcaptcha.guard'; import * as web3Utils from '../../utils/web3'; @@ -35,7 +35,7 @@ import { RegistrationInExchangeOraclesResponseDto, RegistrationInExchangeOracleResponseDto, } from './user.dto'; -import { UserErrorFilter } from './user.error.filter'; +import { UserErrorFilter } from './user.error-filter'; import { UserService } from './user.service'; /** @@ -70,7 +70,7 @@ export class UserController { async registerLabeler( @Req() request: RequestWithUser, ): Promise { - const siteKey = await this.userService.registerLabeler(request.user); + const siteKey = await this.userService.registerLabeler(request.user.id); return { siteKey }; } @@ -95,7 +95,7 @@ export class UserController { @Body() data: RegisterAddressRequestDto, ): Promise { await this.userService.registerAddress( - request.user, + request.user.id, data.address, data.signature, ); @@ -114,9 +114,9 @@ export class UserController { @HttpCode(200) async enableOperator( @Body() data: EnableOperatorDto, - @Request() req: RequestWithUser, + @Request() request: RequestWithUser, ): Promise { - await this.userService.enableOperator(req.user, data.signature); + await this.userService.enableOperator(request.user.id, data.signature); } @ApiOperation({ @@ -132,9 +132,9 @@ export class UserController { @HttpCode(200) async disableOperator( @Body() data: DisableOperatorDto, - @Request() req: RequestWithUser, + @Request() request: RequestWithUser, ): Promise { - await this.userService.disableOperator(req.user, data.signature); + await this.userService.disableOperator(request.user.id, data.signature); } @ApiOperation({ @@ -189,7 +189,7 @@ export class UserController { @Body() data: RegistrationInExchangeOracleDto, ): Promise { await this.userService.registrationInExchangeOracle( - request.user, + request.user.id, data.oracleAddress, ); @@ -215,7 +215,7 @@ export class UserController { @Req() request: RequestWithUser, ): Promise { const oracleAddresses = - await this.userService.getRegistrationInExchangeOracles(request.user); + await this.userService.getRegistrationInExchangeOracles(request.user.id); return { oracleAddresses }; } diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts index 53c389b32a..11e8ec4c51 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEthereumAddress, IsOptional, IsString } from 'class-validator'; -import { SignatureType } from '../../common/enums/web3'; +import { SignatureType } from '../../common/enums'; import { IsLowercasedEnum, IsValidWeb3Signature, diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts index 3b6a75f1ab..f6e33eb36f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.entity.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import type { KycEntity } from '../kyc/kyc.entity'; import type { UserQualificationEntity } from '../qualification/user-qualification.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.error.filter.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.error-filter.ts similarity index 100% rename from packages/apps/reputation-oracle/server/src/modules/user/user.error.filter.ts rename to packages/apps/reputation-oracle/server/src/modules/user/user.error-filter.ts diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.error.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.error.ts index 4a7aa3a515..e369d88bbb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.error.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.error.ts @@ -33,3 +33,11 @@ export class InvalidWeb3SignatureError extends BaseError { this.userId = userId; } } + +export class UserNotFoundError extends BaseError { + userId: number; + constructor(userId: number) { + super(`User not found: ${userId}`); + this.userId = userId; + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts index fb290974af..6c34d08d3c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; -import { HCaptchaModule } from '../../integrations/hcaptcha/hcaptcha.module'; +import { HCaptchaModule } from '../../integrations/hcaptcha'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { SiteKeyRepository } from './site-key.repository'; import { UserController } from './user.controller'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts index b3cdf55a4b..c2161dbc95 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource, FindManyOptions, In } from 'typeorm'; -import { BaseRepository } from '../../database/base.repository'; +import { BaseRepository } from '../../database'; import { Role, UserEntity } from './user.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index c8216f4436..5164f97a55 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -7,8 +7,8 @@ import { Test } from '@nestjs/testing'; import { generateEthWallet } from '../../../test/fixtures/web3'; -import { SignatureType } from '../../common/enums/web3'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { SignatureType } from '../../common/enums'; +import { Web3ConfigService } from '../../config'; import { HCaptchaService } from '../../integrations/hcaptcha/hcaptcha.service'; import * as web3Utils from '../../utils/web3'; @@ -100,7 +100,9 @@ describe('UserService', () => { const user = generateWorkerUser(); user.role = Role.ADMIN; - await expect(userService.registerLabeler(user)).rejects.toThrow( + mockUserRepository.findOneById.mockResolvedValueOnce(user); + + await expect(userService.registerLabeler(user.id)).rejects.toThrow( new UserError(UserErrorMessage.INVALID_ROLE, user.id), ); @@ -109,8 +111,9 @@ describe('UserService', () => { it('should throw if worker does not have evm address', async () => { const user = generateWorkerUser(); + mockUserRepository.findOneById.mockResolvedValueOnce(user); - await expect(userService.registerLabeler(user)).rejects.toThrow( + await expect(userService.registerLabeler(user.id)).rejects.toThrow( new UserError(UserErrorMessage.MISSING_ADDRESS, user.id), ); @@ -123,7 +126,9 @@ describe('UserService', () => { }); user.kyc = generateKycEntity(user.id, KycStatus.NONE); - await expect(userService.registerLabeler(user)).rejects.toThrow( + mockUserRepository.findOneById.mockResolvedValueOnce(user); + + await expect(userService.registerLabeler(user.id)).rejects.toThrow( new UserError(UserErrorMessage.KYC_NOT_APPROVED, user.id), ); @@ -142,7 +147,9 @@ describe('UserService', () => { ); user.siteKeys = [existingSitekey]; - const result = await userService.registerLabeler(user); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + + const result = await userService.registerLabeler(user.id); expect(result).toBe(existingSitekey.siteKey); @@ -155,9 +162,10 @@ describe('UserService', () => { }); user.kyc = generateKycEntity(user.id, KycStatus.APPROVED); + mockUserRepository.findOneById.mockResolvedValueOnce(user); mockHCaptchaService.registerLabeler.mockResolvedValueOnce(false); - await expect(userService.registerLabeler(user)).rejects.toThrow( + await expect(userService.registerLabeler(user.id)).rejects.toThrow( new UserError(UserErrorMessage.LABELING_ENABLE_FAILED, user.id), ); }); @@ -168,11 +176,11 @@ describe('UserService', () => { }); user.kyc = generateKycEntity(user.id, KycStatus.APPROVED); + mockUserRepository.findOneById.mockResolvedValueOnce(user); mockHCaptchaService.registerLabeler.mockResolvedValueOnce(true); - mockHCaptchaService.getLabelerData.mockResolvedValueOnce(null); - await expect(userService.registerLabeler(user)).rejects.toThrow( + await expect(userService.registerLabeler(user.id)).rejects.toThrow( new UserError(UserErrorMessage.LABELING_ENABLE_FAILED, user.id), ); }); @@ -185,6 +193,7 @@ describe('UserService', () => { user.siteKeys = [ generateSiteKeyEntity(user.id, SiteKeyType.REGISTRATION), ]; + mockUserRepository.findOneById.mockResolvedValueOnce(user); mockHCaptchaService.registerLabeler.mockResolvedValueOnce(true); const registeredSitekey = faker.string.uuid(); @@ -195,7 +204,7 @@ describe('UserService', () => { mockedLabelerData, ); - const result = await userService.registerLabeler(user); + const result = await userService.registerLabeler(user.id); expect(result).toBe(registeredSitekey); @@ -234,10 +243,11 @@ describe('UserService', () => { const user = generateWorkerUser({ privateKey: generateEthWallet().privateKey, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); await expect( userService.registerAddress( - user, + user.id, addressToRegister, faker.string.alphanumeric(), ), @@ -251,10 +261,11 @@ describe('UserService', () => { it('should throw if kyc is not approved', async () => { const user = generateWorkerUser(); user.kyc = generateKycEntity(user.id, KycStatus.NONE); + mockUserRepository.findOneById.mockResolvedValueOnce(user); await expect( userService.registerAddress( - user, + user.id, addressToRegister, faker.string.alphanumeric(), ), @@ -267,38 +278,37 @@ describe('UserService', () => { it('should throw if same address already exists', async () => { const user = generateWorkerUser(); + const existingUser = generateWorkerUser({ + privateKey: generateEthWallet().privateKey, + }); user.kyc = generateKycEntity(user.id, KycStatus.APPROVED); - mockUserRepository.findOneByAddress.mockImplementationOnce( - async (address: string) => { - if (address === addressToRegister.toLowerCase()) { - return user; - } - - return null; - }, - ); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + mockUserRepository.findOneByAddress.mockResolvedValueOnce(existingUser); await expect( userService.registerAddress( - user, + user.id, addressToRegister, faker.string.alphanumeric(), ), ).rejects.toThrow( new DuplicatedWalletAddressError(user.id, addressToRegister), ); - + expect(mockUserRepository.findOneByAddress).toHaveBeenCalledWith( + addressToRegister.toLowerCase(), + ); expect(mockUserRepository.updateOne).toHaveBeenCalledTimes(0); }); it('should throw if invalid signature', async () => { const user = generateWorkerUser(); user.kyc = generateKycEntity(user.id, KycStatus.APPROVED); + mockUserRepository.findOneById.mockResolvedValueOnce(user); await expect( userService.registerAddress( - user, + user.id, addressToRegister, faker.string.alphanumeric(), ), @@ -313,6 +323,8 @@ describe('UserService', () => { const user = generateWorkerUser(); user.kyc = generateKycEntity(user.id, KycStatus.APPROVED); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage( web3Utils.prepareSignatureBody({ from: addressToRegister, @@ -322,7 +334,7 @@ describe('UserService', () => { privateKey, ); - await userService.registerAddress(user, addressToRegister, signature); + await userService.registerAddress(user.id, addressToRegister, signature); expect(user.evmAddress).toBe(addressToRegister.toLowerCase()); @@ -338,7 +350,9 @@ describe('UserService', () => { mockSiteKeyRepository.findByUserAndType.mockResolvedValueOnce([siteKey]); - const result = await userService.getRegistrationInExchangeOracles(user); + const result = await userService.getRegistrationInExchangeOracles( + user.id, + ); expect(result).toEqual([siteKey.siteKey]); }); @@ -350,21 +364,16 @@ describe('UserService', () => { const siteKey = generateSiteKeyEntity(user.id, SiteKeyType.REGISTRATION); const oracleAddress = siteKey.siteKey; - mockSiteKeyRepository.findByUserSiteKeyAndType.mockImplementationOnce( - async (userId, sitekey, type) => { - if ( - userId === user.id && - sitekey === oracleAddress && - type === SiteKeyType.REGISTRATION - ) { - return siteKey; - } - return null; - }, + mockUserRepository.findOneById.mockResolvedValueOnce(user); + mockSiteKeyRepository.findByUserSiteKeyAndType.mockResolvedValueOnce( + siteKey, ); - await userService.registrationInExchangeOracle(user, oracleAddress); + await userService.registrationInExchangeOracle(user.id, oracleAddress); + expect( + mockSiteKeyRepository.findByUserSiteKeyAndType, + ).toHaveBeenCalledWith(user.id, oracleAddress, SiteKeyType.REGISTRATION); expect(mockSiteKeyRepository.createUnique).toHaveBeenCalledTimes(0); }); @@ -372,11 +381,12 @@ describe('UserService', () => { const user = generateWorkerUser(); const oracleAddress = generateEthWallet().address; + mockUserRepository.findOneById.mockResolvedValueOnce(user); mockSiteKeyRepository.findByUserSiteKeyAndType.mockResolvedValueOnce( null, ); - await userService.registrationInExchangeOracle(user, oracleAddress); + await userService.registrationInExchangeOracle(user.id, oracleAddress); expect(mockSiteKeyRepository.createUnique).toHaveBeenCalledTimes(1); expect(mockSiteKeyRepository.createUnique).toHaveBeenCalledWith({ @@ -405,9 +415,13 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.ENABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); - await expect(userService.enableOperator(user, signature)).rejects.toThrow( + await expect( + userService.enableOperator(user.id, signature), + ).rejects.toThrow( new InvalidWeb3SignatureError(user.id, user.evmAddress), ); expect(mockedKVStoreSet).toHaveBeenCalledTimes(0); @@ -422,11 +436,15 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.ENABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); mockedKVStoreUtils.get.mockResolvedValueOnce(OperatorStatus.ACTIVE); - await expect(userService.enableOperator(user, signature)).rejects.toThrow( + await expect( + userService.enableOperator(user.id, signature), + ).rejects.toThrow( new UserError(UserErrorMessage.OPERATOR_ALREADY_ACTIVE, user.id), ); expect(mockedKVStoreUtils.get).toHaveBeenCalledTimes(1); @@ -447,9 +465,11 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.ENABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); - await userService.enableOperator(user, signature); + await userService.enableOperator(user.id, signature); expect(mockedKVStoreSet).toHaveBeenCalledTimes(1); expect(mockedKVStoreSet).toHaveBeenCalledWith( @@ -477,10 +497,12 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.DISABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); await expect( - userService.disableOperator(user, signature), + userService.disableOperator(user.id, signature), ).rejects.toThrow( new InvalidWeb3SignatureError(user.id, user.evmAddress), ); @@ -496,12 +518,14 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.DISABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); mockedKVStoreUtils.get.mockResolvedValueOnce(OperatorStatus.INACTIVE); await expect( - userService.disableOperator(user, signature), + userService.disableOperator(user.id, signature), ).rejects.toThrow( new UserError(UserErrorMessage.OPERATOR_NOT_ACTIVE, user.id), ); @@ -523,9 +547,11 @@ describe('UserService', () => { to: mockWeb3ConfigService.operatorAddress, contents: SignatureType.DISABLE_OPERATOR, }); + mockUserRepository.findOneById.mockResolvedValueOnce(user); + const signature = await web3Utils.signMessage(signatureBody, privateKey); - await userService.disableOperator(user, signature); + await userService.disableOperator(user.id, signature); expect(mockedKVStoreSet).toHaveBeenCalledTimes(1); expect(mockedKVStoreSet).toHaveBeenCalledWith( diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index e981308765..b7db3afcc6 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -1,8 +1,8 @@ import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; -import { SignatureType } from '../../common/enums/web3'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { SignatureType } from '../../common/enums'; +import { Web3ConfigService } from '../../config'; import { HCaptchaService } from '../../integrations/hcaptcha/hcaptcha.service'; import * as web3Utils from '../../utils/web3'; @@ -12,12 +12,13 @@ import { Web3Service } from '../web3/web3.service'; import { SiteKeyEntity, SiteKeyType } from './site-key.entity'; import { SiteKeyRepository } from './site-key.repository'; import { OperatorUserEntity, Web2UserEntity } from './types'; -import { Role as UserRole, UserEntity } from './user.entity'; +import { Role as UserRole } from './user.entity'; import { - UserError, - UserErrorMessage, DuplicatedWalletAddressError, InvalidWeb3SignatureError, + UserError, + UserErrorMessage, + UserNotFoundError, } from './user.error'; import { UserRepository } from './user.repository'; @@ -69,7 +70,16 @@ export class UserService { return null; } - async registerLabeler(user: Web2UserEntity): Promise { + async registerLabeler(userId: number): Promise { + const user = (await this.userRepository.findOneById(userId, { + relations: { + kyc: true, + siteKeys: true, + }, + })) as Web2UserEntity; + if (!user) { + throw new UserNotFoundError(userId); + } if (user.role !== UserRole.WORKER) { throw new UserError(UserErrorMessage.INVALID_ROLE, user.id); } @@ -122,12 +132,21 @@ export class UserService { } async registerAddress( - user: UserEntity, + userId: number, address: string, signature: string, ): Promise { const lowercasedAddress = address.toLocaleLowerCase(); + const user = await this.userRepository.findOneById(userId, { + relations: { + kyc: true, + }, + }); + if (!user) { + throw new UserNotFoundError(userId); + } + if (user.evmAddress) { throw new UserError(UserErrorMessage.ADDRESS_EXISTS, user.id); } @@ -161,22 +180,28 @@ export class UserService { await this.userRepository.updateOne(user); } - async enableOperator( - user: OperatorUserEntity, - signature: string, - ): Promise { + async enableOperator(userId: number, signature: string): Promise { + const operatorUser = (await this.userRepository.findOneById( + userId, + )) as OperatorUserEntity; + if (!operatorUser) { + throw new UserNotFoundError(userId); + } const signedData = web3Utils.prepareSignatureBody({ - from: user.evmAddress, + from: operatorUser.evmAddress, to: this.web3ConfigService.operatorAddress, contents: SignatureType.ENABLE_OPERATOR, }); const verified = web3Utils.verifySignature(signedData, signature, [ - user.evmAddress, + operatorUser.evmAddress, ]); if (!verified) { - throw new InvalidWeb3SignatureError(user.id, user.evmAddress); + throw new InvalidWeb3SignatureError( + operatorUser.id, + operatorUser.evmAddress, + ); } const chainId = this.web3ConfigService.reputationNetworkChainId; @@ -185,32 +210,45 @@ export class UserService { let status: string | undefined; try { - status = await KVStoreUtils.get(chainId, signer.address, user.evmAddress); + status = await KVStoreUtils.get( + chainId, + signer.address, + operatorUser.evmAddress, + ); } catch {} if (status === OperatorStatus.ACTIVE) { - throw new UserError(UserErrorMessage.OPERATOR_ALREADY_ACTIVE, user.id); + throw new UserError( + UserErrorMessage.OPERATOR_ALREADY_ACTIVE, + operatorUser.id, + ); } - await kvstore.set(user.evmAddress, OperatorStatus.ACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE); } - async disableOperator( - user: OperatorUserEntity, - signature: string, - ): Promise { + async disableOperator(userId: number, signature: string): Promise { + const operatorUser = (await this.userRepository.findOneById( + userId, + )) as OperatorUserEntity; + if (!operatorUser) { + throw new UserNotFoundError(userId); + } const signedData = web3Utils.prepareSignatureBody({ - from: user.evmAddress, + from: operatorUser.evmAddress, to: this.web3ConfigService.operatorAddress, contents: SignatureType.DISABLE_OPERATOR, }); const verified = web3Utils.verifySignature(signedData, signature, [ - user.evmAddress, + operatorUser.evmAddress, ]); if (!verified) { - throw new InvalidWeb3SignatureError(user.id, user.evmAddress); + throw new InvalidWeb3SignatureError( + operatorUser.id, + operatorUser.evmAddress, + ); } const chainId = this.web3ConfigService.reputationNetworkChainId; @@ -220,20 +258,27 @@ export class UserService { const status = await KVStoreUtils.get( chainId, signer.address, - user.evmAddress, + operatorUser.evmAddress, ); if (status === OperatorStatus.INACTIVE) { - throw new UserError(UserErrorMessage.OPERATOR_NOT_ACTIVE, user.id); + throw new UserError( + UserErrorMessage.OPERATOR_NOT_ACTIVE, + operatorUser.id, + ); } - await kvstore.set(user.evmAddress, OperatorStatus.INACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE); } async registrationInExchangeOracle( - user: Web2UserEntity, + userId: number, oracleAddress: string, ): Promise { + const user = await this.userRepository.findOneById(userId); + if (!user) { + throw new UserNotFoundError(userId); + } const siteKey = await this.siteKeyRepository.findByUserSiteKeyAndType( user.id, oracleAddress, @@ -251,9 +296,9 @@ export class UserService { await this.siteKeyRepository.createUnique(newSiteKey); } - async getRegistrationInExchangeOracles(user: UserEntity): Promise { + async getRegistrationInExchangeOracles(userId: number): Promise { const siteKeys = await this.siteKeyRepository.findByUserAndType( - user.id, + userId, SiteKeyType.REGISTRATION, ); return siteKeys.map((siteKey) => siteKey.siteKey); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts similarity index 78% rename from packages/apps/reputation-oracle/server/src/modules/web3/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts index dc07bf697f..ade74b3553 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts @@ -2,9 +2,10 @@ import { faker } from '@faker-js/faker'; import { Web3ConfigService, Web3Network, -} from '../../config/web3-config.service'; -import { generateEthWallet } from '../../../test/fixtures/web3'; -import { supportedChainIdsByNetwork } from './web3.service'; +} from '../../../config/web3-config.service'; +import { generateEthWallet } from '../../../../test/fixtures/web3'; + +import { supportedChainIdsByNetwork } from '../web3.service'; const testWallet = generateEthWallet(); diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/index.ts b/packages/apps/reputation-oracle/server/src/modules/web3/index.ts new file mode 100644 index 0000000000..ff1b45f78e --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/web3/index.ts @@ -0,0 +1,3 @@ +export { Web3Module } from './web3.module'; +export { Web3Service } from './web3.service'; +export type { WalletWithProvider } from './types'; diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/types.ts b/packages/apps/reputation-oracle/server/src/modules/web3/types.ts new file mode 100644 index 0000000000..6fc7a5e452 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/web3/types.ts @@ -0,0 +1,10 @@ +import { ChainId } from '@human-protocol/sdk'; + +import type { Provider, Wallet } from 'ethers'; + +export type Chain = { + id: ChainId; + rpcUrl: string; +}; + +export type WalletWithProvider = Wallet & { provider: Provider }; diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts index c5aecfcd78..10af353e89 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.spec.ts @@ -1,9 +1,12 @@ import { createMock } from '@golevelup/ts-jest'; +import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { FeeData, JsonRpcProvider, Provider } from 'ethers'; -import { faker } from '@faker-js/faker'; -import { WalletWithProvider, Web3Service } from './web3.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; + +import { Web3ConfigService } from '../../config'; + +import { Web3Service } from './web3.service'; +import type { WalletWithProvider } from './types'; import { generateTestnetChainId, mockWeb3ConfigService } from './fixtures'; diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts index c511913eac..847886401c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/web3.service.ts @@ -1,10 +1,8 @@ import { ChainId } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; -import { Provider, Wallet, ethers } from 'ethers'; -import { - Web3ConfigService, - Web3Network, -} from '../../config/web3-config.service'; +import { Wallet, ethers } from 'ethers'; +import { Web3ConfigService, Web3Network } from '../../config'; +import type { Chain, WalletWithProvider } from './types'; export const supportedChainIdsByNetwork = { [Web3Network.MAINNET]: [ChainId.POLYGON, ChainId.BSC_MAINNET], @@ -16,13 +14,6 @@ export const supportedChainIdsByNetwork = { [Web3Network.LOCAL]: [ChainId.LOCALHOST], } as const; -type Chain = { - id: ChainId; - rpcUrl: string; -}; - -export type WalletWithProvider = Wallet & { provider: Provider }; - @Injectable() export class Web3Service { private signersByChainId: { diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/fixtures.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/fixtures/index.ts similarity index 89% rename from packages/apps/reputation-oracle/server/src/modules/webhook/fixtures.ts rename to packages/apps/reputation-oracle/server/src/modules/webhook/fixtures/index.ts index 77d2775cc0..da17bbb84a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/fixtures.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/fixtures/index.ts @@ -2,14 +2,14 @@ import { faker } from '@faker-js/faker'; import * as crypto from 'crypto'; import stringify from 'json-stable-stringify'; -import { generateTestnetChainId } from '../web3/fixtures'; +import { generateTestnetChainId } from '../../web3/fixtures'; import { IncomingWebhookStatus, OutgoingWebhookEventType, OutgoingWebhookStatus, -} from './types'; -import { IncomingWebhookEntity } from './webhook-incoming.entity'; -import { OutgoingWebhookEntity } from './webhook-outgoing.entity'; +} from '../types'; +import { IncomingWebhookEntity } from '../webhook-incoming.entity'; +import { OutgoingWebhookEntity } from '../webhook-outgoing.entity'; type GenerateIncomingWebhookOptions = { retriesCount?: number; @@ -65,7 +65,7 @@ export function generateOutgoingWebhook( const payload = generateOutgoingWebhookPayload(); const url = faker.internet.url(); const hash = crypto - .createHash('sha1') + .createHash('sha256') .update(stringify({ payload, url }) as string) .digest('hex'); diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/index.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/index.ts new file mode 100644 index 0000000000..6230af5d87 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/index.ts @@ -0,0 +1,5 @@ +export { OutgoingWebhookEventType } from './types'; +export { IncomingWebhookModule } from './webhook-incoming.module'; +export { IncomingWebhookService } from './webhook-incoming.service'; +export { OutgoingWebhookModule } from './webhook-outgoing.module'; +export { OutgoingWebhookService } from './webhook-outgoing.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts index 2c81aa199f..61422aa418 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import { IncomingWebhookStatus } from './types'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'webhook_incoming' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts index b979a0efe6..7058fbdc19 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.module.ts @@ -1,7 +1,7 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { EscrowCompletionModule } from '../escrow-completion/escrow-completion.module'; +import { EscrowCompletionModule } from '../escrow-completion'; import { WebhookController } from './webhook.controller'; import { IncomingWebhookRepository } from './webhook-incoming.repository'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.repository.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.repository.ts index c965e1e026..5913845c8c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ServerConfigService } from '../../config/server-config.service'; -import { BaseRepository } from '../../database/base.repository'; +import { ServerConfigService } from '../../config'; +import { BaseRepository } from '../../database'; import { IncomingWebhookStatus } from './types'; import { IncomingWebhookEntity } from './webhook-incoming.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts index 2bb5f58132..3f3ea6e5ea 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.spec.ts @@ -2,10 +2,9 @@ import { faker } from '@faker-js/faker'; import { createMock } from '@golevelup/ts-jest'; import { Test } from '@nestjs/testing'; -import { PostgresErrorCodes } from '../../common/enums/database'; -import { DatabaseError } from '../../common/errors/database'; -import { ServerConfigService } from '../../config/server-config.service'; -import { EscrowCompletionService } from '../escrow-completion/escrow-completion.service'; +import { DatabaseError, DatabaseErrorMessages } from '../../database'; +import { ServerConfigService } from '../../config'; +import { EscrowCompletionService } from '../escrow-completion'; import { generateTestnetChainId } from '../web3/fixtures'; import { generateIncomingWebhook } from './fixtures'; @@ -135,7 +134,7 @@ describe('WebhookIncomingService', () => { incomingWebhookEntity, ]); mockEscrowCompletionService.createEscrowCompletion.mockRejectedValueOnce( - new DatabaseError(PostgresErrorCodes.Duplicated), + new DatabaseError(DatabaseErrorMessages.DUPLICATED), ); await incomingWebhookService.processPendingIncomingWebhooks(); diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.ts index b3a17a16f2..b877cca8ec 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-incoming.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { BACKOFF_INTERVAL_SECONDS } from '../../common/constants'; -import { isDuplicatedError } from '../../common/errors/database'; -import { ServerConfigService } from '../../config/server-config.service'; -import { EscrowCompletionService } from '../escrow-completion/escrow-completion.service'; +import { isDuplicatedError } from '../../database'; +import { ServerConfigService } from '../../config'; +import { EscrowCompletionService } from '../escrow-completion'; import { calculateExponentialBackoffMs } from '../../utils/backoff'; import logger from '../../logger'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts index bf68fa51f3..bf13bf52de 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index } from 'typeorm'; import { DATABASE_SCHEMA_NAME } from '../../common/constants'; -import { BaseEntity } from '../../database/base.entity'; +import { BaseEntity } from '../../database'; import { OutgoingWebhookStatus } from './types'; @Entity({ schema: DATABASE_SCHEMA_NAME, name: 'webhook_outgoing' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts index 15c63e0d85..c6a07f9209 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.module.ts @@ -1,7 +1,7 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { Web3Module } from '../web3/web3.module'; +import { Web3Module } from '../web3'; import { OutgoingWebhookRepository } from './webhook-outgoing.repository'; import { OutgoingWebhookService } from './webhook-outgoing.service'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.repository.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.repository.ts index 1d6cac026a..f72f7a9bdb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.repository.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { DataSource, LessThanOrEqual } from 'typeorm'; -import { ServerConfigService } from '../../config/server-config.service'; -import { BaseRepository } from '../../database/base.repository'; +import { ServerConfigService } from '../../config'; +import { BaseRepository } from '../../database'; import { OutgoingWebhookStatus } from './types'; import { OutgoingWebhookEntity } from './webhook-outgoing.entity'; diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts index d2841f8d0a..f5d3637be4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.spec.ts @@ -10,8 +10,7 @@ import { createHttpServiceResponse, } from '../../../test/mock-creators/nest'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; -import { ServerConfigService } from '../../config/server-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { ServerConfigService, Web3ConfigService } from '../../config'; import { transformKeysFromCamelToSnake } from '../../utils/case-converters'; import { signMessage } from '../../utils/web3'; import { mockWeb3ConfigService } from '../web3/fixtures'; @@ -68,7 +67,7 @@ describe('WebhookOutgoingService', () => { const url = faker.internet.url(); const hash = crypto - .createHash('sha1') + .createHash('sha256') .update(stringify({ payload, url }) as string) .digest('hex'); diff --git a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.ts b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.ts index ba0ff0b2c3..c2fb0e56a4 100644 --- a/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/webhook/webhook-outgoing.service.ts @@ -8,8 +8,7 @@ import { BACKOFF_INTERVAL_SECONDS, HEADER_SIGNATURE_KEY, } from '../../common/constants'; -import { ServerConfigService } from '../../config/server-config.service'; -import { Web3ConfigService } from '../../config/web3-config.service'; +import { ServerConfigService, Web3ConfigService } from '../../config'; import { calculateExponentialBackoffMs } from '../../utils/backoff'; import { transformKeysFromCamelToSnake } from '../../utils/case-converters'; import { formatAxiosError } from '../../utils/http'; @@ -38,7 +37,7 @@ export class OutgoingWebhookService { url: string, ): Promise { const hash = crypto - .createHash('sha1') + .createHash('sha256') .update(stringify({ payload, url }) as string) .digest('hex'); diff --git a/packages/apps/reputation-oracle/server/src/utils/manifest.ts b/packages/apps/reputation-oracle/server/src/utils/manifest.ts index dda48ddea6..fb0afbac57 100644 --- a/packages/apps/reputation-oracle/server/src/utils/manifest.ts +++ b/packages/apps/reputation-oracle/server/src/utils/manifest.ts @@ -1,9 +1,42 @@ -import { JobManifest } from '../common/interfaces/manifest'; -import { JobRequestType } from '../common/enums'; -import { UnsupportedManifestTypeError } from '../common/errors/manifest'; +import { AudinoJobType, CvatJobType, FortuneJobType } from '../common/enums'; +import { JobManifest, JobRequestType } from '../common/types'; + +const fortuneJobTypes = Object.values(FortuneJobType); + +export function isFortuneJobType(value: string): value is FortuneJobType { + return fortuneJobTypes.includes(value as FortuneJobType); +} + +const cvatJobTypes = Object.values(CvatJobType); + +export function isCvatJobType(value: string): value is CvatJobType { + return cvatJobTypes.includes(value as CvatJobType); +} + +const audinoJobTypes = Object.values(AudinoJobType); + +export function isAudinoJobType(value: string): value is AudinoJobType { + return audinoJobTypes.includes(value as AudinoJobType); +} + +const validJobRequestTypes: string[] = [ + ...fortuneJobTypes, + ...cvatJobTypes, + ...audinoJobTypes, +]; + +function assertValidJobRequestType( + value: string, +): asserts value is JobRequestType { + if (validJobRequestTypes.includes(value)) { + return; + } + + throw new Error(`Unsupported request type: ${value}`); +} export function getJobRequestType(manifest: JobManifest): JobRequestType { - let jobRequestType: JobRequestType | undefined; + let jobRequestType: string | undefined; if ('requestType' in manifest) { jobRequestType = manifest.requestType; @@ -12,8 +45,10 @@ export function getJobRequestType(manifest: JobManifest): JobRequestType { } if (!jobRequestType) { - throw new UnsupportedManifestTypeError(jobRequestType); + throw new Error(`Job request type is missing in manifest`); } + assertValidJobRequestType(jobRequestType); + return jobRequestType; } diff --git a/packages/apps/reputation-oracle/server/src/utils/type-guards.ts b/packages/apps/reputation-oracle/server/src/utils/type-guards.ts index 4b1f7e062b..5dedf4fb34 100644 --- a/packages/apps/reputation-oracle/server/src/utils/type-guards.ts +++ b/packages/apps/reputation-oracle/server/src/utils/type-guards.ts @@ -1,17 +1,3 @@ -import { JobRequestType } from '../common/enums'; - export function isObject(value: unknown): value is object { return typeof value === 'object' && value !== null; } - -export function assertValidJobRequestType( - value: string, -): asserts value is JobRequestType { - const validValues = Object.values(JobRequestType); - - if (validValues.includes(value)) { - return; - } - - throw new Error(`Unsupported request type: ${value}`); -} diff --git a/packages/apps/reputation-oracle/server/test/constants.ts b/packages/apps/reputation-oracle/server/test/constants.ts deleted file mode 100644 index 59a1489c99..0000000000 --- a/packages/apps/reputation-oracle/server/test/constants.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const MOCK_REQUESTER_TITLE = 'Mock job title'; -export const MOCK_REQUESTER_DESCRIPTION = 'Mock job description'; -export const MOCK_ADDRESS = '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e'; -export const MOCK_FILE_URL = 'http://local.test/some-mocked-file-url'; -export const MOCK_WEBHOOK_URL = 'mockedWebhookUrl'; -export const MOCK_FILE_HASH = 'mockedFileHash'; -export const MOCK_FILE_KEY = 'manifest.json'; -export const MOCK_REPUTATION_ORACLE_ADDRESS = - '0xCf88b3f1992458C2f5a229573c768D0E9F70C443'; -export const MOCK_PRIVATE_KEY = - 'd334daf65a631f40549cc7de126d5a0016f32a2d00c49f94563f9737f7135e55'; - -export const MOCK_MAX_RETRY_COUNT = 5; - -export const mockConfig: any = { - WEB3_PRIVATE_KEY: MOCK_PRIVATE_KEY, - REPUTATION_LEVEL_LOW: 300, - REPUTATION_LEVEL_HIGH: 700, - MAX_RETRY_COUNT: MOCK_MAX_RETRY_COUNT, - QUALIFICATION_MIN_VALIDITY: 1, -}; - -export const MOCK_BACKOFF_INTERVAL_SECONDS = 120; diff --git a/packages/apps/reputation-oracle/server/test/fixtures/web3.ts b/packages/apps/reputation-oracle/server/test/fixtures/web3.ts index e7b52cff85..ac172d1b7e 100644 --- a/packages/apps/reputation-oracle/server/test/fixtures/web3.ts +++ b/packages/apps/reputation-oracle/server/test/fixtures/web3.ts @@ -1,4 +1,3 @@ -import { faker } from '@faker-js/faker'; import { ethers, Wallet } from 'ethers'; export const TEST_PRIVATE_KEY = @@ -16,9 +15,19 @@ export function generateEthWallet(privateKey?: string) { }; } -export function generateContractAddress() { - return ethers.getCreateAddress({ - from: generateEthWallet().address, - nonce: faker.number.bigInt(), - }); +export type SignerMock = jest.Mocked> & { + __transactionResponse: { + wait: jest.Mock; + }; +}; + +export function createSignerMock(): SignerMock { + const transactionResponse = { + wait: jest.fn(), + }; + + return { + sendTransaction: jest.fn().mockResolvedValue(transactionResponse), + __transactionResponse: transactionResponse, + }; } diff --git a/packages/core/.env.example b/packages/core/.env.example index 17f1c660b5..2cf77456db 100644 --- a/packages/core/.env.example +++ b/packages/core/.env.example @@ -9,7 +9,6 @@ TENDERLY_FORK_ID= # Add your own API key by getting an account at etherscan (https://etherscan.io), snowtrace (https://snowtrace.io) etc. # This is used for verification purposes when you want to `npx hardhat verify` your contract using Hardhat # The same API key works usually for both testnet and mainnet - ETHERSCAN_API_KEY= CELOSCAN_API_KEY= @@ -43,7 +42,6 @@ SPOKE_WORMHOLE_CHAIN_IDS=6 SPOKE_VOTE_TOKEN_ADDRESS= SPOKE_ADDRESSES= - # VOTING PARAMS VOTING_DELAY=1 VOTING_PERIOD=300 diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index b2d1505ea3..935dc7f4b1 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import re from dataclasses import asdict, dataclass, is_dataclass from enum import Enum, auto from inspect import isclass @@ -110,7 +111,9 @@ def from_url(cls, url: str) -> BucketAccessInfo: path=parsed_url.path.lstrip("/"), ) if Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): + # Check if netloc is an ip address + # or localhost with port (or its /etc/hosts alias, e.g. minio:9000) + if is_ipv4(parsed_url.netloc) or re.fullmatch(r"\w+:\d{4}", parsed_url.netloc): host = parsed_url.netloc bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) else: diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index 4cc935089c..077bc0b480 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -1,8 +1,14 @@ import datetime -from human_protocol_sdk.constants import ChainId, Status +from human_protocol_sdk.constants import ChainId, OrderDirection, Status from human_protocol_sdk.escrow import EscrowUtils -from human_protocol_sdk.filter import EscrowFilter, StatisticsFilter +from human_protocol_sdk.worker import WorkerUtils +from human_protocol_sdk.filter import ( + EscrowFilter, + PayoutFilter, + StatisticsFilter, + WorkerFilter, +) from human_protocol_sdk.statistics import ( StatisticsClient, HMTHoldersParam, @@ -84,6 +90,21 @@ def get_hmt_daily_data(statistics_client: StatisticsClient): ) +def get_payouts(): + filter = PayoutFilter( + chain_id=ChainId.POLYGON, + first=5, + skip=1, + order_direction=OrderDirection.ASC, + ) + + payouts = EscrowUtils.get_payouts(filter) + for payout in payouts: + print( + f"Payout ID: {payout.id}, Amount: {payout.amount}, Recipient: {payout.recipient}" + ) + + def get_escrows(): print( EscrowUtils.get_escrows( @@ -131,6 +152,18 @@ def get_operators(): print(len(operators)) +def get_workers(): + workers = WorkerUtils.get_workers( + WorkerFilter(chain_id=ChainId.POLYGON_AMOY, first=2) + ) + print(workers) + print(WorkerUtils.get_worker(ChainId.POLYGON_AMOY, workers[0].address)) + workers = WorkerUtils.get_workers( + WorkerFilter(chain_id=ChainId.POLYGON_AMOY, worker_address=workers[0].address) + ) + print(len(workers)) + + def agreement_example(): annotations = [ ["cat", "not", "cat"], @@ -150,6 +183,7 @@ def agreement_example(): get_escrows() get_operators() + get_payouts() statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) get_hmt_holders(statistics_client) @@ -160,3 +194,4 @@ def agreement_example(): get_hmt_daily_data(statistics_client) agreement_example() + get_workers() diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index 515919dcac..aafc3132bc 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -32,7 +32,7 @@ from web3 import Web3 from human_protocol_sdk.constants import NETWORKS, ChainId, Status, OrderDirection -from human_protocol_sdk.filter import EscrowFilter +from human_protocol_sdk.filter import EscrowFilter, StatusEventFilter, PayoutFilter from human_protocol_sdk.utils import ( get_data_from_subgraph, ) @@ -129,6 +129,28 @@ def __init__( self.escrow_address = escrow_address +class Payout: + """ + Initializes a Payout instance. + + :param id: The id of the payout. + :param chain_id: The chain identifier where the payout occurred. + :param escrow_address: The address of the escrow that executed the payout. + :param recipient: The address of the recipient. + :param amount: The amount of the payout. + :param created_at: The time of creation of the payout. + """ + + def __init__( + self, id: str, escrow_address: str, recipient: str, amount: int, created_at: int + ): + self.id = id + self.escrow_address = escrow_address + self.recipient = recipient + self.amount = amount + self.created_at = created_at + + class EscrowUtils: """ A utility class that provides additional escrow-related functionalities. @@ -313,86 +335,38 @@ def get_escrow( ) @staticmethod - def get_status_events( - chain_id: ChainId, - statuses: Optional[List[Status]] = None, - date_from: Optional[datetime] = None, - date_to: Optional[datetime] = None, - launcher: Optional[str] = None, - first: int = 10, - skip: int = 0, - order_direction: OrderDirection = OrderDirection.DESC, - ) -> List[StatusEvent]: + def get_status_events(filter: StatusEventFilter) -> List[StatusEvent]: """ Retrieve status events for specified networks and statuses within a date range. - :param chain_id: Network to request data. - :param statuses (Optional[List[Status]]): List of statuses to filter by. - :param date_from (Optional[datetime]): Start date for the query range. - :param date_to (Optional[datetime]): End date for the query range. - :param launcher (Optional[str]): Address of the launcher to filter by. - :param first (int): Number of items per page. - :param skip (int): Page number to retrieve. - :param order_direction (OrderDirection): Order of results, "asc" or "desc". + :param filter: Object containing all the necessary parameters to filter status events. :return List[StatusEvent]: List of status events matching the query parameters. :raise EscrowClientError: If an unsupported chain ID or invalid launcher address is provided. - - :example: - .. code-block:: python - - from datetime import datetime - from human_protocol_sdk.constants import ChainId, Status - from human_protocol_sdk.escrow import EscrowUtils - - print( - EscrowUtils.get_status_events( - chain_id=ChainId.POLYGON_AMOY, - statuses=[Status.Pending, Status.Paid], - date_from=datetime(2023, 1, 1), - date_to=datetime(2023, 12, 31), - launcher="0x1234567890abcdef1234567890abcdef12345678", - first=20, - skip=0, - order_direction=OrderDirection.DESC - ) - ) """ - from human_protocol_sdk.gql.escrow import ( - get_status_query, - ) + from human_protocol_sdk.gql.escrow import get_status_query - if launcher and not Web3.is_address(launcher): + if filter.launcher and not Web3.is_address(filter.launcher): raise EscrowClientError("Invalid Address") - # If statuses are not provided, use all statuses except Launched - effective_statuses = statuses or [ - Status.Launched, - Status.Pending, - Status.Partial, - Status.Paid, - Status.Complete, - Status.Cancelled, - ] - - network = NETWORKS.get(chain_id) + network = NETWORKS.get(filter.chain_id) if not network: raise EscrowClientError("Unsupported Chain ID") - status_names = [status.name for status in effective_statuses] + status_names = [status.name for status in filter.statuses] data = get_data_from_subgraph( network, - get_status_query(date_from, date_to, launcher), + get_status_query(filter.date_from, filter.date_to, filter.launcher), { "status": status_names, - "from": int(date_from.timestamp()) if date_from else None, - "to": int(date_to.timestamp()) if date_to else None, - "launcher": launcher or None, - "first": first, - "skip": skip, - "orderDirection": order_direction.value, + "from": int(filter.date_from.timestamp()) if filter.date_from else None, + "to": int(filter.date_to.timestamp()) if filter.date_to else None, + "launcher": filter.launcher.lower() if filter.launcher else None, + "first": filter.first, + "skip": filter.skip, + "orderDirection": filter.order_direction.value, }, ) @@ -411,9 +385,71 @@ def get_status_events( timestamp=event["timestamp"], escrow_address=event["escrowAddress"], status=event["status"], - chain_id=chain_id, + chain_id=filter.chain_id, ) for event in status_events ] return events_with_chain_id + + @staticmethod + def get_payouts(filter: PayoutFilter) -> List[Payout]: + """ + Fetch payouts from the subgraph based on the provided filter. + + :param filter: Object containing all the necessary parameters to filter payouts. + + :return List[Payout]: List of payouts matching the query parameters. + + :raise EscrowClientError: If an unsupported chain ID or invalid addresses are provided. + """ + from human_protocol_sdk.gql.payout import get_payouts_query + + if filter.escrow_address and not Web3.is_address(filter.escrow_address): + raise EscrowClientError("Invalid escrow address") + + if filter.recipient and not Web3.is_address(filter.recipient): + raise EscrowClientError("Invalid recipient address") + + network = NETWORKS.get(filter.chain_id) + if not network: + raise EscrowClientError("Unsupported Chain ID") + + data = get_data_from_subgraph( + network, + get_payouts_query(filter), + { + "escrowAddress": ( + filter.escrow_address.lower() if filter.escrow_address else None + ), + "recipient": filter.recipient.lower() if filter.recipient else None, + "from": int(filter.date_from.timestamp()) if filter.date_from else None, + "to": int(filter.date_to.timestamp()) if filter.date_to else None, + "first": min(filter.first, 1000), + "skip": filter.skip, + "orderDirection": filter.order_direction.value, + }, + ) + + if ( + not data + or "data" not in data + or "payouts" not in data["data"] + or not data["data"]["payouts"] + ): + return [] + + payouts_raw = data["data"]["payouts"] + + payouts = [ + Payout( + id=payout["id"], + escrow_address=payout["escrowAddress"], + recipient=payout["recipient"], + amount=int(payout["amount"]), + created_at=int(payout["createdAt"]), + ) + for payout in payouts_raw + ] + + return payouts diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/filter.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/filter.py index 38b909a8c6..326c271f02 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/filter.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/filter.py @@ -4,7 +4,7 @@ from typing import List, Optional -from human_protocol_sdk.constants import NETWORKS, ChainId, Status, OrderDirection +from human_protocol_sdk.constants import ChainId, Status, OrderDirection from web3 import Web3 @@ -100,18 +100,26 @@ class PayoutFilter: def __init__( self, + chain_id: ChainId, escrow_address: Optional[str] = None, recipient: Optional[str] = None, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None, + first: int = 10, + skip: int = 0, + order_direction: OrderDirection = OrderDirection.DESC, ): """ - Initializes a PayoutFilter instance. - - :param escrow_address: Escrow address - :param recipient: Recipient address - :param date_from: Created from date - :param date_to: Created to date + Initializes a filter for payouts. + + :param chain_id: The chain ID where the payouts are recorded. + :param escrow_address: Optional escrow address to filter payouts. + :param recipient: Optional recipient address to filter payouts. + :param date_from: Optional start date for filtering. + :param date_to: Optional end date for filtering. + :param first: Optional number of payouts per page. Default is 10. + :param skip: Optional number of payouts to skip. Default is 0. + :param order_direction: Optional order direction. Default is DESC. """ if escrow_address and not Web3.is_address(escrow_address): @@ -125,10 +133,14 @@ def __init__( f"Invalid dates: {date_from} must be earlier than {date_to}" ) + self.chain_id = chain_id self.escrow_address = escrow_address self.recipient = recipient self.date_from = date_from self.date_to = date_to + self.first = first + self.skip = skip + self.order_direction = order_direction class TransactionFilter: @@ -251,3 +263,81 @@ def __init__( self.first = min(first, 1000) self.skip = skip self.order_direction = order_direction + + +class StatusEventFilter: + def __init__( + self, + chain_id: ChainId, + statuses: Optional[List[Status]] = None, + date_from: Optional[datetime] = None, + date_to: Optional[datetime] = None, + launcher: Optional[str] = None, + first: int = 10, + skip: int = 0, + order_direction: OrderDirection = OrderDirection.DESC, + ): + """ + Initializes a filter for status events. + + :param chain_id: The chain ID where the events are recorded. + :param statuses: Optional list of statuses to filter by. + :param date_from: Optional start date for filtering. + :param date_to: Optional end date for filtering. + :param launcher: Optional launcher address to filter by. + :param first: Optional number of events per page. Default is 10. + :param skip: Optional number of events to skip. Default is 0. + :param order_direction: Optional order direction. Default is DESC. + """ + self.chain_id = chain_id + self.statuses = statuses or [ + Status.Launched, + Status.Pending, + Status.Partial, + Status.Paid, + Status.Complete, + Status.Cancelled, + ] + self.date_from = date_from + self.date_to = date_to + self.launcher = launcher + self.first = first + self.skip = skip + self.order_direction = order_direction + + +class WorkerFilter: + """ + A class used to filter workers. + """ + + def __init__( + self, + chain_id: ChainId, + worker_address: Optional[str] = None, + order_by: Optional[str] = "payoutCount", + order_direction: OrderDirection = OrderDirection.DESC, + first: int = 10, + skip: int = 0, + ): + """ + Initializes a WorkerFilter instance. + + :param chain_id: Chain ID to request data + :param worker_address: Address to filter by + :param order_by: Property to order by, e.g., "payoutCount" + :param order_direction: Order direction of results, "asc" or "desc" + :param first: Number of items per page + :param skip: Number of items to skip (for pagination) + """ + if order_direction.value not in set( + order_direction.value for order_direction in OrderDirection + ): + raise FilterError("Invalid order direction") + + self.chain_id = chain_id + self.worker_address = worker_address + self.order_by = order_by + self.order_direction = order_direction + self.first = min(max(first, 1), 1000) + self.skip = max(skip, 0) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/payout.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/payout.py index 59db6df906..a7337d12bf 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/payout.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/payout.py @@ -11,13 +11,16 @@ """ -def get_payouts_query(filter: PayoutFilter): +def get_payouts_query(filter: PayoutFilter) -> str: return """ query GetPayouts( - $escrowAddress: String - $recipient: String - $from: Int - $to: Int + $escrowAddress: String, + $recipient: String, + $from: Int, + $to: Int, + $first: Int, + $skip: Int, + $orderDirection: String ) {{ payouts( where: {{ @@ -26,6 +29,10 @@ def get_payouts_query(filter: PayoutFilter): {from_clause} {to_clause} }} + orderBy: createdAt + orderDirection: $orderDirection + first: $first + skip: $skip ) {{ ...PayoutFields }} diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py new file mode 100644 index 0000000000..868248dab6 --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py @@ -0,0 +1,51 @@ +from human_protocol_sdk.filter import WorkerFilter + +worker_fragment = """ +fragment WorkerFields on Worker { + id + address + totalAmountReceived + payoutCount +} +""" + + +def get_worker_query() -> str: + return """ +query GetWorker($address: String!) {{ + worker(id: $address) {{ + ...WorkerFields + }} +}} +{worker_fragment} +""".format( + worker_fragment=worker_fragment + ) + + +def get_workers_query(filter: WorkerFilter) -> str: + return """ +query GetWorkers( + $address: String + $orderBy: String + $orderDirection: String + $first: Int + $skip: Int +) {{ + workers( + where: {{ + {address_clause} + }} + orderBy: $orderBy + orderDirection: $orderDirection + first: $first + skip: $skip + ) {{ + ...WorkerFields + }} +}} +{worker_fragment} +""".format( + worker_fragment=worker_fragment, + address_clause=("address: $address" if filter.worker_address else ""), + ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/__init__.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/__init__.py new file mode 100644 index 0000000000..2d3c793143 --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/__init__.py @@ -0,0 +1,10 @@ +""" +This module enables to obtain worker information from subgraph. +""" + +from .worker_utils import ( + WorkerUtils, + WorkerData, + WorkerFilter, + WorkerUtilsError, +) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py new file mode 100644 index 0000000000..e4ce2f6930 --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py @@ -0,0 +1,140 @@ +import logging +from typing import List, Optional + +from web3 import Web3 + +from human_protocol_sdk.constants import NETWORKS, ChainId +from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.filter import WorkerFilter + +LOG = logging.getLogger("human_protocol_sdk.worker") + + +class WorkerUtilsError(Exception): + """ + Raised when an error occurs when getting data from subgraph. + """ + + pass + + +class WorkerData: + def __init__( + self, + id: str, + address: str, + total_amount_received: int, + payout_count: int, + ): + """ + Initializes a WorkerData instance. + + :param id: Worker ID + :param address: Worker address + :param total_amount_received: Total amount received by the worker + :param payout_count: Number of payouts received by the worker + """ + + self.id = id + self.address = address + self.total_amount_received = total_amount_received + self.payout_count = payout_count + + +class WorkerUtils: + """ + A utility class that provides additional worker-related functionalities. + """ + + @staticmethod + def get_workers(filter: WorkerFilter) -> List[WorkerData]: + """Get workers data of the protocol. + + :param filter: Worker filter + + :return: List of workers data + """ + + from human_protocol_sdk.gql.worker import get_workers_query + + workers = [] + network = NETWORKS.get(filter.chain_id) + if not network: + raise WorkerUtilsError("Unsupported Chain ID") + + workers_data = get_data_from_subgraph( + network, + query=get_workers_query(filter), + params={ + "address": filter.worker_address, + "orderBy": filter.order_by, + "orderDirection": filter.order_direction.value, + "first": filter.first, + "skip": filter.skip, + }, + ) + + if ( + not workers_data + or "data" not in workers_data + or "workers" not in workers_data["data"] + or not workers_data["data"]["workers"] + ): + return [] + + workers_raw = workers_data["data"]["workers"] + + for worker in workers_raw: + workers.append( + WorkerData( + id=worker.get("id", ""), + address=worker.get("address", ""), + total_amount_received=int(worker.get("totalAmountReceived", 0)), + payout_count=int(worker.get("payoutCount", 0)), + ) + ) + + return workers + + @staticmethod + def get_worker(chain_id: ChainId, worker_address: str) -> Optional[WorkerData]: + """Gets the worker details. + + :param chain_id: Network in which the worker exists + :param worker_address: Address of the worker + + :return: Worker data if exists, otherwise None + """ + + from human_protocol_sdk.gql.worker import get_worker_query + + network = NETWORKS.get(chain_id) + if not network: + raise WorkerUtilsError("Unsupported Chain ID") + + if not Web3.is_address(worker_address): + raise WorkerUtilsError(f"Invalid operator address: {worker_address}") + + network = NETWORKS[chain_id] + worker_data = get_data_from_subgraph( + network, + query=get_worker_query(), + params={"address": worker_address.lower()}, + ) + + if ( + not worker_data + or "data" not in worker_data + or "worker" not in worker_data["data"] + or not worker_data["data"]["worker"] + ): + return None + + worker = worker_data["data"]["worker"] + + return WorkerData( + id=worker.get("id", ""), + address=worker.get("address", ""), + total_amount_received=int(worker.get("totalAmountReceived", 0)), + payout_count=int(worker.get("payoutCount", 0)), + ) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 998ee706c8..a13681e2a0 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -2,7 +2,7 @@ from datetime import datetime from unittest.mock import patch -from human_protocol_sdk.constants import NETWORKS, ChainId, Status +from human_protocol_sdk.constants import NETWORKS, ChainId, Status, OrderDirection from human_protocol_sdk.gql.escrow import ( get_escrow_query, get_escrows_query, @@ -11,7 +11,12 @@ EscrowClientError, EscrowUtils, ) -from human_protocol_sdk.filter import EscrowFilter +from human_protocol_sdk.filter import ( + EscrowFilter, + FilterError, + StatusEventFilter, + PayoutFilter, +) class TestEscrowUtils(unittest.TestCase): @@ -181,15 +186,17 @@ def test_get_escrow_invalid_address_launcher(self): self.assertEqual("Invalid escrow address: invalid_address", str(cm.exception)) def test_get_status_events_unsupported_chain_id(self): + filter = StatusEventFilter(chain_id=9999) with self.assertRaises(EscrowClientError) as context: - EscrowUtils.get_status_events(9999) + EscrowUtils.get_status_events(filter) self.assertEqual(str(context.exception), "Unsupported Chain ID") def test_get_status_events_invalid_launcher(self): + filter = StatusEventFilter( + chain_id=ChainId.POLYGON_AMOY, launcher="invalid_address" + ) with self.assertRaises(EscrowClientError) as context: - EscrowUtils.get_status_events( - ChainId.POLYGON_AMOY, launcher="invalid_address" - ) + EscrowUtils.get_status_events(filter) self.assertEqual(str(context.exception), "Invalid Address") def test_get_status_events(self): @@ -208,9 +215,10 @@ def test_get_status_events(self): } } - result = EscrowUtils.get_status_events( - ChainId.POLYGON_AMOY, statuses=[Status.Pending] + filter = StatusEventFilter( + chain_id=ChainId.POLYGON_AMOY, statuses=[Status.Pending] ) + result = EscrowUtils.get_status_events(filter) self.assertEqual(len(result), 1) self.assertEqual(result[0].timestamp, 1620000000) @@ -237,12 +245,13 @@ def test_get_status_events_with_date_range(self): date_from = datetime(2021, 1, 1) date_to = datetime(2021, 12, 31) - result = EscrowUtils.get_status_events( - ChainId.POLYGON_AMOY, + filter = StatusEventFilter( + chain_id=ChainId.POLYGON_AMOY, statuses=[Status.Pending], date_from=date_from, date_to=date_to, ) + result = EscrowUtils.get_status_events(filter) self.assertEqual(len(result), 1) self.assertEqual(result[0].timestamp, 1620000000) @@ -256,9 +265,10 @@ def test_get_status_events_no_data(self): ) as mock_get_data_from_subgraph: mock_get_data_from_subgraph.return_value = {"data": {}} - result = EscrowUtils.get_status_events( - ChainId.POLYGON_AMOY, statuses=[Status.Pending] + filter = StatusEventFilter( + chain_id=ChainId.POLYGON_AMOY, statuses=[Status.Pending] ) + result = EscrowUtils.get_status_events(filter) self.assertEqual(len(result), 0) @@ -278,11 +288,12 @@ def test_get_status_events_with_launcher(self): } } - result = EscrowUtils.get_status_events( - ChainId.POLYGON_AMOY, + filter = StatusEventFilter( + chain_id=ChainId.POLYGON_AMOY, statuses=[Status.Pending], launcher="0x1234567890abcdef1234567890abcdef12345678", ) + result = EscrowUtils.get_status_events(filter) self.assertEqual(len(result), 1) self.assertEqual(result[0].timestamp, 1620000000) @@ -290,6 +301,139 @@ def test_get_status_events_with_launcher(self): self.assertEqual(result[0].status, "Pending") self.assertEqual(result[0].chain_id, ChainId.POLYGON_AMOY) + def test_get_payouts_unsupported_chain_id(self): + filter = PayoutFilter(chain_id=9999) + with self.assertRaises(EscrowClientError) as context: + EscrowUtils.get_payouts(filter) + self.assertEqual(str(context.exception), "Unsupported Chain ID") + + def test_get_payouts_invalid_escrow_address(self): + with self.assertRaises(FilterError) as context: + PayoutFilter( + chain_id=ChainId.POLYGON_AMOY, escrow_address="invalid_address" + ) + self.assertEqual(str(context.exception), "Invalid address: invalid_address") + + def test_get_payouts_invalid_recipient(self): + with self.assertRaises(FilterError) as context: + PayoutFilter(chain_id=ChainId.POLYGON_AMOY, recipient="invalid_address") + self.assertEqual(str(context.exception), "Invalid address: invalid_address") + + def test_get_payouts(self): + with patch( + "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + ) as mock_get_data_from_subgraph: + mock_get_data_from_subgraph.return_value = { + "data": { + "payouts": [ + { + "id": "1", + "escrowAddress": "0x1234567890123456789012345678901234567890", + "recipient": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "amount": "1000000000000000000", + "createdAt": "1672531200", + } + ] + } + } + + filter = PayoutFilter(chain_id=ChainId.POLYGON_AMOY) + result = EscrowUtils.get_payouts(filter) + + self.assertEqual(len(result), 1) + self.assertEqual(result[0].id, "1") + self.assertEqual( + result[0].escrow_address, "0x1234567890123456789012345678901234567890" + ) + self.assertEqual( + result[0].recipient, "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef" + ) + self.assertEqual(result[0].amount, 1000000000000000000) + self.assertEqual(result[0].created_at, 1672531200) + + def test_get_payouts_with_filters(self): + with patch( + "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + ) as mock_get_data_from_subgraph: + mock_get_data_from_subgraph.return_value = { + "data": { + "payouts": [ + { + "id": "1", + "escrowAddress": "0x1234567890123456789012345678901234567891", + "recipient": "0x1234567890123456789012345678901234567892", + "amount": "1000000000000000000", + "createdAt": "1672531200", + } + ] + } + } + + filter = PayoutFilter( + chain_id=ChainId.POLYGON_AMOY, + escrow_address="0x1234567890123456789012345678901234567891", + recipient="0x1234567890123456789012345678901234567892", + date_from=datetime(2023, 1, 1), + date_to=datetime(2023, 12, 31), + ) + result = EscrowUtils.get_payouts(filter) + + self.assertEqual(len(result), 1) + self.assertEqual(result[0].id, "1") + self.assertEqual( + result[0].escrow_address, "0x1234567890123456789012345678901234567891" + ) + self.assertEqual( + result[0].recipient, "0x1234567890123456789012345678901234567892" + ) + self.assertEqual(result[0].amount, 1000000000000000000) + self.assertEqual(result[0].created_at, 1672531200) + + def test_get_payouts_no_data(self): + with patch( + "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + ) as mock_get_data_from_subgraph: + mock_get_data_from_subgraph.return_value = {"data": {"payouts": []}} + + filter = PayoutFilter(chain_id=ChainId.POLYGON_AMOY) + result = EscrowUtils.get_payouts(filter) + + self.assertEqual(len(result), 0) + + def test_get_payouts_with_pagination(self): + with patch( + "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + ) as mock_get_data_from_subgraph: + mock_get_data_from_subgraph.return_value = { + "data": { + "payouts": [ + { + "id": "1", + "escrowAddress": "0x1234567890123456789012345678901234567890", + "recipient": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "amount": "1000000000000000000", + "createdAt": "1672531200", + }, + { + "id": "2", + "escrowAddress": "0x1234567890123456789012345678901234567890", + "recipient": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", + "amount": "2000000000000000000", + "createdAt": "1672617600", + }, + ] + } + } + + filter = PayoutFilter(chain_id=ChainId.POLYGON_AMOY, first=20, skip=10) + result = EscrowUtils.get_payouts(filter) + + self.assertEqual(len(result), 2) + self.assertEqual(result[0].id, "1") + self.assertEqual(result[1].id, "2") + self.assertEqual(result[0].amount, 1000000000000000000) + self.assertEqual(result[1].amount, 2000000000000000000) + if __name__ == "__main__": unittest.main(exit=True) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py new file mode 100644 index 0000000000..206029f953 --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py @@ -0,0 +1,148 @@ +import unittest +from unittest.mock import patch + +from human_protocol_sdk.constants import NETWORKS, ChainId, OrderDirection +from human_protocol_sdk.worker.worker_utils import WorkerUtils, WorkerUtilsError +from human_protocol_sdk.filter import WorkerFilter +from human_protocol_sdk.gql.worker import get_worker_query, get_workers_query + + +class TestWorkerUtils(unittest.TestCase): + def test_get_workers(self): + with patch( + "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + ) as mock_function: + mock_worker_1 = { + "id": "worker1", + "address": "0x1234567890123456789012345678901234567890", + "totalAmountReceived": "1000", + "payoutCount": 5, + } + mock_worker_2 = { + "id": "worker2", + "address": "0x9876543210987654321098765432109876543210", + "totalAmountReceived": "2000", + "payoutCount": 10, + } + + mock_function.return_value = { + "data": {"workers": [mock_worker_1, mock_worker_2]} + } + + filter = WorkerFilter( + chain_id=ChainId.POLYGON_AMOY, + order_by="totalAmountReceived", + order_direction=OrderDirection.ASC, + ) + + workers = WorkerUtils.get_workers(filter) + + mock_function.assert_called_once_with( + NETWORKS[ChainId.POLYGON_AMOY], + query=get_workers_query(filter), + params={ + "address": None, + "first": 10, + "skip": 0, + "orderBy": "totalAmountReceived", + "orderDirection": "asc", + }, + ) + self.assertEqual(len(workers), 2) + self.assertEqual(workers[0].id, "worker1") + self.assertEqual(workers[1].id, "worker2") + + def test_get_workers_empty_response(self): + with patch( + "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.return_value = {"data": {"workers": []}} + + filter = WorkerFilter( + chain_id=ChainId.POLYGON_AMOY, + worker_address="0x1234567890123456789012345678901234567890", + ) + + workers = WorkerUtils.get_workers(filter) + + mock_function.assert_called_once_with( + NETWORKS[ChainId.POLYGON_AMOY], + query=get_workers_query(filter), + params={ + "address": "0x1234567890123456789012345678901234567890", + "first": 10, + "skip": 0, + "orderBy": "payoutCount", + "orderDirection": "desc", + }, + ) + self.assertEqual(len(workers), 0) + + def test_get_workers_invalid_network(self): + with self.assertRaises(ValueError) as cm: + filter = WorkerFilter(chain_id=ChainId(12345)) + WorkerUtils.get_workers(filter) + self.assertEqual("12345 is not a valid ChainId", str(cm.exception)) + + def test_get_worker(self): + with patch( + "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + ) as mock_function: + mock_worker = { + "id": "worker1", + "address": "0x1234567890123456789012345678901234567890", + "totalAmountReceived": "1000", + "payoutCount": 5, + } + + mock_function.return_value = {"data": {"worker": mock_worker}} + + worker = WorkerUtils.get_worker( + ChainId.POLYGON_AMOY, + "0x1234567890123456789012345678901234567890", + ) + + mock_function.assert_called_once_with( + NETWORKS[ChainId.POLYGON_AMOY], + query=get_worker_query(), + params={"address": "0x1234567890123456789012345678901234567890"}, + ) + self.assertIsNotNone(worker) + self.assertEqual(worker.id, "worker1") + self.assertEqual( + worker.address, "0x1234567890123456789012345678901234567890" + ) + + def test_get_worker_empty_data(self): + with patch( + "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + ) as mock_function: + mock_function.return_value = {"data": {"worker": None}} + + worker = WorkerUtils.get_worker( + ChainId.POLYGON_AMOY, "0x1234567890123456789012345678901234567890" + ) + + mock_function.assert_called_once_with( + NETWORKS[ChainId.POLYGON_AMOY], + query=get_worker_query(), + params={"address": "0x1234567890123456789012345678901234567890"}, + ) + + self.assertIsNone(worker) + + def test_get_worker_invalid_chain_id(self): + with self.assertRaises(ValueError) as cm: + WorkerUtils.get_worker( + ChainId(123), "0x1234567890123456789012345678901234567891" + ) + self.assertEqual("123 is not a valid ChainId", str(cm.exception)) + + def test_get_worker_invalid_address(self): + with self.assertRaises(WorkerUtilsError) as cm: + WorkerUtils.get_worker(ChainId.POLYGON_AMOY, "invalid_address") + self.assertEqual("Invalid operator address: invalid_address", str(cm.exception)) + + +if __name__ == "__main__": + unittest.main(exit=True) diff --git a/packages/sdk/typescript/human-protocol-sdk/example/worker.ts b/packages/sdk/typescript/human-protocol-sdk/example/worker.ts new file mode 100644 index 0000000000..c90936f636 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/example/worker.ts @@ -0,0 +1,26 @@ +/* eslint-disable no-console */ +import { ChainId, OrderDirection } from '../src/enums'; +import { WorkerUtils } from '../src/worker'; + +export const getWorkers = async () => { + const workers = await WorkerUtils.getWorkers({ + chainId: ChainId.POLYGON_AMOY, + first: 100, + skip: 0, + orderBy: 'payoutCount', + orderDirection: OrderDirection.DESC, + }); + + console.log('Filtered workers:', workers); + + const worker = await WorkerUtils.getWorker( + ChainId.POLYGON_AMOY, + workers[0].address + ); + + console.log('Worker details:', worker); +}; + +(async () => { + await getWorkers(); +})(); diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts index 97d51bdfef..46474bdf6b 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts @@ -44,16 +44,23 @@ import { EscrowData, GET_ESCROWS_QUERY, GET_ESCROW_BY_ADDRESS_QUERY, + GET_PAYOUTS_QUERY, GET_STATUS_UPDATES_QUERY, StatusEvent, } from './graphql'; -import { IEscrowConfig, IEscrowsFilter } from './interfaces'; +import { + IEscrowConfig, + IEscrowsFilter, + IPayoutFilter, + IStatusEventFilter, +} from './interfaces'; import { EscrowCancel, EscrowStatus, EscrowWithdraw, NetworkData, TransactionLikeWithNonce, + Payout, } from './types'; import { getSubgraphUrl, @@ -1828,14 +1835,7 @@ export class EscrowUtils { * }; * ``` * - * @param {ChainId} chainId - List of network IDs to query for status events. - * @param {EscrowStatus[]} [statuses] - Optional array of statuses to query for. If not provided, queries for all statuses. - * @param {Date} [from] - Optional start date to filter events. - * @param {Date} [to] - Optional end date to filter events. - * @param {string} [launcher] - Optional launcher address to filter events. Must be a valid Ethereum address. - * @param {number} [first] - Optional number of transactions per page. Default is 10. - * @param {number} [skip] - Optional number of transactions to skip. Default is 0. - * @param {OrderDirection} [orderDirection] - Optional order of the results. Default is DESC. + * @param {IStatusEventFilter} filter Filter parameters. * @returns {Promise} - Array of status events with their corresponding statuses. * * **Code example** @@ -1846,34 +1846,38 @@ export class EscrowUtils { * (async () => { * const fromDate = new Date('2023-01-01'); * const toDate = new Date('2023-12-31'); - * const statusEvents = await EscrowUtils.getStatusEvents( - * [ChainId.POLYGON, ChainId.MAINNET], - * [EscrowStatus.Pending, EscrowStatus.Complete], - * fromDate, - * toDate - * ); + * const statusEvents = await EscrowUtils.getStatusEvents({ + * chainId: ChainId.POLYGON, + * statuses: [EscrowStatus.Pending, EscrowStatus.Complete], + * from: fromDate, + * to: toDate + * }); * console.log(statusEvents); * })(); * ``` */ - public static async getStatusEvents( - chainId: ChainId, - statuses?: EscrowStatus[], - from?: Date, - to?: Date, - launcher?: string, - first?: number, - skip?: number, - orderDirection?: OrderDirection + filter: IStatusEventFilter ): Promise { + const { + chainId, + statuses, + from, + to, + launcher, + first = 10, + skip = 0, + orderDirection = OrderDirection.DESC, + } = filter; + if (launcher && !ethers.isAddress(launcher)) { throw ErrorInvalidAddress; } - first = first !== undefined ? Math.min(first, 1000) : 10; - skip = skip || 0; - orderDirection = orderDirection || OrderDirection.DESC; + const networkData = NETWORKS[chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } // If statuses are not provided, use all statuses except Launched const effectiveStatuses = statuses ?? [ @@ -1885,11 +1889,6 @@ export class EscrowUtils { EscrowStatus.Cancelled, ]; - const networkData = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); const data = await gqlFetch<{ @@ -1902,9 +1901,9 @@ export class EscrowUtils { from: from ? getUnixTimestamp(from) : undefined, to: to ? getUnixTimestamp(to) : undefined, launcher: launcher || undefined, - orderDirection: orderDirection, - first: first, - skip: skip, + orderDirection, + first: Math.min(first, 1000), + skip, } ); @@ -1921,4 +1920,64 @@ export class EscrowUtils { return eventsWithChainId; } + + /** + * This function returns the payouts for a given set of networks. + * + * > This uses Subgraph + * + * **Input parameters** + * Fetch payouts from the subgraph. + * + * @param {IPayoutFilter} filter Filter parameters. + * @returns {Promise} List of payouts matching the filters. + * + * **Code example** + * + * ```ts + * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; + * + * const payouts = await EscrowUtils.getPayouts({ + * chainId: ChainId.POLYGON, + * escrowAddress: '0x1234567890123456789012345678901234567890', + * recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + * from: new Date('2023-01-01'), + * to: new Date('2023-12-31') + * }); + * console.log(payouts); + * ``` + */ + public static async getPayouts(filter: IPayoutFilter): Promise { + const networkData = NETWORKS[filter.chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { + throw ErrorInvalidAddress; + } + if (filter.recipient && !ethers.isAddress(filter.recipient)) { + throw ErrorInvalidAddress; + } + + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + + const { payouts } = await gqlFetch<{ payouts: Payout[] }>( + getSubgraphUrl(networkData), + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: filter.escrowAddress?.toLowerCase(), + recipient: filter.recipient?.toLowerCase(), + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + first: Math.min(first, 1000), + skip, + orderDirection, + } + ); + + return payouts || []; + } } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/payout.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/payout.ts index bb134efbc1..b60df53460 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/payout.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/payout.ts @@ -1,5 +1,5 @@ import gql from 'graphql-tag'; -import { IPayoutFilter } from '../../interfaces'; +import { IPayoutFilter } from 'src/interfaces'; const PAYOUT_FRAGMENT = gql` fragment PayoutFields on Payout { @@ -22,9 +22,6 @@ export const GET_PAYOUTS_QUERY = (filter: IPayoutFilter) => { ${to ? `createdAt_lt: $to` : ''} } `; - const LIMIT_CLAUSE = ` - first: 1000 - `; return gql` query getPayouts( @@ -32,12 +29,16 @@ export const GET_PAYOUTS_QUERY = (filter: IPayoutFilter) => { $recipient: String $from: Int $to: Int + $first: Int + $skip: Int + $orderDirection: String ) { payouts( ${WHERE_CLAUSE} orderBy: createdAt, - orderDirection: desc, - ${LIMIT_CLAUSE} + orderDirection: $orderDirection, + first: $first, + skip: $skip ) { ...PayoutFields } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/worker.ts new file mode 100644 index 0000000000..6a2b2cf612 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/worker.ts @@ -0,0 +1,41 @@ +import gql from 'graphql-tag'; +import { IWorkersFilter } from '../../interfaces'; + +export const GET_WORKER_QUERY = gql` + query GetWorker($address: String!) { + worker(id: $address) { + id + address + totalAmountReceived + payoutCount + } + } +`; + +export const GET_WORKERS_QUERY = (filter: IWorkersFilter) => { + const { address } = filter; + + return gql` + query GetWorkers( + $address: String + $first: Int + $skip: Int + $orderBy: String + $orderDirection: String + ) { + workers( + where: { + ${address ? 'address: $address,' : ''} + } + first: $first + skip: $skip + orderBy: $orderBy + orderDirection: $orderDirection + ) { + id + address + totalAmountReceived + payoutCount + } + }`; +}; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/index.ts index e7abf36174..dd79fbbea4 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/index.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/index.ts @@ -6,6 +6,7 @@ import { StatisticsClient } from './statistics'; import { Encryption, EncryptionUtils } from './encryption'; import { OperatorUtils } from './operator'; import { TransactionUtils } from './transaction'; +import { WorkerUtils } from './worker'; export * from './constants'; export * from './types'; @@ -24,4 +25,5 @@ export { EncryptionUtils, OperatorUtils, TransactionUtils, + WorkerUtils, }; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index 33cd772662..aadcb6afb2 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -103,7 +103,8 @@ export interface IHMTHoldersParams extends IPagination { address?: string; } -export interface IPayoutFilter { +export interface IPayoutFilter extends IPagination { + chainId: ChainId; escrowAddress?: string; recipient?: string; from?: Date; @@ -161,3 +162,24 @@ export interface StakerInfo { lockedUntil: bigint; withdrawableAmount: bigint; } + +export interface IStatusEventFilter extends IPagination { + chainId: ChainId; + statuses?: EscrowStatus[]; + from?: Date; + to?: Date; + launcher?: string; +} + +export interface IWorker { + id: string; + address: string; + totalAmountReceived: number; + payoutCount: number; +} + +export interface IWorkersFilter extends IPagination { + chainId: ChainId; + address?: string; + orderBy?: string; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/types.ts index f16c230091..17ac94cd92 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/types.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/types.ts @@ -171,4 +171,30 @@ export type EscrowWithdraw = { amountWithdrawn: bigint; }; +/** + * Represents a payout from an escrow. + */ +export type Payout = { + /** + * Unique identifier of the payout. + */ + id: string; + /** + * The address of the escrow associated with the payout. + */ + escrowAddress: string; + /** + * The address of the recipient who received the payout. + */ + recipient: string; + /** + * The amount paid to the recipient. + */ + amount: bigint; + /** + * The timestamp when the payout was created (in UNIX format). + */ + createdAt: number; +}; + export type TransactionLikeWithNonce = TransactionLike & { nonce: number }; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts new file mode 100644 index 0000000000..7e9e757cbb --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts @@ -0,0 +1,120 @@ +import gqlFetch from 'graphql-request'; +import { NETWORKS } from './constants'; +import { ChainId, OrderDirection } from './enums'; +import { ErrorInvalidAddress, ErrorUnsupportedChainID } from './error'; +import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from './graphql/queries/worker'; +import { IWorker, IWorkersFilter } from './interfaces'; +import { getSubgraphUrl } from './utils'; +import { ethers } from 'ethers'; + +export class WorkerUtils { + /** + * This function returns the worker data for the given address. + * + * @param {ChainId} chainId The chain ID. + * @param {string} address The worker address. + * @returns {Promise} Returns the worker details. + * + * **Code example** + * + * ```ts + * import { WorkerUtils, ChainId } from '@human-protocol/sdk'; + * + * const worker = await WorkerUtils.getWorker(ChainId.POLYGON, '0x1234567890abcdef1234567890abcdef12345678'); + * ``` + */ + public static async getWorker( + chainId: ChainId, + address: string + ): Promise { + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + if (!ethers.isAddress(address)) { + throw ErrorInvalidAddress; + } + + const { worker } = await gqlFetch<{ + worker: IWorker; + }>(getSubgraphUrl(networkData), GET_WORKER_QUERY, { + address: address.toLowerCase(), + }); + + return worker || null; + } + + /** + * This function returns all worker details based on the provided filter. + * + * **Input parameters** + * + * ```ts + * interface IWorkersFilter { + * chainId: ChainId; // List of chain IDs to query. + * address?: string; // (Optional) The worker address to filter by. + * orderBy?: string; // (Optional) The field to order by. Default is 'payoutCount'. + * orderDirection?: OrderDirection; // (Optional) The direction of the order. Default is 'DESC'. + * first?: number; // (Optional) Number of workers per page. Default is 10. + * skip?: number; // (Optional) Number of workers to skip. Default is 0. + * } + * ``` + * + * ```ts + * type IWorker = { + * id: string; + * address: string; + * totalAmountReceived: string; + * payoutCount: number; + * }; + * ``` + * + * @param {IWorkersFilter} filter Filter for the workers. + * @returns {Promise} Returns an array with all the worker details. + * + * **Code example** + * + * ```ts + * import { WorkerUtils, ChainId } from '@human-protocol/sdk'; + * + * const filter: IWorkersFilter = { + * chainId: ChainId.POLYGON, + * first: 10, + * skip: 0, + * }; + * const workers = await WorkerUtils.getWorkers(filter); + * ``` + */ + public static async getWorkers(filter: IWorkersFilter): Promise { + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderBy = filter.orderBy || 'payoutCount'; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + + const networkData = NETWORKS[filter.chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + if (filter.address && !ethers.isAddress(filter.address)) { + throw ErrorInvalidAddress; + } + + const { workers } = await gqlFetch<{ + workers: IWorker[]; + }>(getSubgraphUrl(networkData), GET_WORKERS_QUERY(filter), { + address: filter?.address?.toLowerCase(), + first: first, + skip: skip, + orderBy: orderBy, + orderDirection: orderDirection, + }); + + if (!workers) { + return []; + } + + return workers; + } +} diff --git a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts index f2d3419e85..b9d69c0a01 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts @@ -35,7 +35,11 @@ import { InvalidEthereumAddressError, } from '../src/error'; import { EscrowClient, EscrowUtils } from '../src/escrow'; -import { GET_ESCROWS_QUERY, GET_ESCROW_BY_ADDRESS_QUERY } from '../src/graphql'; +import { + GET_ESCROWS_QUERY, + GET_ESCROW_BY_ADDRESS_QUERY, + GET_PAYOUTS_QUERY, +} from '../src/graphql'; import { EscrowStatus } from '../src/types'; import { DEFAULT_GAS_PAYER_PRIVKEY, @@ -2917,13 +2921,10 @@ describe('EscrowUtils', () => { test('should throw an error if launcher address is invalid', async () => { await expect( - EscrowUtils.getStatusEvents( - ChainId.POLYGON_AMOY, - undefined, - undefined, - undefined, - 'invalid_address' - ) + EscrowUtils.getStatusEvents({ + chainId: ChainId.POLYGON_AMOY, + launcher: 'invalid_address', + }) ).rejects.toThrow(ErrorInvalidAddress); }); @@ -2947,7 +2948,9 @@ describe('EscrowUtils', () => { .spyOn(gqlFetch, 'default') .mockResolvedValueOnce({ escrowStatusEvents: pendingEvents }); - const result = await EscrowUtils.getStatusEvents(ChainId.LOCALHOST); + const result = await EscrowUtils.getStatusEvents({ + chainId: ChainId.LOCALHOST, + }); expect(result).toEqual(pendingEvents); expect(gqlFetchSpy).toHaveBeenCalled(); }); @@ -2975,12 +2978,11 @@ describe('EscrowUtils', () => { .spyOn(gqlFetch, 'default') .mockResolvedValueOnce({ escrowStatusEvents: pendingEvents }); - const result = await EscrowUtils.getStatusEvents( - ChainId.POLYGON_AMOY, - undefined, - fromDate, - toDate - ); + const result = await EscrowUtils.getStatusEvents({ + chainId: ChainId.POLYGON_AMOY, + from: fromDate, + to: toDate, + }); expect(result).toEqual(pendingEvents); expect(gqlFetchSpy).toHaveBeenCalled(); @@ -3009,12 +3011,12 @@ describe('EscrowUtils', () => { .spyOn(gqlFetch, 'default') .mockResolvedValueOnce({ escrowStatusEvents: partialEvents }); - const result = await EscrowUtils.getStatusEvents( - ChainId.POLYGON_AMOY, - [EscrowStatus.Partial], - fromDate, - toDate - ); + const result = await EscrowUtils.getStatusEvents({ + chainId: ChainId.POLYGON_AMOY, + statuses: [EscrowStatus.Partial], + from: fromDate, + to: toDate, + }); expect(result).toEqual(partialEvents); expect(gqlFetchSpy).toHaveBeenCalled(); @@ -3043,15 +3045,235 @@ describe('EscrowUtils', () => { .spyOn(gqlFetch, 'default') .mockResolvedValueOnce({ escrowStatusEvents: pendingEvents }); - const result = await EscrowUtils.getStatusEvents( - ChainId.POLYGON_AMOY, - undefined, - fromDate, - toDate - ); + const result = await EscrowUtils.getStatusEvents({ + chainId: ChainId.POLYGON_AMOY, + from: fromDate, + to: toDate, + }); expect(result).toEqual(pendingEvents); expect(gqlFetchSpy).toHaveBeenCalled(); }); }); + + describe('getPayouts', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + test('should throw an error if chainId is invalid', async () => { + await expect( + EscrowUtils.getPayouts({ chainId: 123 } as any) + ).rejects.toThrow(ErrorUnsupportedChainID); + }); + + test('should throw an error if escrowAddress is an invalid address', async () => { + const filter = { + chainId: ChainId.POLYGON_AMOY, + escrowAddress: 'invalid_address', + }; + + await expect(EscrowUtils.getPayouts(filter)).rejects.toThrow( + ErrorInvalidAddress + ); + }); + + test('should throw an error if recipient is an invalid address', async () => { + const filter = { + chainId: ChainId.POLYGON_AMOY, + recipient: 'invalid_address', + }; + + await expect(EscrowUtils.getPayouts(filter)).rejects.toThrow( + ErrorInvalidAddress + ); + }); + + test('should successfully getPayouts', async () => { + const payouts = [ + { + id: '1', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '1000000000000000000', + createdAt: '1672531200', + }, + { + id: '2', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '2000000000000000000', + createdAt: '1672617600', + }, + ]; + + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue({ payouts }); + + const filter = { + chainId: ChainId.POLYGON_AMOY, + }; + + const result = await EscrowUtils.getPayouts(filter); + expect(result).toEqual(payouts); + expect(gqlFetchSpy).toHaveBeenCalledWith( + 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: undefined, + recipient: undefined, + from: undefined, + to: undefined, + first: 10, + skip: 0, + orderDirection: OrderDirection.DESC, + } + ); + }); + + test('should successfully getPayouts with filters', async () => { + const payouts = [ + { + id: '1', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '1000000000000000000', + createdAt: '1672531200', + }, + ]; + + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue({ payouts }); + + const filter = { + chainId: ChainId.POLYGON_AMOY, + escrowAddress: ethers.ZeroAddress, + recipient: ethers.ZeroAddress, + from: new Date('2023-01-01'), + to: new Date('2023-01-02'), + }; + + const result = await EscrowUtils.getPayouts(filter); + expect(result).toEqual(payouts); + expect(gqlFetchSpy).toHaveBeenCalledWith( + 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: filter.escrowAddress.toLowerCase(), + recipient: filter.recipient.toLowerCase(), + from: Math.floor(filter.from.getTime() / 1000), + to: Math.floor(filter.to.getTime() / 1000), + first: 10, + skip: 0, + orderDirection: OrderDirection.DESC, + } + ); + }); + + test('should successfully getPayouts with pagination', async () => { + const payouts = [ + { + id: '1', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '1000000000000000000', + createdAt: '1672531200', + }, + { + id: '2', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '2000000000000000000', + createdAt: '1672617600', + }, + ]; + + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue({ payouts }); + + const filter = { + chainId: ChainId.POLYGON_AMOY, + first: 20, + skip: 10, + }; + + const result = await EscrowUtils.getPayouts(filter); + expect(result).toEqual(payouts); + expect(gqlFetchSpy).toHaveBeenCalledWith( + 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: undefined, + recipient: undefined, + from: undefined, + to: undefined, + first: 20, + skip: 10, + orderDirection: OrderDirection.DESC, + } + ); + }); + + test('should successfully getPayouts with pagination over limits', async () => { + const payouts = [ + { + id: '1', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '1000000000000000000', + createdAt: '1672531200', + }, + { + id: '2', + escrowAddress: '0x1234567890123456789012345678901234567890', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + amount: '2000000000000000000', + createdAt: '1672617600', + }, + ]; + + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue({ payouts }); + + const filter = { + chainId: ChainId.POLYGON_AMOY, + first: 20000, + skip: 10, + }; + + const result = await EscrowUtils.getPayouts(filter); + expect(result).toEqual(payouts); + expect(gqlFetchSpy).toHaveBeenCalledWith( + 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: undefined, + recipient: undefined, + from: undefined, + to: undefined, + first: 1000, + skip: 10, + orderDirection: OrderDirection.DESC, + } + ); + }); + + test('should return an empty array if no payouts are found', async () => { + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue({ payouts: [] }); + + const filter = { + chainId: ChainId.POLYGON_AMOY, + }; + + const result = await EscrowUtils.getPayouts(filter); + expect(result).toEqual([]); + expect(gqlFetchSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts index 3f32f81200..3af3444d6b 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts @@ -6,7 +6,7 @@ import { NETWORKS } from '../src/constants'; import { ChainId, OrderDirection } from '../src/enums'; import { ErrorCannotUseDateAndBlockSimultaneously, - ErrorInvalidHahsProvided, + ErrorInvalidHashProvided, } from '../src/error'; import { GET_TRANSACTION_QUERY } from '../src/graphql/queries/transaction'; import { ITransaction, ITransactionsFilter } from '../src/interfaces'; @@ -64,7 +64,7 @@ describe('TransactionUtils', () => { test('should throw an error for an invalid transaction hash', async () => { await expect( TransactionUtils.getTransaction(ChainId.LOCALHOST, invalidHash) - ).rejects.toThrow(ErrorInvalidHahsProvided); + ).rejects.toThrow(ErrorInvalidHashProvided); }); test('should throw an error if the gql fetch fails', async () => { diff --git a/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts new file mode 100644 index 0000000000..2e3ce3a721 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts @@ -0,0 +1,200 @@ +import * as gqlFetch from 'graphql-request'; +import { describe, expect, test, vi } from 'vitest'; +import { NETWORKS } from '../src/constants'; +import { ChainId, OrderDirection } from '../src/enums'; +import { ErrorInvalidAddress } from '../src/error'; +import { + GET_WORKER_QUERY, + GET_WORKERS_QUERY, +} from '../src/graphql/queries/worker'; +import { IWorker, IWorkersFilter } from '../src/interfaces'; +import { WorkerUtils } from '../src/worker'; + +vi.mock('graphql-request', () => { + return { + default: vi.fn(), + }; +}); + +describe('WorkerUtils', () => { + describe('getWorker', () => { + const workerAddress = '0x1234567890abcdef1234567890abcdef12345678'; + const mockWorker: IWorker = { + id: workerAddress, + address: workerAddress, + totalAmountReceived: 1000, + payoutCount: 10, + }; + + test('should return worker details', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default').mockResolvedValueOnce({ + worker: mockWorker, + }); + + const result = await WorkerUtils.getWorker( + ChainId.LOCALHOST, + workerAddress + ); + + expect(gqlFetchSpy).toHaveBeenCalledWith( + NETWORKS[ChainId.LOCALHOST]?.subgraphUrl, + GET_WORKER_QUERY, + { + address: workerAddress.toLowerCase(), + } + ); + expect(result).toEqual(mockWorker); + }); + + test('should return null if worker is not found', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default').mockResolvedValueOnce({ + worker: null, + }); + + const result = await WorkerUtils.getWorker( + ChainId.LOCALHOST, + workerAddress + ); + + expect(gqlFetchSpy).toHaveBeenCalledWith( + NETWORKS[ChainId.LOCALHOST]?.subgraphUrl, + GET_WORKER_QUERY, + { + address: workerAddress.toLowerCase(), + } + ); + expect(result).toBeNull(); + }); + + test('should throw an error for an invalid transaction hash', async () => { + await expect( + WorkerUtils.getWorker(ChainId.LOCALHOST, 'invalid_address') + ).rejects.toThrow(ErrorInvalidAddress); + }); + + test('should throw an error if the gql fetch fails', async () => { + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValueOnce(new Error('Error')); + + await expect( + WorkerUtils.getWorker(ChainId.LOCALHOST, workerAddress) + ).rejects.toThrow(); + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('getWorkers', () => { + const mockWorkers: IWorker[] = [ + { + id: '0x1234567890abcdef1234567890abcdef12345678', + address: '0x1234567890abcdef1234567890abcdef12345678', + totalAmountReceived: 1000, + payoutCount: 10, + }, + { + id: '0xabcdefabcdefabcdefabcdefabcdefabcdef', + address: '0xabcdefabcdefabcdefabcdefabcdefabcdef', + totalAmountReceived: 2000, + payoutCount: 20, + }, + ]; + + test('should return a list of workers', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default').mockResolvedValueOnce({ + workers: mockWorkers, + }); + + const filter: IWorkersFilter = { + chainId: ChainId.LOCALHOST, + first: 10, + skip: 0, + orderBy: 'totalAmountReceived', + orderDirection: OrderDirection.ASC, + }; + + const result = await WorkerUtils.getWorkers(filter); + + expect(gqlFetchSpy).toHaveBeenCalledWith( + NETWORKS[filter.chainId]?.subgraphUrl, + GET_WORKERS_QUERY(filter), + { + address: undefined, + first: 10, + skip: 0, + orderBy: 'totalAmountReceived', + orderDirection: 'asc', + } + ); + expect(result).toEqual(mockWorkers); + }); + + test('should return an empty list if no workers are found', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default').mockResolvedValueOnce({ + workers: [], + }); + + const filter: IWorkersFilter = { + chainId: ChainId.LOCALHOST, + first: 10, + skip: 0, + }; + + const result = await WorkerUtils.getWorkers(filter); + + expect(gqlFetchSpy).toHaveBeenCalledWith( + NETWORKS[filter.chainId]?.subgraphUrl, + GET_WORKERS_QUERY(filter), + { + address: undefined, + first: 10, + skip: 0, + orderBy: 'payoutCount', + orderDirection: 'desc', + } + ); + expect(result).toEqual([]); + }); + test('should throw an error if the gql fetch fails', async () => { + const filter: IWorkersFilter = { + chainId: ChainId.LOCALHOST, + first: 10, + skip: 0, + }; + + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValueOnce(new Error('Error')); + + await expect(WorkerUtils.getWorkers(filter)).rejects.toThrow(); + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + }); + + test('should return an array of transactions with pagination over limits', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default').mockResolvedValueOnce({ + workers: mockWorkers, + }); + + const filter: IWorkersFilter = { + chainId: ChainId.LOCALHOST, + first: 2000, + skip: 0, + }; + + const result = await WorkerUtils.getWorkers(filter); + + expect(gqlFetchSpy).toHaveBeenCalledWith( + NETWORKS[filter.chainId]?.subgraphUrl, + GET_WORKERS_QUERY(filter), + { + address: undefined, + first: 1000, + skip: 0, + orderBy: 'payoutCount', + orderDirection: 'desc', + } + ); + expect(result).toEqual(mockWorkers); + }); + }); +}); diff --git a/scripts/cvat/docker-compose.local.yml b/scripts/cvat/docker-compose.local.yml index 6370bc0dd9..1bcc74669b 100644 --- a/scripts/cvat/docker-compose.local.yml +++ b/scripts/cvat/docker-compose.local.yml @@ -440,9 +440,7 @@ services: PORT: *backend_apps_internal_port POSTGRES_DATABASE: job-launcher S3_BUCKET: *bucket_name_manifests - REPUTATION_ORACLES: *exchange_oracle_address - CVAT_EXCHANGE_ORACLE_ADDRESS: *exchange_oracle_address - CVAT_RECORDING_ORACLE_ADDRESS: *recording_oracle_address + REPUTATION_ORACLES: *reputation_oracle_address REPUTATION_ORACLE_ADDRESS: *reputation_oracle_address FE_URL: http://localhost:${JOB_LAUNCHER_CLIENT_PORT:?} diff --git a/scripts/cvat/env-files/.env.job-launcher b/scripts/cvat/env-files/.env.job-launcher index 8dcf04083d..9ec113276b 100644 --- a/scripts/cvat/env-files/.env.job-launcher +++ b/scripts/cvat/env-files/.env.job-launcher @@ -42,11 +42,8 @@ f68MBhP9N6oE =dNxu -----END PGP PUBLIC KEY BLOCK-----" -FORTUNE_EXCHANGE_ORACLE_ADDRESS=disabled -FORTUNE_RECORDING_ORACLE_ADDRESS=disabled HCAPTCHA_RECORDING_ORACLE_URI=disabled HCAPTCHA_REPUTATION_ORACLE_URI=disabled -HCAPTCHA_ORACLE_ADDRESS=disabled # Auth JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- diff --git a/scripts/fortune/.env.jl-server b/scripts/fortune/.env.jl-server index d808ebf43b..eab5941b7f 100644 --- a/scripts/fortune/.env.jl-server +++ b/scripts/fortune/.env.jl-server @@ -22,14 +22,9 @@ GAS_PRICE_MULTIPLIER=1 # PGP_PRIVATE_KEY= PGP_ENCRYPT=false # PGP_PASSPHRASE= -FORTUNE_EXCHANGE_ORACLE_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC -FORTUNE_RECORDING_ORACLE_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 -CVAT_EXCHANGE_ORACLE_ADDRESS=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC -CVAT_RECORDING_ORACLE_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 REPUTATION_ORACLE_ADDRESS=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 HCAPTCHA_RECORDING_ORACLE_URI=a HCAPTCHA_REPUTATION_ORACLE_URI=a -HCAPTCHA_ORACLE_ADDRESS=a HCAPTCHA_SITE_KEY=a # Auth