diff --git a/.changeset/cozy-poets-go.md b/.changeset/cozy-poets-go.md new file mode 100644 index 0000000000..7728c99e1f --- /dev/null +++ b/.changeset/cozy-poets-go.md @@ -0,0 +1,6 @@ +--- +"@human-protocol/sdk": major +"@human-protocol/python-sdk": major +--- + +Updated KV Store utils in sdk to return empty string in case no value in subgraph instead of throwing and error diff --git a/.changeset/crisp-buckets-agree.md b/.changeset/crisp-buckets-agree.md new file mode 100644 index 0000000000..3915fea605 --- /dev/null +++ b/.changeset/crisp-buckets-agree.md @@ -0,0 +1,5 @@ +--- +"@human-protocol/sdk": minor +--- + +Added typed subgraph errors (SubgraphRequestError, SubgraphBadIndexerError) and wrapped subgraph request failures with these classes diff --git a/.changeset/nine-seas-dream.md b/.changeset/nine-seas-dream.md new file mode 100644 index 0000000000..a6c0ea17cd --- /dev/null +++ b/.changeset/nine-seas-dream.md @@ -0,0 +1,11 @@ +--- +"@human-protocol/core": major +"@human-protocol/sdk": major +"@human-protocol/python-sdk": major +--- + +Updated escrow contracts and SDKs to fetch oracle fees from `KVStore` instead of passing fee values during escrow setup. `Escrow.setup(...)` and factory setup flows no longer accept fee arguments, escrow deployments now require a `KVStore` address, and fee validation is enforced on-chain from `KVStore` values, including per-oracle and total fee limits. Added upgrade-safe `EscrowFactory` support for storing and updating the `KVStore` address. + +Updated TypeScript and Python SDK escrow setup APIs to match the new contract signatures by removing fee arguments from `setup(...)` and create-and-setup helpers. Existing config fee fields remain optional for backward compatibility but are ignored by setup calls. + +Updated SDKs to use a dedicated HMT stats subgraph endpoint for HMT statistics methods and removed `totalAmountPaid` and `averageAmountPerWorker` from `IDailyPayment`. diff --git a/.changeset/shaggy-months-post.md b/.changeset/shaggy-months-post.md new file mode 100644 index 0000000000..183d64c44e --- /dev/null +++ b/.changeset/shaggy-months-post.md @@ -0,0 +1,5 @@ +--- +"@human-protocol/sdk": patch +--- + +Split combined domain files into module folders with explicit files per responsibility. diff --git a/.github/workflows/cd-packages.yaml b/.github/workflows/cd-packages.yaml index 8aea8c284a..583e54b4f8 100644 --- a/.github/workflows/cd-packages.yaml +++ b/.github/workflows/cd-packages.yaml @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - token: ${{ secrets.GH_GITBOOK_TOKEN }} + token: ${{ secrets.RELEASE_GH_TOKEN }} - name: Setup git identity run: | git config --global user.name "github-actions[bot]" diff --git a/.github/workflows/cd-python-sdk.yaml b/.github/workflows/cd-python-sdk.yaml index 30b69eb072..e323231a1c 100644 --- a/.github/workflows/cd-python-sdk.yaml +++ b/.github/workflows/cd-python-sdk.yaml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - token: ${{ secrets.GH_GITBOOK_TOKEN }} + token: ${{ secrets.RELEASE_GH_TOKEN }} - name: Setup git identity run: | git config --global user.name "github-actions[bot]" diff --git a/.github/workflows/cd-subgraph.yaml b/.github/workflows/cd-subgraph-hmt.yaml similarity index 85% rename from .github/workflows/cd-subgraph.yaml rename to .github/workflows/cd-subgraph-hmt.yaml index 4bd0f85114..c8a3306ba6 100644 --- a/.github/workflows/cd-subgraph.yaml +++ b/.github/workflows/cd-subgraph-hmt.yaml @@ -1,4 +1,8 @@ -name: Subgraph deployment +name: HMT Subgraph Deployment + +permissions: + contents: read + on: workflow_dispatch: inputs: @@ -11,7 +15,7 @@ on: jobs: subgraph: - name: Deploy Subgraph + name: Deploy HMT Subgraph runs-on: ubuntu-latest strategy: matrix: @@ -45,17 +49,17 @@ jobs: fi done echo "Match found: $MATCH" - echo "::set-output name=continue::$MATCH" + echo "continue=$MATCH" >> "$GITHUB_OUTPUT" - name: Install dependencies if: steps.filter_networks.outputs.continue == 'true' - run: yarn workspaces focus @tools/subgraph + run: yarn workspaces focus @tools/subgraph-hmt - name: Build packages (scoped) if: steps.filter_networks.outputs.continue == 'true' - run: yarn workspaces foreach -Rpt --from @tools/subgraph run build + run: yarn workspaces foreach -Rpt --from @tools/subgraph-hmt run build - name: Generate and build Subgraph if: steps.filter_networks.outputs.continue == 'true' run: yarn generate && yarn build - working-directory: ./packages/sdk/typescript/subgraph + working-directory: ./packages/subgraph/hmt env: NETWORK: ${{ matrix.network.name }} - name: Authenticate & Deploy @@ -64,9 +68,9 @@ jobs: API_KEY: ${{ secrets.HP_GRAPH_API_KEY }} NETWORK: ${{ matrix.network.name }} LABEL: ${{ github.event.inputs.label }} - working-directory: ./packages/sdk/typescript/subgraph + working-directory: ./packages/subgraph/hmt run: | yarn dlx @graphprotocol/graph-cli@0.71.2 \ auth --studio "$API_KEY" yarn dlx @graphprotocol/graph-cli@0.71.2 \ - deploy --studio ${NETWORK} -l ${LABEL} + deploy --studio hmt-${NETWORK} -l ${LABEL} diff --git a/.github/workflows/cd-subgraph-human.yaml b/.github/workflows/cd-subgraph-human.yaml new file mode 100644 index 0000000000..0b54337f57 --- /dev/null +++ b/.github/workflows/cd-subgraph-human.yaml @@ -0,0 +1,76 @@ +name: Human Protocol Subgraph Deployment + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + label: + description: "New version label" + required: true + networks: + description: "Comma-separated list of networks to deploy" + required: true + +jobs: + subgraph: + name: Deploy Human Protocol Subgraph + runs-on: ubuntu-latest + strategy: + matrix: + network: + - name: amoy + - name: bsc-testnet + - name: bsc + - name: ethereum + - name: polygon + - name: sepolia + fail-fast: true + max-parallel: 3 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: yarn + - name: Filter Networks + id: filter_networks + run: | + INPUT_NETWORKS="${{ github.event.inputs.networks }}" + IFS=',' read -ra NETWORK_LIST <<< "$INPUT_NETWORKS" + echo "Input networks: $INPUT_NETWORKS" + echo "Current matrix network: ${{ matrix.network.name }}" + MATCH=false + for network in "${NETWORK_LIST[@]}"; do + if [[ "${network}" == "${{ matrix.network.name }}" ]]; then + MATCH=true + break + fi + done + echo "Match found: $MATCH" + echo "continue=$MATCH" >> "$GITHUB_OUTPUT" + - name: Install dependencies + if: steps.filter_networks.outputs.continue == 'true' + run: yarn workspaces focus @tools/subgraph-human-protocol + - name: Build packages (scoped) + if: steps.filter_networks.outputs.continue == 'true' + run: yarn workspaces foreach -Rpt --from @tools/subgraph-human-protocol run build + - name: Generate and build Subgraph + if: steps.filter_networks.outputs.continue == 'true' + run: yarn generate && yarn build + working-directory: ./packages/subgraph/human-protocol + env: + NETWORK: ${{ matrix.network.name }} + - name: Authenticate & Deploy + if: steps.filter_networks.outputs.continue == 'true' + env: + API_KEY: ${{ secrets.HP_GRAPH_API_KEY }} + NETWORK: ${{ matrix.network.name }} + LABEL: ${{ github.event.inputs.label }} + working-directory: ./packages/subgraph/human-protocol + run: | + yarn dlx @graphprotocol/graph-cli@0.71.2 \ + auth --studio "$API_KEY" + yarn dlx @graphprotocol/graph-cli@0.71.2 \ + deploy --studio human-${NETWORK} -l ${LABEL} diff --git a/.github/workflows/ci-dependency-review.yaml b/.github/workflows/ci-dependency-review.yaml index e30c6d1045..d5eb760ed3 100644 --- a/.github/workflows/ci-dependency-review.yaml +++ b/.github/workflows/ci-dependency-review.yaml @@ -14,6 +14,6 @@ jobs: steps: - uses: actions/checkout@v6 - name: Dependency Review - uses: actions/dependency-review-action@v4.8.2 + uses: actions/dependency-review-action@v4.9.0 with: show-openssf-scorecard: false diff --git a/.github/workflows/ci-test-subgraph.yaml b/.github/workflows/ci-test-subgraph-hmt.yaml similarity index 65% rename from .github/workflows/ci-test-subgraph.yaml rename to .github/workflows/ci-test-subgraph-hmt.yaml index d569f765f2..f7cd44b3c3 100644 --- a/.github/workflows/ci-test-subgraph.yaml +++ b/.github/workflows/ci-test-subgraph-hmt.yaml @@ -1,15 +1,18 @@ -name: Subgraph check +name: HMT Subgraph check on: push: branches: "**" paths: - "packages/core/**" - - "packages/sdk/typescript/subgraph/**" + - "packages/subgraph/hmt/**" + +permissions: + contents: read jobs: subgraph-test: - name: Subgraph Test + name: HMT Subgraph Test # TODO: Use ubuntu-latest when graph binary is not failing on ubuntu 24.04 runs-on: ubuntu-22.04 steps: @@ -19,10 +22,10 @@ jobs: node-version-file: .nvmrc cache: yarn - name: Install dependencies - run: yarn workspaces focus @tools/subgraph + run: yarn workspaces focus @tools/subgraph-hmt - name: Build core package run: yarn workspace @human-protocol/core build - name: Generate manifest for Polygon for tests - run: NETWORK=polygon yarn workspace @tools/subgraph generate + run: NETWORK=polygon yarn workspace @tools/subgraph-hmt generate - name: Run subgraph test - run: yarn workspace @tools/subgraph test + run: yarn workspace @tools/subgraph-hmt test diff --git a/.github/workflows/ci-test-subgraph-human.yaml b/.github/workflows/ci-test-subgraph-human.yaml new file mode 100644 index 0000000000..863843e8cd --- /dev/null +++ b/.github/workflows/ci-test-subgraph-human.yaml @@ -0,0 +1,31 @@ +name: Human Protocol Subgraph check + +on: + push: + branches: "**" + paths: + - "packages/core/**" + - "packages/subgraph/human-protocol/**" + +permissions: + contents: read + +jobs: + subgraph-test: + name: Human Protocol Subgraph Test + # TODO: Use ubuntu-latest when graph binary is not failing on ubuntu 24.04 + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: yarn + - name: Install dependencies + run: yarn workspaces focus @tools/subgraph-human-protocol + - name: Build core package + run: yarn workspace @human-protocol/core build + - name: Generate manifest for Polygon for tests + run: NETWORK=polygon yarn workspace @tools/subgraph-human-protocol generate + - name: Run subgraph test + run: yarn workspace @tools/subgraph-human-protocol test diff --git a/docker-setup/Makefile b/docker-setup/Makefile index d9a3d81c62..724723d377 100644 --- a/docker-setup/Makefile +++ b/docker-setup/Makefile @@ -3,7 +3,7 @@ build-services services-up services-stop export COMPOSE_BAKE=false -DOCKER_PARALLEL ?= 4 +DOCKER_PARALLEL ?= 2 check-env-file: @if [ ! -f "./.env.compose.local" ]; then \ @@ -17,11 +17,19 @@ infra-stop: @docker compose --env-file .env.compose.local stop postgres redis minio minio-client build-services: check-env-file - @docker compose --env-file .env.compose.local --parallel $(DOCKER_PARALLEL) up --no-start + @# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. + @docker compose --env-file .env.compose.local config --services | \ + xargs -n $(DOCKER_PARALLEL) docker compose --env-file .env.compose.local build + @docker compose --env-file .env.compose.local up --no-start services-up: check-env-file @service_names="$(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))"; \ - docker compose --env-file .env.compose.local --parallel $(DOCKER_PARALLEL) up -d $$service_names + @# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. + if [ -n "$$service_names" ]; then \ + printf '%s\n' $$service_names | xargs -n $(DOCKER_PARALLEL) docker compose --env-file .env.compose.local up -d; \ + else \ + docker compose --env-file .env.compose.local config --services | xargs -n $(DOCKER_PARALLEL) docker compose --env-file .env.compose.local up -d; \ + fi services-stop: @service_names="$(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))"; \ diff --git a/packages/apps/dashboard/client/eslint.config.mjs b/packages/apps/dashboard/client/eslint.config.mjs index 6605a1e56a..d41d2254a2 100644 --- a/packages/apps/dashboard/client/eslint.config.mjs +++ b/packages/apps/dashboard/client/eslint.config.mjs @@ -46,6 +46,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'react/prop-types': 'off', 'react/display-name': 'off', 'react/react-in-jsx-scope': 'off', diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index 0e0346a378..a2f93a521b 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -20,9 +20,9 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@human-protocol/sdk": "workspace:*", - "@mui/icons-material": "^7.2.0", + "@mui/icons-material": "^7.3.8", "@mui/material": "^7.2.0", - "@mui/styled-engine-sc": "7.2.0", + "@mui/styled-engine-sc": "7.3.8", "@mui/system": "^7.2.0", "@mui/x-data-grid": "^8.7.0", "@mui/x-date-pickers": "^8.26.0", @@ -35,10 +35,10 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-number-format": "^5.4.3", - "react-router-dom": "^6.23.1", + "react-router-dom": "^7.13.0", "recharts": "^2.13.0-alpha.4", "simplebar-react": "^3.3.2", - "styled-components": "^6.1.11", + "styled-components": "^6.3.11", "swiper": "^11.1.3", "use-debounce": "^10.1.0", "vite-plugin-node-polyfills": "^0.25.0", @@ -46,7 +46,7 @@ "zustand": "^5.0.10" }, "devDependencies": { - "@eslint/js": "^9.27.0", + "@eslint/js": "^10.0.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^7.2.0", @@ -59,7 +59,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.11", "globals": "^16.2.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "sass": "^1.89.2", "terser": "^5.36.0", "typescript": "^5.6.3", diff --git a/packages/apps/dashboard/client/src/features/searchResults/model/transactionDetailsSchema.ts b/packages/apps/dashboard/client/src/features/searchResults/model/transactionDetailsSchema.ts index 810967e0f2..eb6b703ced 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/model/transactionDetailsSchema.ts +++ b/packages/apps/dashboard/client/src/features/searchResults/model/transactionDetailsSchema.ts @@ -8,6 +8,7 @@ const internalTransactionSchema = z.object({ receiver: z.string().nullable(), escrow: z.string().nullable(), token: z.string().nullable(), + tokenSymbol: z.string().nullable().optional(), }); const transactionDetailsSchema = z.object({ @@ -18,6 +19,7 @@ const transactionDetailsSchema = z.object({ receiver: z.string().nullable(), block: z.number(), value: z.string(), + tokenSymbol: z.string().nullable().optional(), internalTransactions: z.array(internalTransactionSchema), }); diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableBody.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableBody.tsx index f39de49cd3..5ebeb02c22 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableBody.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableBody.tsx @@ -114,6 +114,7 @@ const TransactionsTableBody: FC = ({ data, isLoading, error }) => { @@ -150,6 +151,7 @@ const TransactionsTableBody: FC = ({ data, isLoading, error }) => { diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableCellValue.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableCellValue.tsx index 4c031b002c..8622ee7572 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableCellValue.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletTransactions/TransactionsTableCellValue.tsx @@ -1,8 +1,7 @@ import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; import Typography from '@mui/material/Typography'; -import useHmtPrice from '@/shared/api/useHmtPrice'; -import formatHmtDecimals from '@/shared/lib/formatHmtDecimals'; +import formatTokenDecimals from '@/shared/lib/formatTokenDecimals'; import CustomTooltip from '@/shared/ui/CustomTooltip'; const InfoTooltip = ({ title }: { title: string }) => ( @@ -19,26 +18,24 @@ const InfoTooltip = ({ title }: { title: string }) => ( const TransactionsTableCellValue = ({ value, method, + tokenSymbol, }: { value: string; method: string; + tokenSymbol?: string | null; }) => { - const { isError, isPending } = useHmtPrice(); - - if (isError) { - return N/A; - } - - if (isPending) { - return ...; - } - return ( - {formatHmtDecimals(value)} - - HMT - + {Number(value) === 0 && !tokenSymbol ? ( + '-' + ) : ( + <> + {formatTokenDecimals(value)} + + {tokenSymbol} + + + )} {method === 'approve' && ( )} diff --git a/packages/apps/dashboard/client/src/shared/lib/formatHmtDecimals.ts b/packages/apps/dashboard/client/src/shared/lib/formatTokenDecimals.ts similarity index 73% rename from packages/apps/dashboard/client/src/shared/lib/formatHmtDecimals.ts rename to packages/apps/dashboard/client/src/shared/lib/formatTokenDecimals.ts index c0610ad83b..b18d169155 100644 --- a/packages/apps/dashboard/client/src/shared/lib/formatHmtDecimals.ts +++ b/packages/apps/dashboard/client/src/shared/lib/formatTokenDecimals.ts @@ -1,7 +1,9 @@ -import { formatEther } from 'ethers'; +const formatTokenDecimals = (value: string) => { + const formattedValue = Number(value); -const formatHmtDecimals = (value: string) => { - const formattedValue = Number(formatEther(value)); + if (Number.isNaN(formattedValue)) { + return value; + } if (Number.isInteger(formattedValue)) { return formattedValue.toString(); @@ -23,4 +25,4 @@ const formatHmtDecimals = (value: string) => { : formattedValue.toString(); }; -export default formatHmtDecimals; +export default formatTokenDecimals; diff --git a/packages/apps/dashboard/server/eslint.config.mjs b/packages/apps/dashboard/server/eslint.config.mjs index 0d8f27e5ea..12c294c9ab 100644 --- a/packages/apps/dashboard/server/eslint.config.mjs +++ b/packages/apps/dashboard/server/eslint.config.mjs @@ -30,6 +30,8 @@ export default tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/dashboard/server/package.json b/packages/apps/dashboard/server/package.json index 1aa0bbc590..1a2a35ac27 100644 --- a/packages/apps/dashboard/server/package.json +++ b/packages/apps/dashboard/server/package.json @@ -32,7 +32,7 @@ "@nestjs/core": "^11.1.12", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^11.1.12", - "@nestjs/schedule": "^6.1.0", + "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.5", "axios": "^1.3.1", "cache-manager": "7.2.8", @@ -51,7 +51,7 @@ "@golevelup/ts-jest": "^1.2.1", "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", + "@nestjs/testing": "^11.1.14", "@types/express": "^5.0.6", "@types/jest": "30.0.0", "@types/node": "22.10.5", @@ -62,7 +62,7 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "source-map-support": "^0.5.20", "ts-jest": "29.2.5", "ts-node": "^10.0.0", diff --git a/packages/apps/dashboard/server/src/common/constants/chains.ts b/packages/apps/dashboard/server/src/common/constants/chains.ts index 74043bf6bc..5def586326 100644 --- a/packages/apps/dashboard/server/src/common/constants/chains.ts +++ b/packages/apps/dashboard/server/src/common/constants/chains.ts @@ -26,3 +26,5 @@ export const ChainIds = Object.values( ).filter((value): value is ChainId => typeof value === 'number'); export type ChainId = ProductionChainId | DevelopmentChainId; + +export const TOKEN_CACHE_PREFIX = 'token'; diff --git a/packages/apps/dashboard/server/src/common/filters/exception.filter.ts b/packages/apps/dashboard/server/src/common/filters/exception.filter.ts index fe958fdd00..5e5e80ba4d 100644 --- a/packages/apps/dashboard/server/src/common/filters/exception.filter.ts +++ b/packages/apps/dashboard/server/src/common/filters/exception.filter.ts @@ -6,6 +6,7 @@ import { HttpException, } from '@nestjs/common'; import { Request, Response } from 'express'; +import { SubgraphRequestError } from '@human-protocol/sdk'; import logger from '../../logger'; @@ -23,7 +24,15 @@ export class ExceptionFilter implements IExceptionFilter { message: 'Internal server error', }; - if (exception instanceof HttpException) { + if (exception instanceof SubgraphRequestError) { + status = HttpStatus.BAD_GATEWAY; + responseBody.message = exception.message; + + this.logger.error('Subgraph request failed', { + error: exception, + path: request.url, + }); + } else if (exception instanceof HttpException) { status = exception.getStatus(); const exceptionResponse = exception.getResponse(); if (typeof exceptionResponse === 'string') { 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 63905022ea..0b40757ced 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.service.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.service.ts @@ -3,6 +3,7 @@ import { EscrowUtils, IEscrowsFilter, IOperatorsFilter, + ITransaction, KVStoreUtils, NETWORKS, OperatorUtils, @@ -19,18 +20,19 @@ import { plainToInstance } from 'class-transformer'; import { ethers } from 'ethers'; import { firstValueFrom } from 'rxjs'; -import { GetOperatorsPaginationOptions } from '../../common/types'; import { EnvironmentConfigService } from '../../common/config/env-config.service'; import { NetworkConfigService } from '../../common/config/network-config.service'; import { MAX_LEADERS_COUNT, MIN_STAKED_AMOUNT, REPUTATION_PLACEHOLDER, + TOKEN_CACHE_PREFIX, type ChainId, } from '../../common/constants'; -import * as httpUtils from '../../common/utils/http'; import { OperatorsOrderBy } from '../../common/enums/operator'; import { ReputationLevel } from '../../common/enums/reputation'; +import { GetOperatorsPaginationOptions } from '../../common/types'; +import * as httpUtils from '../../common/utils/http'; import logger from '../../logger'; import { KVStoreDataDto } from './dto/details-response.dto'; import { EscrowDto, EscrowPaginationDto } from './dto/escrow.dto'; @@ -41,6 +43,10 @@ import { WalletDto } from './dto/wallet.dto'; @Injectable() export class DetailsService { private readonly logger = logger.child({ context: DetailsService.name }); + private readonly tokenData = new Map< + string, + Promise<{ decimals: number; symbol: string | null }> + >(); constructor( @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, @@ -53,11 +59,7 @@ export class DetailsService { chainId: ChainId, address: string, ): Promise { - 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 provider = this.getProvider(chainId); const escrowData = await EscrowUtils.getEscrow(chainId, address); if (escrowData) { @@ -66,9 +68,9 @@ export class DetailsService { }); const { decimals, symbol } = await this.getTokenData( + provider, chainId, escrowData.token, - provider, ); escrowDto.balance = ethers.formatUnits(escrowData.balance, decimals); @@ -139,11 +141,7 @@ export class DetailsService { chainId: ChainId, hmtAddress: string, ): Promise { - 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 provider = this.getProvider(chainId); const hmtContract = HMToken__factory.connect( NETWORKS[chainId].hmtAddress, provider, @@ -157,6 +155,7 @@ export class DetailsService { first: number, skip: number, ): Promise { + const provider = this.getProvider(chainId); const transactions = await TransactionUtils.getTransactions({ chainId, fromAddress: address, @@ -164,15 +163,23 @@ export class DetailsService { first, skip, }); - const result = transactions.map((transaction) => { - const transactionPaginationObject: TransactionPaginationDto = - plainToInstance( - TransactionPaginationDto, - { ...transaction, currentAddress: address }, - { excludeExtraneousValues: true }, + + const result = await Promise.all( + transactions.map(async (transaction) => { + const formattedTransaction = await this.formatTransactionValues( + chainId, + provider, + transaction, ); - return transactionPaginationObject; - }); + const transactionPaginationObject: TransactionPaginationDto = + plainToInstance( + TransactionPaginationDto, + { ...formattedTransaction, currentAddress: address }, + { excludeExtraneousValues: true }, + ); + return transactionPaginationObject; + }), + ); return result; } @@ -439,24 +446,108 @@ export class DetailsService { } private async getTokenData( + provider: ethers.JsonRpcProvider, chainId: ChainId, tokenAddress: string, - provider: ethers.JsonRpcProvider, - ): Promise<{ decimals: number; symbol: string }> { - const tokenCacheKey = `token:${chainId}:${tokenAddress.toLowerCase()}`; - let data = await this.cacheManager.get<{ - decimals: number; - symbol: string; - }>(tokenCacheKey); - if (!data) { - const erc20Contract = HMToken__factory.connect(tokenAddress, provider); - const [decimals, symbol] = await Promise.all([ - erc20Contract.decimals(), - erc20Contract.symbol(), - ]); - data = { decimals: Number(decimals), symbol }; - await this.cacheManager.set(tokenCacheKey, data); + ): Promise<{ decimals: number; symbol: string | null }> { + if (!ethers.isAddress(tokenAddress)) { + throw new Error(`Invalid token address: ${tokenAddress}`); } - return data; + + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const tokenCacheKey = `${TOKEN_CACHE_PREFIX}:${chainId}:${normalizedTokenAddress}`; + + if (!this.tokenData.has(tokenCacheKey)) { + const tokenDataPromise = (async () => { + try { + const cachedData = await this.cacheManager.get<{ + decimals: number; + symbol: string; + }>(tokenCacheKey); + if (cachedData) { + return cachedData; + } + + const erc20Contract = HMToken__factory.connect( + tokenAddress, + provider, + ); + const [decimals, symbol] = await Promise.all([ + erc20Contract.decimals(), + erc20Contract.symbol(), + ]); + const resolvedTokenData = { decimals: Number(decimals), symbol }; + await this.cacheManager.set(tokenCacheKey, resolvedTokenData); + + return resolvedTokenData; + } catch (error) { + this.tokenData.delete(tokenCacheKey); + throw error; + } + })(); + + this.tokenData.set(tokenCacheKey, tokenDataPromise); + } + + return this.tokenData.get(tokenCacheKey)!; + } + + private getProvider(chainId: ChainId): ethers.JsonRpcProvider { + const network = this.networkConfig.networks.find( + (network) => network.chainId === chainId, + ); + if (!network?.rpcUrl) { + throw new BadRequestException('Invalid chainId provided'); + } + + return new ethers.JsonRpcProvider(network.rpcUrl); + } + + private async formatTransactionValues( + chainId: ChainId, + provider: ethers.JsonRpcProvider, + transaction: ITransaction, + ): Promise> { + const getFormattedTokenData = async (tokenAddress: string | null) => { + if (!tokenAddress) { + return null; + } + + try { + return await this.getTokenData(provider, chainId, tokenAddress); + } catch (error) { + this.logger.warn('Failed to resolve token metadata.', { + chainId, + tokenAddress, + txHash: transaction.txHash, + error: error instanceof Error ? error.message : String(error), + }); + throw new Error('Failed to resolve token metadata'); + } + }; + + const internalTransactions = await Promise.all( + transaction.internalTransactions.map(async (internalTransaction) => { + const tokenData = await getFormattedTokenData( + internalTransaction.token, + ); + return { + ...internalTransaction, + value: ethers.formatUnits( + internalTransaction.value, + tokenData?.decimals ?? 18, + ), + ...(tokenData ? { tokenSymbol: tokenData.symbol } : {}), + }; + }), + ); + const tokenData = await getFormattedTokenData(transaction.token); + + return { + ...transaction, + value: ethers.formatUnits(transaction.value, tokenData?.decimals ?? 18), + ...(tokenData ? { tokenSymbol: tokenData.symbol } : {}), + internalTransactions, + }; } } diff --git a/packages/apps/dashboard/server/src/modules/details/details.spec.ts b/packages/apps/dashboard/server/src/modules/details/details.spec.ts index 6cbc1dae10..2d31b20cc0 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.spec.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.spec.ts @@ -1,8 +1,10 @@ +import { HMToken__factory } from '@human-protocol/core/typechain-types'; import { IOperator, KVStoreUtils, OperatorUtils, OrderDirection, + TransactionUtils, } from '@human-protocol/sdk'; import { HttpService } from '@nestjs/axios'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; @@ -32,8 +34,14 @@ jest.mock('../../common/constants/operator', () => ({ describe('DetailsService', () => { let service: DetailsService; let httpService: HttpService; + let cacheManager: { get: jest.Mock; set: jest.Mock }; beforeEach(async () => { + cacheManager = { + get: jest.fn(), + set: jest.fn(), + }; + const module: TestingModule = await Test.createTestingModule({ providers: [ DetailsService, @@ -55,14 +63,17 @@ describe('DetailsService', () => { getAvailableNetworks: jest .fn() .mockResolvedValue([DevelopmentChainId.SEPOLIA]), + networks: [ + { + chainId: DevelopmentChainId.SEPOLIA, + rpcUrl: 'http://localhost:8545', + }, + ], }, }, { provide: CACHE_MANAGER, - useValue: { - get: jest.fn(), - set: jest.fn(), - }, + useValue: cacheManager, }, ], }).compile(); @@ -193,4 +204,205 @@ describe('DetailsService', () => { expect.objectContaining({ key: 'key2', value: 'value2' }), ]); }); + + it('should format transactions using token decimals and symbol', async () => { + const walletAddress = '0xA'; + const senderAddress = '0xB'; + const receiverAddress = '0xC'; + const tokenAddress = '0xD'; + const txHash = '0x1'; + + const mockTransactions = [ + { + block: 123n, + txHash, + from: senderAddress, + to: walletAddress, + timestamp: Date.now(), + value: 1234567n, + method: 'bulkTransfer', + receiver: null, + escrow: null, + token: tokenAddress, + internalTransactions: [ + { + from: senderAddress, + to: receiverAddress, + value: 345678n, + method: 'transfer', + receiver: null, + escrow: null, + token: tokenAddress, + }, + ], + }, + ]; + + jest + .spyOn(TransactionUtils, 'getTransactions') + .mockResolvedValue(mockTransactions); + + jest.spyOn(service as any, 'getTokenData').mockResolvedValue({ + decimals: 6, + symbol: 'USDC', + }); + + const result = await service.getTransactions( + DevelopmentChainId.SEPOLIA, + walletAddress, + 10, + 0, + ); + + expect(result).toEqual([ + expect.objectContaining({ + value: '1.234567', + tokenSymbol: 'USDC', + internalTransactions: [ + expect.objectContaining({ + value: '0.345678', + tokenSymbol: 'USDC', + }), + ], + }), + ]); + }); + + it('should omit tokenSymbol when transaction has no token', async () => { + const walletAddress = '0xA'; + const senderAddress = '0xB'; + const receiverAddress = '0xC'; + + const mockTransactions = [ + { + block: 123n, + txHash: '0x1', + from: senderAddress, + to: walletAddress, + timestamp: Date.now(), + value: 1234567n, + method: 'bulkTransfer', + receiver: null, + escrow: null, + token: null, + internalTransactions: [ + { + from: senderAddress, + to: receiverAddress, + value: 345678n, + method: 'transfer', + receiver: null, + escrow: null, + token: null, + }, + ], + }, + ]; + + jest + .spyOn(TransactionUtils, 'getTransactions') + .mockResolvedValue(mockTransactions); + + const result = await service.getTransactions( + DevelopmentChainId.SEPOLIA, + walletAddress, + 10, + 0, + ); + + expect(result[0].value).toBe('0.000000000001234567'); + expect(result[0].tokenSymbol).toBeUndefined(); + expect(JSON.parse(JSON.stringify(result[0]))).not.toHaveProperty( + 'tokenSymbol', + ); + expect(result[0].internalTransactions[0].value).toBe( + '0.000000000000345678', + ); + expect(result[0].internalTransactions[0].tokenSymbol).toBeUndefined(); + expect( + JSON.parse(JSON.stringify(result[0].internalTransactions[0])), + ).not.toHaveProperty('tokenSymbol'); + }); + + it('should throw when token metadata cannot be resolved for a tokenized transaction', async () => { + const walletAddress = '0xA'; + const senderAddress = '0xB'; + const tokenAddress = '0x000000000000000000000000000000000000000d'; + + const mockTransactions = [ + { + block: 123n, + txHash: '0x1', + from: senderAddress, + to: walletAddress, + timestamp: Date.now(), + value: 1234567n, + method: 'bulkTransfer', + receiver: null, + escrow: null, + token: tokenAddress, + internalTransactions: [], + }, + ]; + + jest + .spyOn(TransactionUtils, 'getTransactions') + .mockResolvedValue(mockTransactions); + + jest.spyOn(service as any, 'getTokenData').mockRejectedValue(new Error()); + + await expect( + service.getTransactions(DevelopmentChainId.SEPOLIA, walletAddress, 10, 0), + ).rejects.toThrow('Failed to resolve token metadata'); + }); + + it('should deduplicate concurrent token metadata fetches and reuse resolved promises', async () => { + const tokenAddress = '0x000000000000000000000000000000000000000d'; + const provider = (service as any).getProvider(DevelopmentChainId.SEPOLIA); + const tokenCacheKey = `token:${DevelopmentChainId.SEPOLIA}:${tokenAddress.toLowerCase()}`; + + cacheManager.get.mockResolvedValue(null); + + const decimals = jest.fn().mockImplementation( + async () => + await new Promise((resolve) => { + setTimeout(() => resolve(6n), 5); + }), + ); + const symbol = jest.fn().mockResolvedValue('USDC'); + const connectSpy = jest + .spyOn(HMToken__factory, 'connect') + .mockReturnValue({ decimals, symbol } as any); + + const first = (service as any).getTokenData( + provider, + DevelopmentChainId.SEPOLIA, + tokenAddress, + ); + const second = (service as any).getTokenData( + provider, + DevelopmentChainId.SEPOLIA, + tokenAddress, + ); + + const [firstResult, secondResult] = await Promise.all([first, second]); + const thirdResult = await (service as any).getTokenData( + provider, + DevelopmentChainId.SEPOLIA, + tokenAddress, + ); + + expect(firstResult).toEqual({ decimals: 6, symbol: 'USDC' }); + expect(secondResult).toEqual({ decimals: 6, symbol: 'USDC' }); + expect(thirdResult).toEqual({ decimals: 6, symbol: 'USDC' }); + expect(connectSpy).toHaveBeenCalledTimes(1); + expect(decimals).toHaveBeenCalledTimes(1); + expect(symbol).toHaveBeenCalledTimes(1); + expect(cacheManager.get).toHaveBeenCalledTimes(1); + expect(cacheManager.set).toHaveBeenCalledWith(tokenCacheKey, { + decimals: 6, + symbol: 'USDC', + }); + expect((service as any).tokenData.size).toBe(1); + }); }); diff --git a/packages/apps/dashboard/server/src/modules/details/dto/transaction.dto.ts b/packages/apps/dashboard/server/src/modules/details/dto/transaction.dto.ts index d9e8688177..a8635cf6f1 100644 --- a/packages/apps/dashboard/server/src/modules/details/dto/transaction.dto.ts +++ b/packages/apps/dashboard/server/src/modules/details/dto/transaction.dto.ts @@ -24,6 +24,9 @@ export class InternalTransaction { @ApiProperty() @Expose() token: string | null; + @ApiProperty({ required: false, nullable: true, example: 'USDC' }) + @Expose() + tokenSymbol: string | null; } export class TransactionPaginationDto { @@ -65,6 +68,9 @@ export class TransactionPaginationDto { }) @Expose() value: string; + @ApiProperty({ required: false, nullable: true, example: 'HMT' }) + @Expose() + tokenSymbol: string | null; @ApiProperty({ type: [Object], diff --git a/packages/apps/faucet/client/eslint.config.mjs b/packages/apps/faucet/client/eslint.config.mjs index e6ac5e722b..a20306124a 100644 --- a/packages/apps/faucet/client/eslint.config.mjs +++ b/packages/apps/faucet/client/eslint.config.mjs @@ -25,6 +25,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index 0e0aad48dd..b821969172 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -21,12 +21,12 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@human-protocol/sdk": "workspace:*", - "@mui/icons-material": "^7.0.1", + "@mui/icons-material": "^7.3.8", "@mui/material": "^5.16.7", "react": "^18.3.1", "react-dom": "^18.3.1", "react-loading-skeleton": "^3.3.1", - "react-router-dom": "^6.4.3", + "react-router-dom": "^7.13.0", "serve": "^14.2.4", "viem": "2.x" }, @@ -41,7 +41,7 @@ "eslint-plugin-import": "^2.29.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^5.1.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "typescript": "^5.8.3", "vite": "^6.2.4", "vite-plugin-node-polyfills": "^0.25.0" diff --git a/packages/apps/faucet/server/eslint.config.mjs b/packages/apps/faucet/server/eslint.config.mjs index 1c908b5aea..c7c9b424f2 100644 --- a/packages/apps/faucet/server/eslint.config.mjs +++ b/packages/apps/faucet/server/eslint.config.mjs @@ -24,6 +24,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', 'prettier/prettier': 'error', '@/quotes': [ diff --git a/packages/apps/fortune/exchange-oracle/client/Dockerfile b/packages/apps/fortune/exchange-oracle/client/Dockerfile index 1e5800c6bb..93e9380e77 100644 --- a/packages/apps/fortune/exchange-oracle/client/Dockerfile +++ b/packages/apps/fortune/exchange-oracle/client/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/fortune-exchange-oracle-client # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/fortune-exchange-oracle-client run build +RUN yarn workspaces foreach -Rpt --from @apps/fortune-exchange-oracle-client --exclude @apps/fortune-exchange-oracle-client run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +32,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/fortune/exchange-oracle/client/eslint.config.mjs b/packages/apps/fortune/exchange-oracle/client/eslint.config.mjs index 3649fc8f9a..ee368a0556 100644 --- a/packages/apps/fortune/exchange-oracle/client/eslint.config.mjs +++ b/packages/apps/fortune/exchange-oracle/client/eslint.config.mjs @@ -35,6 +35,8 @@ const config = tseslint.config( 'react-refresh': reactRefreshPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', 'react-refresh/only-export-components': [ diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index 7ffddc7d88..9e2a7f1309 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -30,7 +30,7 @@ "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@human-protocol/sdk": "workspace:^", - "@mui/icons-material": "^7.0.1", + "@mui/icons-material": "^7.3.8", "@mui/material": "^5.16.7", "@tanstack/query-sync-storage-persister": "^5.68.0", "@tanstack/react-query": "^5.67.2", @@ -39,7 +39,7 @@ "ethers": "^6.15.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.24.1", + "react-router-dom": "^7.13.0", "serve": "^14.2.4", "viem": "2.x", "wagmi": "^2.14.6" @@ -54,7 +54,7 @@ "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.11", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "typescript": "^5.6.3", "vite": "^6.2.4", "vite-plugin-node-polyfills": "^0.25.0" diff --git a/packages/apps/fortune/exchange-oracle/server/Dockerfile b/packages/apps/fortune/exchange-oracle/server/Dockerfile index 969544f4b1..14690d1feb 100644 --- a/packages/apps/fortune/exchange-oracle/server/Dockerfile +++ b/packages/apps/fortune/exchange-oracle/server/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/fortune-exchange-oracle-server # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/fortune-exchange-oracle-server run build +RUN yarn workspaces foreach -Rpt --from @apps/fortune-exchange-oracle-server --exclude @apps/fortune-exchange-oracle-server run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} diff --git a/packages/apps/fortune/exchange-oracle/server/eslint.config.mjs b/packages/apps/fortune/exchange-oracle/server/eslint.config.mjs index 471eb358d4..3c398c43c0 100644 --- a/packages/apps/fortune/exchange-oracle/server/eslint.config.mjs +++ b/packages/apps/fortune/exchange-oracle/server/eslint.config.mjs @@ -29,6 +29,8 @@ const config = tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index 6a3d223834..17fe5b0829 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -38,7 +38,7 @@ "@nestjs/core": "^11.1.12", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.12", - "@nestjs/schedule": "^6.1.0", + "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.5", "@nestjs/terminus": "^11.0.0", "@nestjs/typeorm": "^11.0.0", @@ -64,7 +64,7 @@ "@golevelup/ts-jest": "^0.6.1", "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", + "@nestjs/testing": "^11.1.14", "@types/body-parser": "^1", "@types/express": "^5.0.6", "@types/jest": "30.0.0", @@ -79,7 +79,7 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "source-map-support": "^0.5.20", "ts-jest": "29.2.5", "ts-node": "^10.9.2", diff --git a/packages/apps/fortune/exchange-oracle/server/src/common/exceptions/exception.filter.ts b/packages/apps/fortune/exchange-oracle/server/src/common/exceptions/exception.filter.ts index 96e7b88236..5bfc921d06 100644 --- a/packages/apps/fortune/exchange-oracle/server/src/common/exceptions/exception.filter.ts +++ b/packages/apps/fortune/exchange-oracle/server/src/common/exceptions/exception.filter.ts @@ -5,6 +5,7 @@ import { HttpStatus, } from '@nestjs/common'; import { Request, Response } from 'express'; +import { SubgraphRequestError } from '@human-protocol/sdk'; import logger from '../../logger'; import { @@ -36,6 +37,8 @@ export class ExceptionFilter implements IExceptionFilter { return HttpStatus.UNPROCESSABLE_ENTITY; } else if (exception instanceof DatabaseError) { return HttpStatus.UNPROCESSABLE_ENTITY; + } else if (exception instanceof SubgraphRequestError) { + return HttpStatus.BAD_GATEWAY; } const exceptionStatusCode = exception.statusCode || exception.status; @@ -51,7 +54,12 @@ export class ExceptionFilter implements IExceptionFilter { const status = this.getStatus(exception); const message = exception.message || 'Internal server error'; - if (status === HttpStatus.INTERNAL_SERVER_ERROR) { + if (exception instanceof SubgraphRequestError) { + this.logger.error('Subgraph request failed', { + error: exception, + path: request.url, + }); + } else if (status === HttpStatus.INTERNAL_SERVER_ERROR) { this.logger.error('Unhandled exception', { error: exception, path: request.url, diff --git a/packages/apps/fortune/recording-oracle/Dockerfile b/packages/apps/fortune/recording-oracle/Dockerfile index b6569d2c63..c25c15dda0 100644 --- a/packages/apps/fortune/recording-oracle/Dockerfile +++ b/packages/apps/fortune/recording-oracle/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/fortune-recording-oracle # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/fortune-recording-oracle run build +RUN yarn workspaces foreach -Rpt --from @apps/fortune-recording-oracle --exclude @apps/fortune-recording-oracle run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} diff --git a/packages/apps/fortune/recording-oracle/eslint.config.mjs b/packages/apps/fortune/recording-oracle/eslint.config.mjs index c131251039..e2042f3b2f 100644 --- a/packages/apps/fortune/recording-oracle/eslint.config.mjs +++ b/packages/apps/fortune/recording-oracle/eslint.config.mjs @@ -29,6 +29,8 @@ export default tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', 'prettier/prettier': 'error', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index 7ae73fc159..4cef509a1b 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", + "@nestjs/testing": "^11.1.14", "@types/express": "^5.0.6", "@types/jest": "^29.5.14", "@types/node": "^22.15.16", @@ -53,7 +53,7 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "ts-jest": "29.2.5", "ts-node": "^10.9.2", "typescript": "^5.8.3" diff --git a/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts index 7a1a4d6144..865b9f747d 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts @@ -6,6 +6,7 @@ export const envValidator = Joi.object({ PORT: Joi.string(), // Web3 WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number(), RPC_URL_POLYGON: Joi.string(), RPC_URL_BSC: Joi.string(), RPC_URL_POLYGON_AMOY: Joi.string(), diff --git a/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts index 619e9a0444..e740cf11ca 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts @@ -12,4 +12,12 @@ export class Web3ConfigService { get privateKey(): string { return this.configService.getOrThrow('WEB3_PRIVATE_KEY'); } + + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } } diff --git a/packages/apps/fortune/recording-oracle/src/common/exceptions/exception.filter.ts b/packages/apps/fortune/recording-oracle/src/common/exceptions/exception.filter.ts index 558cff998a..d9d7940c69 100644 --- a/packages/apps/fortune/recording-oracle/src/common/exceptions/exception.filter.ts +++ b/packages/apps/fortune/recording-oracle/src/common/exceptions/exception.filter.ts @@ -5,6 +5,7 @@ import { HttpStatus, } from '@nestjs/common'; import { Request, Response } from 'express'; +import { SubgraphRequestError } from '@human-protocol/sdk'; import logger from '../../logger'; import { @@ -33,6 +34,8 @@ export class ExceptionFilter implements IExceptionFilter { return HttpStatus.CONFLICT; } else if (exception instanceof ServerError) { return HttpStatus.UNPROCESSABLE_ENTITY; + } else if (exception instanceof SubgraphRequestError) { + return HttpStatus.BAD_GATEWAY; } const exceptionStatusCode = exception.statusCode || exception.status; @@ -48,7 +51,12 @@ export class ExceptionFilter implements IExceptionFilter { const status = this.getStatus(exception); const message = exception.message || 'Internal server error'; - if (status === HttpStatus.INTERNAL_SERVER_ERROR) { + if (exception instanceof SubgraphRequestError) { + this.logger.error('Subgraph request failed', { + error: exception, + path: request.url, + }); + } else if (status === HttpStatus.INTERNAL_SERVER_ERROR) { this.logger.error('Unhandled exception', { error: exception, path: request.url, diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index 09210cec12..cccaa60244 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -202,24 +202,18 @@ export class JobService { jobSolutionUploaded.url, jobSolutionUploaded.hash, !lastProcessedSolution?.error ? amountToReserve : 0n, + { timeoutMs: this.web3ConfigService.txTimeoutMs }, ); if ( recordingOracleSolutions.filter((solution) => !solution.error).length >= submissionsRequired ) { - let reputationOracleWebhook: string | null = null; - try { - const reputationOracleAddress = - await escrowClient.getReputationOracleAddress(webhook.escrowAddress); - reputationOracleWebhook = (await KVStoreUtils.get( - webhook.chainId, - reputationOracleAddress, - KVStoreKeys.webhookUrl, - )) as string; - } catch { - //Ignore the error - } + const reputationOracleWebhook = await KVStoreUtils.get( + webhook.chainId, + await escrowClient.getReputationOracleAddress(webhook.escrowAddress), + KVStoreKeys.webhookUrl, + ); if (reputationOracleWebhook) { await sendWebhook( @@ -238,16 +232,11 @@ export class JobService { } if (errorSolutions.length) { - let exchangeOracleURL: string | null = null; - try { - exchangeOracleURL = (await KVStoreUtils.get( - webhook.chainId, - await escrowClient.getExchangeOracleAddress(webhook.escrowAddress), - KVStoreKeys.webhookUrl, - )) as string; - } catch { - //Ignore the error - } + const exchangeOracleURL = await KVStoreUtils.get( + webhook.chainId, + await escrowClient.getExchangeOracleAddress(webhook.escrowAddress), + KVStoreKeys.webhookUrl, + ); if (exchangeOracleURL) { const eventData: AssignmentRejection[] = errorSolutions.map( @@ -307,20 +296,14 @@ export class JobService { intermediateResultsURL, intermediateResultsHash, 0n, + { timeoutMs: this.web3ConfigService.txTimeoutMs }, ); - let reputationOracleWebhook: string | null = null; - try { - const reputationOracleAddress = - await escrowClient.getReputationOracleAddress(webhook.escrowAddress); - reputationOracleWebhook = (await KVStoreUtils.get( - webhook.chainId, - reputationOracleAddress, - KVStoreKeys.webhookUrl, - )) as string; - } catch { - //Ignore the error - } + const reputationOracleWebhook = await KVStoreUtils.get( + webhook.chainId, + await escrowClient.getReputationOracleAddress(webhook.escrowAddress), + KVStoreKeys.webhookUrl, + ); if (reputationOracleWebhook) { await sendWebhook( diff --git a/packages/apps/human-app/frontend/Dockerfile b/packages/apps/human-app/frontend/Dockerfile index a2371a5cec..d60ea4451d 100644 --- a/packages/apps/human-app/frontend/Dockerfile +++ b/packages/apps/human-app/frontend/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/human-app-frontend # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/human-app-frontend run build +RUN yarn workspaces foreach -Rpt --from @apps/human-app-frontend --exclude @apps/human-app-frontend run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +32,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/human-app/frontend/eslint.config.mjs b/packages/apps/human-app/frontend/eslint.config.mjs index fb7986dd00..21c512d7f1 100644 --- a/packages/apps/human-app/frontend/eslint.config.mjs +++ b/packages/apps/human-app/frontend/eslint.config.mjs @@ -34,6 +34,8 @@ export default tseslint.config( import: eslintPluginImport, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index c43bff58fb..1973f80321 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -25,7 +25,7 @@ "@hcaptcha/react-hcaptcha": "^1.14.0", "@hookform/resolvers": "^5.1.0", "@human-protocol/sdk": "workspace:*", - "@mui/icons-material": "^7.0.1", + "@mui/icons-material": "^7.3.8", "@mui/material": "^5.16.7", "@mui/system": "^5.15.14", "@mui/x-date-pickers": "^8.26.0", @@ -33,7 +33,7 @@ "@reown/appkit-adapter-wagmi": "^1.7.11", "@synaps-io/verify-sdk": "^4.0.45", "@tanstack/react-query": "^5.75.5", - "@wagmi/core": "^2.17.1", + "@wagmi/core": "^3.4.0", "date-fns": "^4.1.0", "ethers": "^6.15.0", "i18next": "^25.8.0", @@ -50,7 +50,7 @@ "react-i18next": "^15.1.0", "react-imask": "^7.4.0", "react-number-format": "^5.4.3", - "react-router-dom": "^6.22.0", + "react-router-dom": "^7.13.0", "serve": "^14.2.4", "viem": "^2.31.4", "vite-plugin-svgr": "^4.2.0", @@ -77,9 +77,9 @@ "eslint-plugin-react-refresh": "^0.4.11", "husky": "^9.1.6", "jsdom": "^25.0.1", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "typescript": "^5.6.3", "vite": "^6.2.4", - "vitest": "^3.1.1" + "vitest": "^4.0.18" } } diff --git a/packages/apps/human-app/server/Dockerfile b/packages/apps/human-app/server/Dockerfile index 36d786f20f..dac8c581e6 100644 --- a/packages/apps/human-app/server/Dockerfile +++ b/packages/apps/human-app/server/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/human-app-server # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/human-app-server run build +RUN yarn workspaces foreach -Rpt --from @apps/human-app-server --exclude @apps/human-app-server run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +32,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/human-app/server/eslint.config.mjs b/packages/apps/human-app/server/eslint.config.mjs index 2d61b62292..600fdc911e 100644 --- a/packages/apps/human-app/server/eslint.config.mjs +++ b/packages/apps/human-app/server/eslint.config.mjs @@ -30,6 +30,8 @@ const config = tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', 'prettier/prettier': 'error', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/human-app/server/package.json b/packages/apps/human-app/server/package.json index 34289c7932..1bb66447bd 100644 --- a/packages/apps/human-app/server/package.json +++ b/packages/apps/human-app/server/package.json @@ -35,7 +35,7 @@ "@nestjs/core": "^11.1.12", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.12", - "@nestjs/schedule": "^6.1.0", + "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.5", "@nestjs/terminus": "^11.0.0", "@types/passport-jwt": "^4.0.1", @@ -57,7 +57,7 @@ "devDependencies": { "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", + "@nestjs/testing": "^11.1.14", "@types/express": "^5.0.6", "@types/jest": "30.0.0", "@types/jsonwebtoken": "^9.0.7", @@ -71,8 +71,8 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0", - "nock": "^13.5.1", - "prettier": "^3.7.4", + "nock": "^14.0.11", + "prettier": "^3.8.1", "source-map-support": "^0.5.20", "ts-jest": "29.2.5", "ts-node": "^10.9.2", diff --git a/packages/apps/human-app/server/src/common/filter/exceptions.filter.ts b/packages/apps/human-app/server/src/common/filter/exceptions.filter.ts index 61eb3a6c4d..cf8bea9b16 100644 --- a/packages/apps/human-app/server/src/common/filter/exceptions.filter.ts +++ b/packages/apps/human-app/server/src/common/filter/exceptions.filter.ts @@ -5,6 +5,7 @@ import { HttpException, HttpStatus, } from '@nestjs/common'; +import { SubgraphRequestError } from '@human-protocol/sdk'; import logger from '../../logger'; import { AxiosError } from 'axios'; import * as errorUtils from '../utils/error'; @@ -21,7 +22,15 @@ export class ExceptionFilter implements IExceptionFilter { let status = HttpStatus.INTERNAL_SERVER_ERROR; let message: any = 'Internal Server Error'; - if (exception instanceof HttpException) { + if (exception instanceof SubgraphRequestError) { + status = HttpStatus.BAD_GATEWAY; + message = exception.message; + + this.logger.error('Subgraph request failed', { + error: errorUtils.formatError(exception), + path: request.url, + }); + } else if (exception instanceof HttpException) { status = exception.getStatus(); message = exception.getResponse(); } else if (exception instanceof AxiosError) { diff --git a/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts b/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts index c08b05d221..492dc6ebdd 100644 --- a/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts +++ b/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts @@ -1,9 +1,4 @@ -import { - ChainId, - InvalidKeyError, - KVStoreKeys, - KVStoreUtils, -} from '@human-protocol/sdk'; +import { ChainId, KVStoreKeys, KVStoreUtils } from '@human-protocol/sdk'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { HttpException, Inject, Injectable } from '@nestjs/common'; import { Cache } from 'cache-manager'; @@ -36,9 +31,7 @@ export class KvStoreGateway { oracleUrl = await KVStoreUtils.get(chainId, address, KVStoreKeys.url); } catch (error) { - if (error instanceof InvalidKeyError) { - oracleUrl = ''; - } else if (error.toString().includes('Error: Invalid address')) { + if (error.toString().includes('Error: Invalid address')) { throw new HttpException( `Unable to retrieve URL from address: ${address}`, 400, @@ -53,7 +46,7 @@ export class KvStoreGateway { } } - if (!oracleUrl || oracleUrl === '') { + if (!oracleUrl) { throw new HttpException('Oracle does not have URL set in KV store', 422); } @@ -92,7 +85,7 @@ export class KvStoreGateway { } } - if (!jobTypes || jobTypes === '') { + if (!jobTypes) { return; } else { await this.cacheManager.set( diff --git a/packages/apps/job-launcher/client/Dockerfile b/packages/apps/job-launcher/client/Dockerfile index ec0bea13d1..b0d2180584 100644 --- a/packages/apps/job-launcher/client/Dockerfile +++ b/packages/apps/job-launcher/client/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/job-launcher-client # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/job-launcher-client run build +RUN yarn workspaces foreach -Rpt --from @apps/job-launcher-client --exclude @apps/job-launcher-client run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +32,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/job-launcher/client/eslint.config.mjs b/packages/apps/job-launcher/client/eslint.config.mjs index 349d15df68..0016de8981 100644 --- a/packages/apps/job-launcher/client/eslint.config.mjs +++ b/packages/apps/job-launcher/client/eslint.config.mjs @@ -33,6 +33,8 @@ export default tseslint.config( import: eslintPluginImport, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 1add81d388..9cf9b95fcd 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -8,7 +8,7 @@ "@emotion/styled": "^11.10.5", "@hcaptcha/react-hcaptcha": "^1.14.0", "@human-protocol/sdk": "workspace:*", - "@mui/icons-material": "^7.0.1", + "@mui/icons-material": "^7.3.8", "@mui/lab": "^6.0.0-dev.240424162023-9968b4889d", "@mui/material": "^5.16.7", "@mui/system": "^5.15.14", @@ -30,7 +30,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-redux": "^9.1.0", - "react-router-dom": "^6.14.1", + "react-router-dom": "^7.13.0", "recharts": "^2.7.2", "serve": "^14.2.4", "swr": "^2.2.4", @@ -76,7 +76,7 @@ "eslint-plugin-import": "^2.29.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^5.1.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "resize-observer-polyfill": "^1.5.1", "vite": "^6.2.4", "vite-plugin-node-polyfills": "^0.25.0" diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx index fedc78c4db..110b78efe2 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx @@ -8,7 +8,6 @@ import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import { JobType, PayMethod } from '../../../types'; import { CvatJobRequestForm } from './CvatJobRequestForm'; import { FortuneJobRequestForm } from './FortuneJobRequestForm'; -import { HCaptchaJobRequestForm } from './HCaptchaJobRequestForm'; export const CreateJob = () => { const { payMethod, jobRequest, updateJobRequest } = useCreateJobPageUI(); @@ -65,9 +64,6 @@ export const CreateJob = () => { Fortune )} {!IS_MAINNET && CVAT} - {/* {!IS_MAINNET && ( - hCaptcha - )} */} { {jobRequest.jobType === JobType.FORTUNE && } {jobRequest.jobType === JobType.CVAT && } - {jobRequest.jobType === JobType.HCAPTCHA && } ); }; diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx index 4b8a106d13..f9fce014bb 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx @@ -218,13 +218,7 @@ export const CryptoPayForm = ({ } // create job - const { - jobType, - chainId, - fortuneRequest, - cvatRequest, - hCaptchaRequest, - } = jobRequest; + const { jobType, chainId, fortuneRequest, cvatRequest } = jobRequest; if (jobType === JobType.FORTUNE && fortuneRequest) { await jobService.createFortuneJob( chainId, @@ -241,8 +235,6 @@ export const CryptoPayForm = ({ Number(amount), fundTokenSymbol, ); - } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { - await jobService.createHCaptchaJob(chainId, hCaptchaRequest); } onFinish(); } catch (err) { diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx index d2e2e5e164..82502c9b9b 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx @@ -28,11 +28,7 @@ import { TokenSelect } from '../../../components/TokenSelect'; import { CURRENCY } from '../../../constants/payment'; import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import { useSnackbar } from '../../../providers/SnackProvider'; -import { - createCvatJob, - createFortuneJob, - createHCaptchaJob, -} from '../../../services/job'; +import { createCvatJob, createFortuneJob } from '../../../services/job'; import { confirmFiatPayment, createFiatPayment, @@ -247,8 +243,7 @@ export const FiatPayForm = ({ } // create job - const { jobType, chainId, fortuneRequest, cvatRequest, hCaptchaRequest } = - jobRequest; + const { jobType, chainId, fortuneRequest, cvatRequest } = jobRequest; if (!chainId) return; if (jobType === JobType.FORTUNE && fortuneRequest) { @@ -267,8 +262,6 @@ export const FiatPayForm = ({ amount, tokenSymbol, ); - } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { - await createHCaptchaJob(chainId, hCaptchaRequest); } // Update balance and finish payment diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx deleted file mode 100644 index f12750a53d..0000000000 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx +++ /dev/null @@ -1,748 +0,0 @@ -import AddIcon from '@mui/icons-material/Add'; -import CloseIcon from '@mui/icons-material/Close'; -import CloudIcon from '@mui/icons-material/Cloud'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; -import InfoIcon from '@mui/icons-material/Info'; -import { - Autocomplete, - Box, - Button, - Chip, - FormControl, - FormHelperText, - Grid, - IconButton, - InputAdornment, - InputLabel, - MenuItem, - Select, - TextField, - Tooltip, -} from '@mui/material'; -import Typography from '@mui/material/Typography'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { useFormik } from 'formik'; -import { useEffect, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { - Accordion, - AccordionDetails, - AccordionSummary, -} from '../../../components/Accordion'; -import { CollectionsFilledIcon } from '../../../components/Icons/CollectionsFilledIcon'; -import languages from '../../../data/languages.json'; -import locations from '../../../data/locations.json'; -import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; -import { getQualifications } from '../../../services/qualification'; -import { HCaptchaJobType, Qualification } from '../../../types'; -import { HCaptchaJobRequesteValidationSchema } from './schema'; - -export const HCaptchaJobRequestForm = () => { - const { jobRequest, updateJobRequest, goToPrevStep, goToNextStep } = - useCreateJobPageUI(); - const [expanded, setExpanded] = useState(['panel1']); - const [searchParams] = useSearchParams(); - const [qualificationsOptions, setQualificationsOptions] = useState< - Qualification[] - >([]); - - const initialValues = { - dataUrl: '', - accuracyTarget: 85, - completionDate: null, - minRequests: null, - maxRequests: null, - qualifications: [], - // Advanced - workerLanguage: null, - workerLocation: null, - targetBrowser: 'desktop', - // Annotation - type: HCaptchaJobType.COMPARISON, - taskBidPrice: null, - label: '', - labelingPrompt: null, - groundTruths: null, - minWorkerConfidence: 85, - images: [], - }; - - const handleChange = - (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { - if (newExpanded) { - setExpanded([...expanded, panel]); - } else { - setExpanded(expanded.filter((item) => item !== panel)); - } - }; - - const handleNext = (data: any) => { - const hCaptchaRequest: any = { - dataUrl: data.dataUrl, - accuracyTarget: Number(data.accuracyTarget) / 100, - minRequests: Number(data.minRequests), - maxRequests: Number(data.maxRequests), - - qualifications: (data.qualifications as Qualification[]).map( - (qualification) => qualification.reference, - ), - advanced: { - workerLanguage: data.workerLanguage, - workerLocation: data.workerLocation, - targetBrowser: data.targetBrowser, - }, - annotations: { - typeOfJob: data.type, - taskBidPrice: Number(data.taskBidPrice), - label: data.label ?? '', - labelingPrompt: data.labelingPrompt, - groundTruths: data.groundTruths, - exampleImages: data.images, - }, - }; - if (data.completionDate) { - hCaptchaRequest['completionDate'] = data.completionDate; - } - updateJobRequest({ - ...jobRequest, - hCaptchaRequest, - }); - goToNextStep(); - }; - - const { - errors, - touched, - values, - dirty, - isValid, - handleSubmit, - handleBlur, - setFieldValue, - } = useFormik({ - initialValues, - validationSchema: HCaptchaJobRequesteValidationSchema, - onSubmit: handleNext, - }); - - useEffect(() => { - const type = searchParams.get('type'); - if ( - type && - Object.values(HCaptchaJobType).includes(type as HCaptchaJobType) - ) { - setFieldValue('type', type); - } - const fetchData = async () => { - if (jobRequest.chainId !== undefined) { - try { - setQualificationsOptions(await getQualifications(jobRequest.chainId)); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error fetching data:', error); - } - } - }; - - fetchData(); - }, [jobRequest.chainId]); - - return ( - -
- - - - - General - - - - - - - - - Select job type - - - - - - - - - - - - ), - }} - value={values.taskBidPrice} - onChange={(e) => - setFieldValue('taskBidPrice', e.target.value) - } - onBlur={handleBlur} - error={ - touched.taskBidPrice && Boolean(errors.taskBidPrice) - } - helperText={errors.taskBidPrice} - type="number" - /> - - - - - - - - - Unsure what job type to choose? - - - - - - - - - setFieldValue('minRequests', e.target.value) - } - onBlur={handleBlur} - error={touched.minRequests && Boolean(errors.minRequests)} - type="number" - /> - - - - - - setFieldValue('maxRequests', e.target.value) - } - onBlur={handleBlur} - error={touched.maxRequests && Boolean(errors.maxRequests)} - type="number" - /> - - - - - - - - Qualifications - - - - - - - - - option.title} - value={values.qualifications} - onChange={(event, newValues) => { - setFieldValue('qualifications', newValues); - }} - selectOnFocus - onBlur={handleBlur} - handleHomeEndKeys - renderTags={(value, getTagProps) => - value.map((option, index) => ( - - )) - } - renderInput={(params) => ( - - - - - - - ), - }} - /> - - )} - /> - - - - - - - - - - - Job annotation details - - - - - - - - - - ), - }} - value={values.dataUrl} - onChange={(e) => setFieldValue('dataUrl', e.target.value)} - onBlur={handleBlur} - error={touched.dataUrl && Boolean(errors.dataUrl)} - helperText={errors.dataUrl} - /> - - - {(values.type === HCaptchaJobType.POLYGON || - values.type === HCaptchaJobType.BOUNDING_BOX) && ( - - - setFieldValue('label', e.target.value)} - onBlur={handleBlur} - error={touched.label && Boolean(errors.label)} - /> - - - )} - - - - setFieldValue('labelingPrompt', e.target.value) - } - onBlur={handleBlur} - error={ - touched.labelingPrompt && Boolean(errors.labelingPrompt) - } - /> - - - {values.type !== HCaptchaJobType.CATEGORIZATION && ( - - - - Add Image - - Add up to 3 images to be shown by the capcha - - - - {values.images.map((image, index) => ( - - - {index + 1} - - { - const newImages: string[] = [...values.images]; - newImages[index] = e.target.value; - setFieldValue('images', newImages); - }} - onBlur={handleBlur} - error={Boolean(errors.images?.[index])} - /> - { - const newImages: string[] = [...values.images]; - newImages.splice(index, 1); - setFieldValue('images', newImages); - }} - > - - - - ))} - - - - - - - )} - - - - setFieldValue('groundTruths', e.target.value) - } - onBlur={handleBlur} - error={touched.groundTruths && Boolean(errors.groundTruths)} - /> - - - - - - - - - - ), - }} - value={values.accuracyTarget} - onChange={(e) => - setFieldValue('accuracyTarget', e.target.value) - } - onBlur={handleBlur} - error={ - touched.accuracyTarget && Boolean(errors.accuracyTarget) - } - helperText={errors.accuracyTarget} - type="number" - /> - - - - - - - - - - ), - }} - value={values.minWorkerConfidence} - onChange={(e) => - setFieldValue('minWorkerConfidence', e.target.value) - } - onBlur={handleBlur} - error={ - touched.minWorkerConfidence && - Boolean(errors.minWorkerConfidence) - } - type="number" - /> - - - - - - - - - - Advanced (optional) - - - - - - - - setFieldValue('completionDate', newValue) - } - slotProps={{ openPickerIcon: { sx: { color: '#858EC6' } } }} - /> - - - - - - Worker language - - - {errors.workerLanguage && ( - - {errors.workerLanguage} - - )} - - - - - - Target browser - - - {errors.targetBrowser && ( - - {errors.targetBrowser} - - )} - - - - - - Worker location - - - {errors.workerLocation && ( - - {errors.workerLocation} - - )} - - - - - - - - - -
-
- ); -}; diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts index 827358218a..85884c5fba 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts @@ -62,30 +62,3 @@ export const FortuneJobRequestValidationSchema = Yup.object().shape({ .moreThan(0, 'FortunesRequested must be greater than 0'), qualifications: Yup.array().of(Yup.object()), }); - -export const HCaptchaJobRequesteValidationSchema = Yup.object().shape({ - taskBidPrice: Yup.number() - .required('Task Bid Price is required') - .moreThan(0, 'Task Bid Price must be greater than 0'), - minRequests: Yup.number() - .required('Min Requests is required') - .moreThan(0, 'Min Requests must be greater than 0'), - maxRequests: Yup.number() - .required('Max Requests is required') - .moreThan( - Yup.ref('minRequests'), - 'Max Requests must be greater than min requests', - ), - dataUrl: Yup.string().required('Data URL is required').url('Invalid URL'), - labelingPrompt: Yup.string().required('Labeling Prompt is required'), - groundTruths: Yup.string() - .required('Ground Truth URL is required') - .url('Invalid URL'), - accuracyTarget: Yup.number() - .required('Accuracy target is required') - .moreThan(0, 'Accuracy target must be greater than 0') - .max(100, 'Accuracy target must be less than or equal to 100'), - targetBrowser: Yup.string().required('Target Browser is required'), - images: Yup.array().of(Yup.string().url('Invalid Image URL')), - qualifications: Yup.array().of(Yup.object()), -}); diff --git a/packages/apps/job-launcher/client/src/services/job.ts b/packages/apps/job-launcher/client/src/services/job.ts index fae6ea60d4..ceb719f492 100644 --- a/packages/apps/job-launcher/client/src/services/job.ts +++ b/packages/apps/job-launcher/client/src/services/job.ts @@ -6,7 +6,6 @@ import { CvatRequest, JobStatus, JobDetailsResponse, - HCaptchaRequest, FortuneFinalResult, } from '../types'; import api from '../utils/api'; @@ -56,16 +55,6 @@ export const createCvatJob = async ( await api.post('/job/cvat', body); }; -export const createHCaptchaJob = async ( - chainId: number, - data: HCaptchaRequest, -) => { - await api.post('/job/hCaptcha', { - chainId, - ...data, - }); -}; - export const getJobList = async ({ chainId = ChainId.ALL, status, diff --git a/packages/apps/job-launcher/client/src/types/index.ts b/packages/apps/job-launcher/client/src/types/index.ts index 6add95a163..0ca3d5a11b 100644 --- a/packages/apps/job-launcher/client/src/types/index.ts +++ b/packages/apps/job-launcher/client/src/types/index.ts @@ -98,14 +98,6 @@ export enum CvatJobType { IMAGE_SKELETONS_FROM_BOXES = 'image_skeletons_from_boxes', } -export enum HCaptchaJobType { - POLYGON = 'polygon', - CATEGORIZATION = 'categorization', - POINT = 'point', - BOUNDING_BOX = 'bounding_box', - COMPARISON = 'comparison', -} - export type FortuneRequest = { title: string; fortunesRequested: number; @@ -225,34 +217,11 @@ export type CvatRequest = { accuracyTarget: number; }; -export type HCaptchaRequest = { - dataUrl: string; - accuracyTarget: number; - completionDate: Date; - minRequests: number; - maxRequests: number; - qualifications?: string[]; - advanced: { - workerLanguage: string; - workerLocation: string; - targetBrowser: string; - }; - annotations: { - typeOfJob: string; - taskBidPrice: number; - label: string; - labelingPrompt: string; - groundTruths: string; - exampleImages: string[]; - }; -}; - export type JobRequest = { jobType: JobType; chainId?: ChainId; fortuneRequest?: FortuneRequest; cvatRequest?: CvatRequest; - hCaptchaRequest?: HCaptchaRequest; }; export enum JobStatus { diff --git a/packages/apps/job-launcher/server/Dockerfile b/packages/apps/job-launcher/server/Dockerfile index ddeb087e3b..3c5ff227c7 100644 --- a/packages/apps/job-launcher/server/Dockerfile +++ b/packages/apps/job-launcher/server/Dockerfile @@ -23,7 +23,7 @@ RUN yarn workspaces focus @apps/job-launcher-server # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ # Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/job-launcher-server run build +RUN yarn workspaces foreach -Rpt --from @apps/job-launcher-server --exclude @apps/job-launcher-server run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +32,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/job-launcher/server/eslint.config.mjs b/packages/apps/job-launcher/server/eslint.config.mjs index 4f8ea39dc5..8dd55ac3e3 100644 --- a/packages/apps/job-launcher/server/eslint.config.mjs +++ b/packages/apps/job-launcher/server/eslint.config.mjs @@ -30,6 +30,8 @@ const config = tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index c41032dea4..c048e3030d 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -30,7 +30,7 @@ "typeorm": "typeorm-ts-node-commonjs" }, "dependencies": { - "@google-cloud/storage": "^7.15.0", + "@google-cloud/storage": "^7.19.0", "@google-cloud/vision": "^4.3.2", "@human-protocol/logger": "workspace:*", "@human-protocol/sdk": "workspace:*", @@ -41,7 +41,7 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.12", - "@nestjs/schedule": "^6.1.0", + "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.5", "@nestjs/terminus": "^11.0.0", "@nestjs/throttler": "^6.5.0", @@ -51,7 +51,7 @@ "@types/uuid": "^10.0.0", "async-mutex": "^0.5.0", "axios": "^1.7.2", - "bcrypt": "^5.1.1", + "bcrypt": "^6.0.0", "body-parser": "^1.20.3", "class-transformer": "^0.5.1", "class-validator": "0.14.1", @@ -79,8 +79,8 @@ "@golevelup/ts-jest": "^0.6.1", "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", - "@types/bcrypt": "^5.0.2", + "@nestjs/testing": "^11.1.14", + "@types/bcrypt": "^6.0.0", "@types/express": "^5.0.6", "@types/jest": "30.0.0", "@types/node": "22.10.5", @@ -93,7 +93,7 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "source-map-support": "^0.5.20", "ts-jest": "29.2.5", "ts-node": "^10.9.2", 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 2dca07e7b4..195f9becb1 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 @@ -26,6 +26,7 @@ export const envValidator = Joi.object({ // Web3 WEB3_ENV: Joi.string(), WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number(), GAS_PRICE_MULTIPLIER: Joi.number(), APPROVE_AMOUNT_USD: Joi.number(), REPUTATION_ORACLE_ADDRESS: Joi.string().required(), 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 b2f0f8a5d0..9c5806f0f4 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 @@ -96,4 +96,12 @@ export class Web3ConfigService { get approveAmountUsd(): number { return this.configService.get('APPROVE_AMOUNT_USD', 0); } + + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } } 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 bb20d188ed..cf6079025a 100644 --- a/packages/apps/job-launcher/server/src/common/constants/tokens.ts +++ b/packages/apps/job-launcher/server/src/common/constants/tokens.ts @@ -90,5 +90,15 @@ export const TOKEN_ADDRESSES: { decimals: 18, symbol: EscrowFundToken.HMT, }, + [EscrowFundToken.USDC]: { + address: '0x64544969ed7EBf5f083679233325356EbE738930', + decimals: 18, + symbol: EscrowFundToken.USDC, + }, + [EscrowFundToken.USDT]: { + address: '0x66E972502A34A625828C544a1914E8D8cc2A9dE5', + decimals: 18, + symbol: EscrowFundToken.USDT, + }, }, }; diff --git a/packages/apps/job-launcher/server/src/common/exceptions/exception.filter.ts b/packages/apps/job-launcher/server/src/common/exceptions/exception.filter.ts index c1b6f3362a..de67ef3283 100644 --- a/packages/apps/job-launcher/server/src/common/exceptions/exception.filter.ts +++ b/packages/apps/job-launcher/server/src/common/exceptions/exception.filter.ts @@ -5,6 +5,7 @@ import { HttpStatus, } from '@nestjs/common'; import { Request, Response } from 'express'; +import { SubgraphRequestError } from '@human-protocol/sdk'; import { ValidationError, @@ -36,6 +37,8 @@ export class ExceptionFilter implements IExceptionFilter { return HttpStatus.UNPROCESSABLE_ENTITY; } else if (exception instanceof DatabaseError) { return HttpStatus.UNPROCESSABLE_ENTITY; + } else if (exception instanceof SubgraphRequestError) { + return HttpStatus.BAD_GATEWAY; } const exceptionStatusCode = exception.statusCode || exception.status; @@ -51,7 +54,12 @@ export class ExceptionFilter implements IExceptionFilter { const status = this.getStatus(exception); const message = exception.message || 'Internal server error'; - if (status === HttpStatus.INTERNAL_SERVER_ERROR) { + if (exception instanceof SubgraphRequestError) { + this.logger.error('Subgraph request failed', { + error: exception, + path: request.url, + }); + } else if (status === HttpStatus.INTERNAL_SERVER_ERROR) { this.logger.error('Unhandled exception', { error: exception, path: request.url, diff --git a/packages/apps/job-launcher/server/src/modules/job/fixtures.ts b/packages/apps/job-launcher/server/src/modules/job/fixtures.ts index f95f9fdc70..cf8f0ddf88 100644 --- a/packages/apps/job-launcher/server/src/modules/job/fixtures.ts +++ b/packages/apps/job-launcher/server/src/modules/job/fixtures.ts @@ -8,10 +8,9 @@ import { CvatJobType, EscrowFundToken, FortuneJobType, - JobCaptchaShapeType, } from '../../common/enums/job'; import { PaymentCurrency } from '../../common/enums/payment'; -import { JobCaptchaDto, JobCvatDto, JobFortuneDto } from './job.dto'; +import { JobCvatDto, JobFortuneDto } from './job.dto'; import { JobEntity } from './job.entity'; import { JobStatus } from '../../common/enums/job'; @@ -67,36 +66,6 @@ export const createCvatJobDto = (overrides = {}): JobCvatDto => ({ ...overrides, }); -export const createCaptchaJobDto = (overrides = {}): JobCaptchaDto => ({ - chainId: ChainId.POLYGON_AMOY, - data: { - provider: getMockedProvider(), - region: getMockedRegion(), - bucketName: faker.lorem.word(), - path: faker.system.filePath(), - }, - accuracyTarget: faker.number.float({ min: 0.1, max: 1, fractionDigits: 4 }), - 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, fractionDigits: 6 }), - 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)), - exchangeOracle: faker.finance.ethereumAddress(), - recordingOracle: faker.finance.ethereumAddress(), - reputationOracle: faker.finance.ethereumAddress(), - advanced: {}, - ...overrides, -}); - export const createJobEntity = ( overrides: Partial = {}, ): JobEntity => { 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 e5c3bb17c8..bc7ae37fb9 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 @@ -37,7 +37,6 @@ import { JobFortuneDto, JobIdDto, JobListDto, - // JobCaptchaDto, JobQuickLaunchDto, } from './job.dto'; import { JobService } from './job.service'; @@ -176,49 +175,6 @@ export class JobController { ); } - // @ApiOperation({ - // summary: 'Create a hCaptcha job', - // description: 'Endpoint to create a new hCaptcha job.', - // }) - // @ApiBody({ type: JobCaptchaDto }) - // @ApiResponse({ - // status: 201, - // description: 'ID of the created hCaptcha job.', - // type: Number, - // }) - // @ApiResponse({ - // status: 400, - // description: 'Bad Request. Invalid input parameters.', - // }) - // @ApiResponse({ - // status: 401, - // description: 'Unauthorized. Missing or invalid credentials.', - // }) - // @ApiResponse({ - // status: 409, - // description: 'Conflict. Conflict with the current state of the server.', - // }) - // @Post('/hCaptcha') - // public async createCaptchaJob( - // @Body() data: JobCaptchaDto, - // @Request() req: RequestWithUser, - // ): Promise { - // throw new ForbiddenError( - // 'Hcaptcha jobs disabled temporally', - // ); - // return await this.mutexManagerService.runExclusive( - // `user${req.user.id}`, - // MUTEX_TIMEOUT, - // async () => { - // return await this.jobService.createJob( - // req.user, - // JobRequestType.HCAPTCHA, - // data, - // ); - // }, - // ); - // } - @ApiOperation({ summary: 'Get a list of jobs', description: 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 32a58b0a85..729fcb3ccb 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 @@ -1,47 +1,39 @@ +import { ChainId } from '@human-protocol/sdk'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; import { + ArrayMinSize, IsArray, + IsEthereumAddress, + IsIn, + IsNotEmpty, IsNumber, + IsNumberString, + IsObject, + IsOptional, IsPositive, IsString, IsUrl, - IsDateString, - IsOptional, - IsObject, - IsNumberString, - IsIn, - Min, Max, - IsNotEmpty, - IsEthereumAddress, + Min, ValidateNested, - IsDefined, - IsNotEmptyObject, - ArrayMinSize, } from 'class-validator'; -import { Type } from 'class-transformer'; -import { ChainId } from '@human-protocol/sdk'; +import { IsEnumCaseInsensitive } from '../../common/decorators'; import { - JobCaptchaShapeType, + CvatJobType, EscrowFundToken, JobRequestType, JobSortField, JobStatus, JobStatusFilter, - WorkerBrowser, - WorkerLanguage, - Country, - CvatJobType, JobType, } from '../../common/enums/job'; -import { Transform } from 'class-transformer'; +import { PaymentCurrency } from '../../common/enums/payment'; import { AWSRegions, StorageProviders } from '../../common/enums/storage'; import { PageOptionsDto } from '../../common/pagination/pagination.dto'; -import { IsEnumCaseInsensitive } from '../../common/decorators'; -import { PaymentCurrency } from '../../common/enums/payment'; +import { IsValidTokenDecimals } from '../../common/validators/token-decimals'; 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' }) @@ -372,112 +364,4 @@ export class GetJobsDto extends PageOptionsDto { status?: JobStatusFilter; } -export class JobCaptchaAdvancedDto { - @ApiProperty({ - enum: WorkerLanguage, - name: 'worker_language', - }) - @IsEnumCaseInsensitive(WorkerLanguage) - @IsOptional() - workerLanguage?: WorkerLanguage; - - @ApiProperty({ - enum: Country, - name: 'worker_location', - }) - @IsEnumCaseInsensitive(Country) - @IsOptional() - workerLocation?: Country; - - @ApiProperty({ - enum: WorkerBrowser, - name: 'target_browser', - }) - @IsEnumCaseInsensitive(WorkerBrowser) - @IsOptional() - targetBrowser?: WorkerBrowser; -} - -class JobCaptchaAnnotationsDto { - @ApiProperty({ - enum: JobCaptchaShapeType, - name: 'type_of_job', - }) - @IsEnumCaseInsensitive(JobCaptchaShapeType) - typeOfJob: JobCaptchaShapeType; - - @ApiProperty({ name: 'task_bid_price' }) - @IsNumber() - @IsPositive() - taskBidPrice: number; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - label?: string; - - @ApiProperty({ name: 'labeling_prompt' }) - @IsString() - labelingPrompt: string; - - @ApiProperty({ name: 'ground_truths' }) - @IsString() - groundTruths: string; - - @ApiProperty({ name: 'example_images' }) - @IsOptional() - @IsArray() - exampleImages?: string[]; -} - -export class JobCaptchaDto extends JobDto { - @ApiProperty() - @IsObject() - @ValidateNested() - @Type(() => StorageDataDto) - data: StorageDataDto; - - @ApiProperty({ name: 'accuracy_target' }) - @IsNumber() - @IsPositive() - @Max(1) - accuracyTarget: number; - - @ApiProperty({ name: 'completion_date' }) - @IsDateString() - @IsOptional() - completionDate: Date; - - @ApiProperty({ name: 'min_requests' }) - @IsNumber() - @IsPositive() - @Max(100) - minRequests: number; - - @ApiProperty({ name: 'max_requests' }) - @IsNumber() - @IsPositive() - @Max(100) - maxRequests: number; - - @ApiProperty() - @IsDefined() - @IsObject() - @ValidateNested() - @Type(() => JobCaptchaAdvancedDto) - advanced: JobCaptchaAdvancedDto; - - @ApiProperty() - @IsDefined() - @IsNotEmptyObject() - @IsObject() - @ValidateNested() - @Type(() => JobCaptchaAnnotationsDto) - annotations: JobCaptchaAnnotationsDto; -} - -export type CreateJob = - | JobQuickLaunchDto - | JobFortuneDto - | JobCvatDto - | JobCaptchaDto; +export type CreateJob = JobQuickLaunchDto | JobFortuneDto | JobCvatDto; 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 1946bf3a75..b4ad55106a 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,7 +1,6 @@ import { CvatJobType, JobRequestType } from '../../common/enums/job'; import { CvatDataDto, - JobCaptchaDto, JobCvatDto, JobFortuneDto, StorageDataDto, @@ -10,7 +9,7 @@ import { JobEntity } from './job.entity'; export interface RequestAction { createManifest: ( - dto: JobFortuneDto | JobCvatDto | JobCaptchaDto, + dto: JobFortuneDto | JobCvatDto, requestType: JobRequestType, fundAmount: number, decimals: number, 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 42f7b763b0..98da9f3fe2 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 @@ -15,6 +15,7 @@ import { Test } from '@nestjs/testing'; import { ethers, ZeroAddress } from 'ethers'; import { createSignerMock } from '../../../test/fixtures/web3'; import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { ErrorEscrow, ErrorJob } from '../../common/constants/errors'; import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; import { @@ -41,7 +42,6 @@ import { getTokenDecimals } from '../../common/utils/tokens'; import { createMockCvatManifest, createMockFortuneManifest, - createMockHcaptchaManifest, } from '../manifest/fixtures'; import { ManifestService } from '../manifest/manifest.service'; import { PaymentRepository } from '../payment/payment.repository'; @@ -56,7 +56,6 @@ import { WebhookRepository } from '../webhook/webhook.repository'; import { WhitelistEntity } from '../whitelist/whitelist.entity'; import { WhitelistService } from '../whitelist/whitelist.service'; import { - createCaptchaJobDto, createCvatJobDto, createFortuneJobDto, createJobEntity, @@ -82,6 +81,9 @@ const mockRateService = createMock(); const mockRoutingProtocolService = createMock(); const mockManifestService = createMock(); const mockWhitelistService = createMock(); +const mockWeb3ConfigService = { + txTimeoutMs: faker.number.int({ min: 30000, max: 120000 }), +}; const mockedEscrowClient = jest.mocked(EscrowClient); const mockedEscrowUtils = jest.mocked(EscrowUtils); @@ -129,6 +131,7 @@ describe('JobService', () => { provide: ManifestService, useValue: mockManifestService, }, + { provide: Web3ConfigService, useValue: mockWeb3ConfigService }, ], }).compile(); @@ -568,92 +571,6 @@ describe('JobService', () => { }); }); - describe('HCaptcha', () => { - it('should create an HCaptcha job', async () => { - const captchaJobDto = createCaptchaJobDto(); - const fundTokenDecimals = getTokenDecimals( - captchaJobDto.chainId!, - captchaJobDto.escrowFundToken, - ); - - const mockManifest = createMockHcaptchaManifest(); - mockManifestService.createManifest.mockResolvedValueOnce(mockManifest); - const mockUrl = faker.internet.url(); - const mockHash = faker.string.uuid(); - mockManifestService.uploadManifest.mockResolvedValueOnce({ - url: mockUrl, - hash: mockHash, - }); - const jobEntityMock = createJobEntity(); - mockJobRepository.createUnique = jest - .fn() - .mockResolvedValueOnce(jobEntityMock); - mockRateService.getRate - .mockResolvedValueOnce(tokenToUsdRate) - .mockResolvedValueOnce(usdToTokenRate); - - await jobService.createJob( - userMock, - HCaptchaJobType.HCAPTCHA, - captchaJobDto, - ); - - expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith( - captchaJobDto.chainId, - ); - expect(mockRoutingProtocolService.selectOracles).not.toHaveBeenCalled(); - expect(mockRoutingProtocolService.validateOracles).toHaveBeenCalledWith( - captchaJobDto.chainId, - HCaptchaJobType.HCAPTCHA, - captchaJobDto.reputationOracle, - captchaJobDto.exchangeOracle, - captchaJobDto.recordingOracle, - ); - expect(mockManifestService.createManifest).toHaveBeenCalledWith( - captchaJobDto, - HCaptchaJobType.HCAPTCHA, - captchaJobDto.paymentAmount, - fundTokenDecimals, - ); - expect(mockManifestService.uploadManifest).toHaveBeenCalledWith( - captchaJobDto.chainId, - mockManifest, - [ - captchaJobDto.exchangeOracle, - captchaJobDto.reputationOracle, - captchaJobDto.recordingOracle, - ], - ); - expect(mockPaymentService.createWithdrawalPayment).toHaveBeenCalledWith( - userMock.id, - expect.any(Number), - captchaJobDto.paymentCurrency, - tokenToUsdRate, - ); - expect(mockJobRepository.updateOne).toHaveBeenCalledWith({ - chainId: captchaJobDto.chainId, - userId: userMock.id, - manifestUrl: mockUrl, - manifestHash: mockHash, - requestType: HCaptchaJobType.HCAPTCHA, - fee: expect.any(Number), - fundAmount: Number( - mul( - mul(captchaJobDto.paymentAmount, tokenToUsdRate), - usdToTokenRate, - ).toFixed(6), - ), - status: JobStatus.MODERATION_PASSED, - waitUntil: expect.any(Date), - token: captchaJobDto.escrowFundToken, - exchangeOracle: captchaJobDto.exchangeOracle, - recordingOracle: captchaJobDto.recordingOracle, - reputationOracle: captchaJobDto.reputationOracle, - payments: expect.any(Array), - }); - }); - }); - describe('JobQuickLaunchDto', () => { it('should create a job with quick launch dto', async () => { const jobQuickLaunchDto = new JobQuickLaunchDto(); @@ -754,7 +671,10 @@ describe('JobService', () => { } as unknown as EscrowClient); mockWeb3Service.ensureEscrowAllowance.mockResolvedValueOnce(undefined); - mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(1n); + mockWeb3Service.calculateTxFees.mockResolvedValueOnce({ + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + }); const token = (TOKEN_ADDRESSES[jobEntity.chainId as ChainId] ?? {})[ jobEntity.token as EscrowFundToken @@ -774,7 +694,7 @@ describe('JobService', () => { expectedWeiAmount, NETWORKS[jobEntity.chainId as ChainId]!.factoryAddress, ); - expect(mockWeb3Service.calculateGasPrice).toHaveBeenCalledWith( + expect(mockWeb3Service.calculateTxFees).toHaveBeenCalledWith( jobEntity.chainId, ); expect(createFundAndSetupEscrowMock).toHaveBeenCalledWith( @@ -783,15 +703,16 @@ describe('JobService', () => { jobEntity.userId.toString(), expect.objectContaining({ recordingOracle: jobEntity.recordingOracle, - recordingOracleFee: 1n, reputationOracle: jobEntity.reputationOracle, - reputationOracleFee: 1n, exchangeOracle: jobEntity.exchangeOracle, - exchangeOracleFee: 1n, manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }), - { gasPrice: 1n }, + { + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + timeoutMs: mockWeb3ConfigService.txTimeoutMs, + }, ); expect(result.status).toBe(JobStatus.LAUNCHED); expect(result.escrowAddress).toBe(escrowAddress); @@ -837,7 +758,10 @@ describe('JobService', () => { } as unknown as EscrowClient); mockWeb3Service.ensureEscrowAllowance.mockResolvedValueOnce(undefined); - mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(1n); + mockWeb3Service.calculateTxFees.mockResolvedValueOnce({ + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + }); const token = (TOKEN_ADDRESSES[jobEntity.chainId as ChainId] ?? {})[ jobEntity.token as EscrowFundToken @@ -864,7 +788,11 @@ describe('JobService', () => { expectedWeiAmount, jobEntity.userId.toString(), expect.any(Object), - { gasPrice: 1n }, + { + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + timeoutMs: mockWeb3ConfigService.txTimeoutMs, + }, ); expect(mockJobRepository.updateOne).not.toHaveBeenCalled(); @@ -1262,7 +1190,10 @@ describe('JobService', () => { describe('processEscrowCancellation', () => { it('should process escrow cancellation', async () => { const jobEntity = createJobEntity(); - mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(1n); + mockWeb3Service.calculateTxFees.mockResolvedValueOnce({ + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + }); const getStatusMock = jest.fn().mockResolvedValueOnce('Active'); const requestCancellationMock = jest .fn() @@ -1283,7 +1214,10 @@ describe('JobService', () => { it('should throw if escrow status is not Active', async () => { const jobEntity = createJobEntity(); - mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(1n); + mockWeb3Service.calculateTxFees.mockResolvedValueOnce({ + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + }); mockedEscrowClient.build.mockResolvedValueOnce({ getStatus: jest.fn().mockResolvedValueOnce(EscrowStatus.Complete), requestCancellation: jest.fn(), @@ -1299,7 +1233,7 @@ describe('JobService', () => { // TODO: Re-enable when cancellation is removed from processEscrowCancellation // it('should throw if requestCancellation throws an error', async () => { // const jobEntity = createJobEntity(); - // mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(1n); + // mockWeb3Service.calculateTxFees.mockResolvedValueOnce({ maxFeePerGas: 1n, maxPriorityFeePerGas: 1n }); // mockedEscrowClient.build.mockResolvedValueOnce({ // getStatus: jest.fn().mockResolvedValueOnce(EscrowStatus.Pending), // requestCancellation: jest 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 5984349463..271743182f 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 @@ -15,6 +15,7 @@ import { } from 'class-validator'; import { ethers } from 'ethers'; import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { CANCEL_JOB_STATUSES } from '../../common/constants'; import { ErrorEscrow, @@ -37,6 +38,7 @@ import { PaymentCurrency, PaymentType, } from '../../common/enums/payment'; +import { Web3Env } from '../../common/enums/web3'; import { EventType, OracleType } from '../../common/enums/webhook'; import { ConflictError, @@ -87,6 +89,7 @@ export class JobService { constructor( @Inject(Web3Service) private readonly web3Service: Web3Service, + private readonly web3ConfigService: Web3ConfigService, private readonly jobRepository: JobRepository, private readonly webhookRepository: WebhookRepository, private readonly paymentService: PaymentService, @@ -113,8 +116,8 @@ export class JobService { ): Promise { // DISABLE HMT if ( + this.web3ConfigService.env === Web3Env.MAINNET && requestType !== HCaptchaJobType.HCAPTCHA && - dto.chainId !== ChainId.LOCALHOST && (dto.escrowFundToken === EscrowFundToken.HMT || dto.paymentCurrency === PaymentCurrency.HMT) ) { @@ -315,20 +318,8 @@ export class JobService { const escrowConfig = { recordingOracle: jobEntity.recordingOracle, - recordingOracleFee: await this.getOracleFee( - jobEntity.recordingOracle, - jobEntity.chainId, - ), reputationOracle: jobEntity.reputationOracle, - reputationOracleFee: await this.getOracleFee( - jobEntity.reputationOracle, - jobEntity.chainId, - ), exchangeOracle: jobEntity.exchangeOracle, - exchangeOracleFee: await this.getOracleFee( - jobEntity.exchangeOracle, - jobEntity.chainId, - ), manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }; @@ -346,7 +337,8 @@ export class JobService { jobEntity.userId.toString(), escrowConfig, { - gasPrice: await this.web3Service.calculateGasPrice(jobEntity.chainId), + ...(await this.web3Service.calculateTxFees(jobEntity.chainId)), + timeoutMs: this.web3ConfigService.txTimeoutMs, }, ); @@ -610,7 +602,8 @@ export class JobService { // TODO: Remove try-catch when requestCancellation is fully supported by all escrows try { await (escrowClient as any).requestCancellation(escrowAddress!, { - gasPrice: await this.web3Service.calculateGasPrice(chainId), + ...(await this.web3Service.calculateTxFees(chainId)), + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } catch (error: any) { this.logger.warn( @@ -623,7 +616,8 @@ export class JobService { }, ); await (escrowClient as any).cancel(escrowAddress!, { - gasPrice: await this.web3Service.calculateGasPrice(chainId), + ...(await this.web3Service.calculateTxFees(chainId)), + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } } @@ -786,17 +780,11 @@ export class JobService { oracleAddress: string, chainId: ChainId, ): Promise { - let feeValue: string | undefined; - - try { - feeValue = await KVStoreUtils.get( - chainId, - oracleAddress, - KVStoreKeys.fee, - ); - } catch { - // Ignore error - } + const feeValue = await KVStoreUtils.get( + chainId, + oracleAddress, + KVStoreKeys.fee, + ); return BigInt(feeValue ? feeValue : 1); } diff --git a/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts index dde47c00b4..ba2b12bb13 100644 --- a/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts +++ b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts @@ -1,25 +1,14 @@ import { faker } from '@faker-js/faker'; import { ChainId } from '@human-protocol/sdk'; -import { AuthConfigService } from '../../common/config/auth-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { - CvatJobType, - EscrowFundToken, - JobCaptchaRequestType, - JobCaptchaShapeType, -} from '../../common/enums/job'; +import { CvatJobType, EscrowFundToken } from '../../common/enums/job'; import { PaymentCurrency } from '../../common/enums/payment'; -import { JobCaptchaDto, JobCvatDto } from '../job/job.dto'; +import { JobCvatDto } from '../job/job.dto'; import { getMockedProvider, getMockedRegion, } from '../../../test/fixtures/storage'; -import { - CvatManifestDto, - FortuneManifestDto, - HCaptchaManifestDto, -} from './manifest.dto'; +import { CvatManifestDto, FortuneManifestDto } from './manifest.dto'; import { FortuneJobType } from '../../common/enums/job'; export const mockCvatConfigService: Omit = { @@ -29,21 +18,6 @@ export const mockCvatConfigService: Omit = { 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 createJobCvatDto( overrides: Partial = {}, ): JobCvatDto { @@ -75,36 +49,6 @@ export function createJobCvatDto( }; } -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, - }; -} - export function createMockFortuneManifest( overrides: Partial = {}, ): FortuneManifestDto { @@ -150,55 +94,3 @@ export function createMockCvatManifest( ...overrides, }; } - -export function createMockHcaptchaManifest( - overrides: Partial = {}, -): HCaptchaManifestDto { - return { - job_mode: faker.lorem.word(), - request_type: faker.helpers.arrayElement( - Object.values(JobCaptchaRequestType), - ), - request_config: { - shape_type: faker.helpers.arrayElement( - Object.values(JobCaptchaShapeType), - ), - min_shapes_per_image: faker.number.int({ min: 1, max: 5 }), - max_shapes_per_image: faker.number.int({ min: 6, max: 10 }), - min_points: faker.number.int({ min: 1, max: 5 }), - max_points: faker.number.int({ min: 6, max: 10 }), - minimum_selection_area_per_shape: faker.number.int({ min: 1, max: 100 }), - multiple_choice_max_choices: faker.number.int({ min: 1, max: 5 }), - multiple_choice_min_choices: faker.number.int({ min: 1, max: 5 }), - answer_type: faker.lorem.word(), - overlap_threshold: faker.number.float({ min: 0, max: 1 }), - max_length: faker.number.int({ min: 1, max: 100 }), - min_length: faker.number.int({ min: 1, max: 100 }), - }, - requester_accuracy_target: faker.number.float({ min: 0.1, max: 1 }), - requester_max_repeats: faker.number.int({ min: 1, max: 10 }), - requester_min_repeats: faker.number.int({ min: 1, max: 10 }), - requester_question_example: [faker.internet.url()], - requester_question: { en: faker.lorem.sentence() }, - taskdata_uri: faker.internet.url(), - job_total_tasks: faker.number.int({ min: 1, max: 1000 }), - task_bid_price: faker.number.float({ min: 0.01, max: 10 }), - groundtruth_uri: faker.internet.url(), - public_results: faker.datatype.boolean(), - oracle_stake: faker.number.int({ min: 1, max: 100 }), - repo_uri: faker.internet.url(), - ro_uri: faker.internet.url(), - restricted_audience: {}, - requester_restricted_answer_set: {}, - taskdata: [ - { - task_key: faker.string.uuid(), - datapoint_uri: faker.internet.url(), - datapoint_hash: faker.string.uuid(), - datapoint_text: { en: faker.lorem.sentence() }, - }, - ], - qualifications: [faker.lorem.word()], - ...overrides, - }; -} 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 index e06ba03ca1..2f9363f42e 100644 --- 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 @@ -7,51 +7,19 @@ import { faker } from '@faker-js/faker'; import { createMock } from '@golevelup/ts-jest'; import { Encryption } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; -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 { - CvatJobType, - FortuneJobType, - HCaptchaJobType, - JobCaptchaMode, - JobCaptchaRequestType, - JobCaptchaShapeType, -} from '../../common/enums/job'; +import { CvatJobType, FortuneJobType } from '../../common/enums/job'; import { ConflictError, ServerError, ValidationError, } from '../../common/errors'; -import { - generateBucketUrl, - listObjectsInBucket, -} from '../../common/utils/storage'; +import { generateBucketUrl } from '../../common/utils/storage'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; -import { - createJobCaptchaDto, - createJobCvatDto, - mockAuthConfigService, - mockCvatConfigService, - mockWeb3ConfigService, -} from './fixtures'; +import { createJobCvatDto, mockCvatConfigService } from './fixtures'; import { FortuneManifestDto } from './manifest.dto'; import { ManifestService } from './manifest.service'; import { @@ -72,19 +40,11 @@ describe('ManifestService', () => { 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(); @@ -297,422 +257,6 @@ describe('ManifestService', () => { ).rejects.toThrow(new ConflictError(ErrorJob.DataNotExist)); }); }); - - 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 ValidationError 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 ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - 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 ValidationError 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 ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - 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 ValidationError 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 ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - 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 ValidationError 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 ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - it('should throw ValidationError 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 ValidationError(ErrorJob.HCaptchaInvalidJobType)); - }); - }); }); describe('uploadManifest', () => { 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 index 6276568d13..93a47b8481 100644 --- a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts @@ -5,34 +5,13 @@ import { } 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 { CvatJobType, FortuneJobType, HCaptchaJobType, - JobCaptchaMode, - JobCaptchaRequestType, - JobCaptchaShapeType, JobRequestType, } from '../../common/enums/job'; import { ConflictError, ValidationError } from '../../common/errors'; @@ -40,12 +19,7 @@ import { generateBucketUrl, listObjectsInBucket, } from '../../common/utils/storage'; -import { - CreateJob, - JobCaptchaAdvancedDto, - JobCaptchaDto, - JobCvatDto, -} from '../job/job.dto'; +import { CreateJob, JobCvatDto } from '../job/job.dto'; import { CvatAnnotationData, CvatCalculateJobBounty, @@ -59,7 +33,6 @@ import { FortuneManifestDto, HCaptchaManifestDto, ManifestDto, - RestrictedAudience, } from './manifest.dto'; @Injectable() @@ -68,8 +41,6 @@ export class ManifestService { 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, @@ -83,9 +54,6 @@ export class ManifestService { decimals: number, ): Promise { switch (requestType) { - case HCaptchaJobType.HCAPTCHA: - return this.createHCaptchaManifest(dto as JobCaptchaDto); - case FortuneJobType.FORTUNE: return { ...dto, @@ -311,226 +279,6 @@ export class ManifestService { }; } - 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 ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...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 || [], - }; - - case JobCaptchaShapeType.POINT: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...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 || [], - }; - - case JobCaptchaShapeType.BOUNDING_BOX: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...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 || [], - }; - - case JobCaptchaShapeType.IMMO: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...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: [], - }; - - default: - throw new ValidationError(ErrorJob.HCaptchaInvalidJobType); - } - } - - 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, diff --git a/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts b/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts index cedade9fad..d278f5b619 100644 --- a/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/payment/payment.controller.ts @@ -22,11 +22,14 @@ import { JwtAuthGuard } from '../../common/guards'; import { RequestWithUser } from '../../common/types'; import { ChainId } from '@human-protocol/sdk'; -import { ErrorPayment } from 'src/common/constants/errors'; import { ServerConfigService } from '../../common/config/server-config.service'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { HEADER_SIGNATURE_KEY } from '../../common/constants'; +import { ErrorPayment } from '../../common/constants/errors'; import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; import { ApiKey } from '../../common/decorators'; +import { EscrowFundToken } from '../../common/enums/job'; +import { Web3Env } from '../../common/enums/web3'; import { ConflictError, ServerError, @@ -51,7 +54,6 @@ import { UserBalanceDto, } from './payment.dto'; import { PaymentService } from './payment.service'; -import { EscrowFundToken } from '../../common/enums/job'; @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -61,6 +63,7 @@ export class PaymentController { constructor( private readonly paymentService: PaymentService, private readonly serverConfigService: ServerConfigService, + private readonly web3ConfigService: Web3ConfigService, private readonly rateService: RateService, ) {} @@ -452,7 +455,7 @@ export class PaymentController { throw new ValidationError(ErrorPayment.InvalidChainId); } - if (chainId === ChainId.LOCALHOST) { + if (this.web3ConfigService.env !== Web3Env.MAINNET) { return tokens; } diff --git a/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.spec.ts b/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.spec.ts index df1f6b43bc..d2d97a19b2 100644 --- a/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.spec.ts @@ -104,14 +104,23 @@ describe.only('QualificationService', () => { expect(result).toEqual(qualifications); }); - it('should throw a ServerError when KVStoreUtils.get fails', async () => { - (KVStoreUtils.get as any).mockRejectedValue(new Error('KV store error')); + it('should throw a ServerError when reputation oracle url not set', async () => { + (KVStoreUtils.get as any).mockResolvedValueOnce(''); await expect( qualificationService.getQualifications(ChainId.LOCALHOST), ).rejects.toThrow(new ServerError(ErrorWeb3.ReputationOracleUrlNotSet)); }); + it('should throw a ServerError when KVStoreUtils.get fails', async () => { + const syntheticError = new Error('KV store error'); + (KVStoreUtils.get as any).mockRejectedValue(syntheticError); + + await expect( + qualificationService.getQualifications(ChainId.LOCALHOST), + ).rejects.toThrow(new ServerError(syntheticError.message)); + }); + it('should throw a ServerError when HTTP request fails', async () => { (KVStoreUtils.get as any).mockResolvedValue(MOCK_REPUTATION_ORACLE_URL); diff --git a/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.ts b/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.ts index 9709208f4d..dd5d6e6b73 100644 --- a/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.ts +++ b/packages/apps/job-launcher/server/src/modules/qualification/qualification.service.ts @@ -25,20 +25,15 @@ export class QualificationService { public async getQualifications( chainId: ChainId, ): Promise { - let reputationOracleUrl = ''; this.web3Service.validateChainId(chainId); - try { - reputationOracleUrl = await KVStoreUtils.get( - chainId, - this.web3ConfigService.reputationOracleAddress, - KVStoreKeys.url, - ); - } catch { - // Ignore error - } + const reputationOracleUrl = await KVStoreUtils.get( + chainId, + this.web3ConfigService.reputationOracleAddress, + KVStoreKeys.url, + ); - if (!reputationOracleUrl || reputationOracleUrl === '') { + if (!reputationOracleUrl) { throw new ServerError(ErrorWeb3.ReputationOracleUrlNotSet); } diff --git a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts index a08edebae1..dcaaadd94b 100644 --- a/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/web3/web3.service.spec.ts @@ -36,6 +36,7 @@ jest.mock('@human-protocol/sdk', () => { describe('Web3Service', () => { let configService: ConfigService; let web3Service: Web3Service; + let web3ConfigService: Web3ConfigService; const mockRateService = { getRate: jest.fn(), }; @@ -66,6 +67,7 @@ describe('Web3Service', () => { }).compile(); web3Service = moduleRef.get(Web3Service); + web3ConfigService = moduleRef.get(Web3ConfigService); configService = moduleRef.get(ConfigService); }); @@ -115,41 +117,77 @@ describe('Web3Service', () => { }); }); - describe('calculateGasPrice', () => { - it('should return gas price multiplied by the multiplier', async () => { + describe('calculateTxFees', () => { + it('should return transaction fees multiplied by the multiplier', async () => { jest.spyOn(configService, 'get').mockImplementation((key: string) => { if (key === 'GAS_PRICE_MULTIPLIER') return 1; return mockConfig[key]; }); - const mockGasPrice = BigInt(1000000000); + const mockMaxFeePerGas = faker.number.bigInt(); + const mockMaxPriorityFeePerGas = faker.number.bigInt(); web3Service.getSigner = jest.fn().mockReturnValue({ address: MOCK_ADDRESS, getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), provider: { - getFeeData: jest - .fn() - .mockResolvedValueOnce({ gasPrice: mockGasPrice }), + getFeeData: jest.fn().mockResolvedValueOnce({ + maxFeePerGas: mockMaxFeePerGas, + maxPriorityFeePerGas: mockMaxPriorityFeePerGas, + }), }, }); - const result = await web3Service.calculateGasPrice(ChainId.POLYGON_AMOY); - expect(result).toBe(mockGasPrice * BigInt(1)); + const result = await web3Service.calculateTxFees(ChainId.POLYGON_AMOY); + expect(result).toEqual({ + maxFeePerGas: + mockMaxFeePerGas * BigInt(web3ConfigService.gasPriceMultiplier), + maxPriorityFeePerGas: + mockMaxPriorityFeePerGas * + BigInt(web3ConfigService.gasPriceMultiplier), + }); }); - it('should throw an error if gasPrice is undefined', async () => { + it('should throw an error if transaction fees are missing', async () => { web3Service.getSigner = jest.fn().mockReturnValue({ address: MOCK_ADDRESS, getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), provider: { - getFeeData: jest.fn().mockResolvedValueOnce({ gasPrice: undefined }), + getFeeData: jest.fn().mockResolvedValueOnce({ + maxFeePerGas: undefined, + maxPriorityFeePerGas: undefined, + }), }, }); await expect( - web3Service.calculateGasPrice(ChainId.POLYGON_AMOY), + web3Service.calculateTxFees(ChainId.POLYGON_AMOY), ).rejects.toThrow(new ConflictError(ErrorWeb3.GasPriceError)); }); + + it('should fallback to legacy gasPrice data', async () => { + const mockGasPrice = faker.number.bigInt(); + + web3Service.getSigner = jest.fn().mockReturnValue({ + address: MOCK_ADDRESS, + getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }), + provider: { + getFeeData: jest.fn().mockResolvedValueOnce({ + gasPrice: mockGasPrice, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + }), + }, + }); + + await expect( + web3Service.calculateTxFees(ChainId.POLYGON_AMOY), + ).resolves.toEqual({ + maxFeePerGas: + mockGasPrice * BigInt(web3ConfigService.gasPriceMultiplier), + maxPriorityFeePerGas: + mockGasPrice * BigInt(web3ConfigService.gasPriceMultiplier), + }); + }); }); describe('validateChainId', () => { diff --git a/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts b/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts index a288dc2be3..10bb5fdd40 100644 --- a/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts +++ b/packages/apps/job-launcher/server/src/modules/web3/web3.service.ts @@ -45,14 +45,29 @@ export class Web3Service { } } - public async calculateGasPrice(chainId: number): Promise { + public async calculateTxFees(chainId: number): Promise<{ + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + }> { const signer = this.getSigner(chainId); - const multiplier = this.web3ConfigService.gasPriceMultiplier; + const multiplier = BigInt(this.web3ConfigService.gasPriceMultiplier); + const feeData = await signer.provider?.getFeeData(); - const gasPrice = (await signer.provider?.getFeeData())?.gasPrice; - if (gasPrice) { - return gasPrice * BigInt(multiplier); + if (!feeData) { + throw new ConflictError(ErrorWeb3.GasPriceError); } + + const maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice; + const maxPriorityFeePerGas = + feeData.maxPriorityFeePerGas ?? feeData.gasPrice; + + if (maxFeePerGas && maxPriorityFeePerGas) { + return { + maxFeePerGas: maxFeePerGas * multiplier, + maxPriorityFeePerGas: maxPriorityFeePerGas * multiplier, + }; + } + throw new ConflictError(ErrorWeb3.GasPriceError); } diff --git a/packages/apps/reputation-oracle/server/Dockerfile b/packages/apps/reputation-oracle/server/Dockerfile index c8e947c145..425086d324 100644 --- a/packages/apps/reputation-oracle/server/Dockerfile +++ b/packages/apps/reputation-oracle/server/Dockerfile @@ -22,8 +22,9 @@ RUN yarn workspaces focus @apps/reputation-oracle # Copy base TS config that is required to build packages COPY tsconfig.base.json ./ -# Build libs (scoped) -RUN yarn workspaces foreach -Rpt --from @apps/reputation-oracle run build +# Build only dependency workspaces; the app itself is built later +# after its full source (including tsconfig.json) is copied. +RUN yarn workspaces foreach -Rpt --from @apps/reputation-oracle --exclude @apps/reputation-oracle run build # Copy everything else COPY ${APP_PATH} ./${APP_PATH} @@ -32,4 +33,4 @@ WORKDIR ./${APP_PATH} RUN yarn build # Start the server using the build -CMD [ "yarn", "start:prod" ] \ No newline at end of file +CMD [ "yarn", "start:prod" ] diff --git a/packages/apps/reputation-oracle/server/eslint.config.mjs b/packages/apps/reputation-oracle/server/eslint.config.mjs index 19d77d0394..6275a10188 100644 --- a/packages/apps/reputation-oracle/server/eslint.config.mjs +++ b/packages/apps/reputation-oracle/server/eslint.config.mjs @@ -28,6 +28,8 @@ export default tseslint.config( 'import': importPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn', diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index 83b3a75c43..9ea92ab5a0 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -40,7 +40,7 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.12", - "@nestjs/schedule": "^6.1.0", + "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.5", "@nestjs/terminus": "^11.0.0", "@nestjs/typeorm": "^11.0.0", @@ -50,7 +50,7 @@ "@slack/webhook": "^7.0.5", "@types/passport-jwt": "^4.0.1", "axios": "^1.8.1", - "bcrypt": "^5.1.1", + "bcrypt": "^6.0.0", "body-parser": "^1.20.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -73,13 +73,13 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@eslint/js": "^9.33.0", + "@eslint/js": "^10.0.1", "@faker-js/faker": "^9.8.0", "@golevelup/ts-jest": "^0.6.1", "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.12", - "@types/bcrypt": "^5.0.2", + "@nestjs/testing": "^11.1.14", + "@types/bcrypt": "^6.0.0", "@types/express": "^5.0.6", "@types/jest": "30.0.0", "@types/lodash": "^4.17.14", @@ -93,8 +93,8 @@ "eslint-plugin-prettier": "^5.5.5", "globals": "^16.3.0", "jest": "^29.7.0", - "nock": "^14.0.3", - "prettier": "^3.7.4", + "nock": "^14.0.11", + "prettier": "^3.8.1", "ts-jest": "29.2.5", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", 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 f15ee7315c..eac86fcbea 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 @@ -1,3 +1,4 @@ +import { SubgraphRequestError } from '@human-protocol/sdk'; import { ArgumentsHost, Catch, @@ -31,6 +32,13 @@ export class ExceptionFilter implements IExceptionFilter { responseBody.message = 'Unprocessable entity'; } this.logger.error('Database error', exception); + } else if (exception instanceof SubgraphRequestError) { + status = HttpStatus.BAD_GATEWAY; + responseBody.message = exception.message; + this.logger.error('Subgraph request failed', { + error: exception, + path: request.url, + }); } else if (exception instanceof HttpException) { status = exception.getStatus(); const exceptionResponse = exception.getResponse(); diff --git a/packages/apps/reputation-oracle/server/src/config/env-schema.ts b/packages/apps/reputation-oracle/server/src/config/env-schema.ts index 78876e7ec8..c2a55a98d8 100644 --- a/packages/apps/reputation-oracle/server/src/config/env-schema.ts +++ b/packages/apps/reputation-oracle/server/src/config/env-schema.ts @@ -47,6 +47,7 @@ export const envValidator = Joi.object({ // Web3 WEB3_ENV: Joi.string().valid(...Object.values(Web3Network)), WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number().integer(), GAS_PRICE_MULTIPLIER: Joi.number().positive(), RPC_URL_SEPOLIA: Joi.string().uri({ scheme: ['http', 'https'] }), RPC_URL_POLYGON: Joi.string().uri({ scheme: ['http', 'https'] }), 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 dbb40d55f4..98cc7b4a0e 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 @@ -56,6 +56,14 @@ export class Web3ConfigService { return Number(this.configService.get('GAS_PRICE_MULTIPLIER')) || 1; } + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } + getRpcUrlByChainId(chainId: number): string | undefined { const rpcUrlsByChainId: Record = { [ChainId.POLYGON]: this.configService.get('RPC_URL_POLYGON'), diff --git a/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts b/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts index 29f9ac3618..45ca142409 100644 --- a/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts +++ b/packages/apps/reputation-oracle/server/src/modules/auth/auth.error.ts @@ -26,19 +26,19 @@ export class InvalidOperatorSignupDataError extends BaseError { } export class InvalidOperatorRoleError extends InvalidOperatorSignupDataError { - constructor(role: string) { + constructor(role?: string) { super(`Invalid role: ${role}`); } } export class InvalidOperatorFeeError extends InvalidOperatorSignupDataError { - constructor(fee: string) { + constructor(fee?: string) { super(`Invalid fee: ${fee}`); } } export class InvalidOperatorUrlError extends InvalidOperatorSignupDataError { - constructor(url: string) { + constructor(url?: string) { super(`Invalid url: ${url}`); } } 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 9606f53499..3eebd54bb9 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 @@ -198,7 +198,7 @@ describe('AuthService', () => { return faker.internet.url(); } - throw new Error('Invalid key'); + return ''; }, ); @@ -278,7 +278,7 @@ describe('AuthService', () => { return mockedRole; } - throw new Error('Invalid key'); + return ''; }, ); @@ -301,9 +301,7 @@ describe('AuthService', () => { mockUserRepository.findOneByAddress.mockResolvedValueOnce(null); const mockedRole = faker.string.alpha(); - mockKVStoreUtils.get.mockImplementation(async () => { - throw new Error('Invalid key'); - }); + mockKVStoreUtils.get.mockResolvedValueOnce(''); await expect( service.web3Signup(signature, ethWallet.address), @@ -332,7 +330,7 @@ describe('AuthService', () => { return ''; } - throw new Error('Invalid key'); + return ''; }, ); @@ -360,7 +358,7 @@ describe('AuthService', () => { return Role.ExchangeOracle; } - throw new Error('Invalid key'); + return ''; }, ); @@ -400,7 +398,7 @@ describe('AuthService', () => { return invalidUrl; } - throw new Error('Invalid key'); + return ''; }, ); @@ -432,7 +430,7 @@ describe('AuthService', () => { return String(faker.number.int({ min: 1, max: 50 })); } - throw new Error('Invalid key'); + return ''; }, ); @@ -821,7 +819,7 @@ describe('AuthService', () => { return mockedOperatorStatus; } - throw new Error('Invalid key'); + return ''; }, ); @@ -855,9 +853,7 @@ describe('AuthService', () => { accessToken: faker.string.alpha(), refreshToken: faker.string.uuid(), }); - mockKVStoreUtils.get.mockImplementation(async () => { - throw new Error('Invalid key'); - }); + mockKVStoreUtils.get.mockResolvedValueOnce(''); await service.web3Auth(operator); 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 24afae39c2..e32d92f809 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 @@ -113,13 +113,8 @@ export class AuthService { } const chainId = this.web3ConfigService.reputationNetworkChainId; - let role = ''; - try { - role = await KVStoreUtils.get(chainId, address, KVStoreKeys.role); - } catch { - // noop - } + const role = await KVStoreUtils.get(chainId, address, KVStoreKeys.role); // We need to exclude ReputationOracle role const isValidRole = [ Role.JobLauncher, @@ -131,22 +126,12 @@ export class AuthService { throw new InvalidOperatorRoleError(role); } - let fee = ''; - try { - fee = await KVStoreUtils.get(chainId, address, KVStoreKeys.fee); - } catch { - // noop - } + const fee = await KVStoreUtils.get(chainId, address, KVStoreKeys.fee); if (!fee) { throw new InvalidOperatorFeeError(fee); } - let url = ''; - try { - url = await KVStoreUtils.get(chainId, address, KVStoreKeys.url); - } catch { - // noop - } + const url = await KVStoreUtils.get(chainId, address, KVStoreKeys.url); if (!url || !httpUtils.isValidHttpUrl(url)) { throw new InvalidOperatorUrlError(url); } @@ -317,16 +302,12 @@ export class AuthService { * and subgraph does not have the actual value yet, * the status can be outdated */ - let operatorStatus = OperatorStatus.INACTIVE; - try { - operatorStatus = (await KVStoreUtils.get( + const operatorStatus = + (await KVStoreUtils.get( this.web3ConfigService.reputationNetworkChainId, this.web3ConfigService.operatorAddress, userEntity.evmAddress, - )) as OperatorStatus; - } catch { - // noop - } + )) || OperatorStatus.INACTIVE; const jwtPayload = { status: userEntity.status, 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 e13aa05392..d8a951f98f 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 @@ -26,11 +26,14 @@ import stringify from 'json-stable-stringify'; import _ from 'lodash'; import { CvatJobType, FortuneJobType } from '@/common/enums'; -import { ServerConfigService } from '@/config'; +import { ServerConfigService, Web3ConfigService } from '@/config'; import { ReputationService } from '@/modules/reputation'; import { StorageService } from '@/modules/storage'; import { WalletWithProvider, Web3Service } from '@/modules/web3'; -import { generateTestnetChainId } from '@/modules/web3/fixtures'; +import { + generateTestnetChainId, + mockWeb3ConfigService, +} from '@/modules/web3/fixtures'; import { OutgoingWebhookService } from '@/modules/webhook'; import { createSignerMock, type SignerMock } from '~/test/fixtures/web3'; @@ -99,6 +102,10 @@ describe('EscrowCompletionService', () => { provide: StorageService, useValue: mockStorageService, }, + { + provide: Web3ConfigService, + useValue: mockWeb3ConfigService, + }, { provide: OutgoingWebhookService, useValue: mockOutgoingWebhookService, @@ -998,8 +1005,11 @@ describe('EscrowCompletionService', () => { recordingOracle: recordingOracleAddress, } as unknown as IEscrow); mockGetEscrowStatus.mockResolvedValueOnce(escrowStatus); - const mockGasPrice = faker.number.bigInt(); - mockWeb3Service.calculateGasPrice.mockResolvedValueOnce(mockGasPrice); + const mockFees = { + maxFeePerGas: faker.number.bigInt(), + maxPriorityFeePerGas: faker.number.bigInt(), + }; + mockWeb3Service.calculateTxFees.mockResolvedValueOnce(mockFees); const paidPayoutsRecord = generateEscrowCompletion( EscrowCompletionStatus.PAID, @@ -1042,7 +1052,8 @@ describe('EscrowCompletionService', () => { expect(mockCompleteEscrow).toHaveBeenCalledWith( paidPayoutsRecord.escrowAddress, { - gasPrice: mockGasPrice, + ...mockFees, + timeoutMs: mockWeb3ConfigService.txTimeoutMs, }, ); expect(mockReputationService.assessEscrowParties).toHaveBeenCalledTimes( 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 205e915fcc..cf119c2515 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 @@ -16,7 +16,7 @@ import { v4 as uuidv4 } from 'uuid'; import { BACKOFF_INTERVAL_SECONDS } from '@/common/constants'; import { JobManifest, JobRequestType } from '@/common/types'; -import { ServerConfigService } from '@/config'; +import { ServerConfigService, Web3ConfigService } from '@/config'; import { isDuplicatedError } from '@/database'; import logger from '@/logger'; import { ReputationService } from '@/modules/reputation'; @@ -58,6 +58,7 @@ export class EscrowCompletionService { private readonly escrowCompletionRepository: EscrowCompletionRepository, private readonly escrowPayoutsBatchRepository: EscrowPayoutsBatchRepository, private readonly web3Service: Web3Service, + private readonly web3ConfigService: Web3ConfigService, private readonly storageService: StorageService, private readonly outgoingWebhookService: OutgoingWebhookService, private readonly reputationService: ReputationService, @@ -240,13 +241,19 @@ export class EscrowCompletionService { EscrowStatus.ToCancel, ].includes(escrowStatus) ) { - const gasPrice = await this.web3Service.calculateGasPrice(chainId); + const feeOverrides = await this.web3Service.calculateTxFees(chainId); if (escrowStatus === EscrowStatus.ToCancel) { - await escrowClient.cancel(escrowAddress, { gasPrice }); + await escrowClient.cancel(escrowAddress, { + ...feeOverrides, + timeoutMs: this.web3ConfigService.txTimeoutMs, + }); escrowStatus = EscrowStatus.Cancelled; } else { - await escrowClient.complete(escrowAddress, { gasPrice }); + await escrowClient.complete(escrowAddress, { + ...feeOverrides, + timeoutMs: this.web3ConfigService.txTimeoutMs, + }); escrowStatus = EscrowStatus.Complete; } @@ -439,9 +446,9 @@ export class EscrowCompletionService { uuidv4(), // TODO obtain it from intermediate results false, { - gasPrice: await this.web3Service.calculateGasPrice( + ...(await this.web3Service.calculateTxFees( escrowCompletionEntity.chainId, - ), + )), nonce: payoutsBatch.txNonce, }, ); @@ -453,7 +460,10 @@ export class EscrowCompletionService { try { const transactionResponse = await signer.sendTransaction(rawTransaction); - await transactionResponse.wait(); + await transactionResponse.wait( + undefined, + this.web3ConfigService.txTimeoutMs, + ); await this.escrowPayoutsBatchRepository.deleteOne(payoutsBatch); } catch (error) { 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 be2744f64a..c81002a821 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 @@ -30,7 +30,6 @@ import { } from './user.error'; import { UserRepository } from './user.repository'; import { UserService, OperatorStatus } from './user.service'; - const mockUserRepository = createMock(); const mockSiteKeyRepository = createMock(); const mockHCaptchaService = createMock(); @@ -472,6 +471,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.ACTIVE, + { timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); }); }); @@ -554,6 +554,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.INACTIVE, + { timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); }); }); 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 331a6003c0..777b2cedcd 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 @@ -204,16 +204,11 @@ export class UserService { const signer = this.web3Service.getSigner(chainId); const kvstore = await KVStoreClient.build(signer); - let status: string | undefined; - try { - status = await KVStoreUtils.get( - chainId, - signer.address, - operatorUser.evmAddress, - ); - } catch { - // noop - } + const status = await KVStoreUtils.get( + chainId, + signer.address, + operatorUser.evmAddress, + ); if (status === OperatorStatus.ACTIVE) { throw new UserError( @@ -222,7 +217,9 @@ export class UserService { ); } - await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE, { + timeoutMs: this.web3ConfigService.txTimeoutMs, + }); } async disableOperator(userId: number, signature: string): Promise { @@ -266,7 +263,9 @@ export class UserService { ); } - await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE, { + timeoutMs: this.web3ConfigService.txTimeoutMs, + }); } async registrationInExchangeOracle( diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts index fde77f55ed..fe1eff7c1f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts @@ -18,6 +18,7 @@ export const mockWeb3ConfigService: Omit = { operatorAddress: testWallet.address, network: Web3Network.TESTNET, gasPriceMultiplier: faker.number.int({ min: 1, max: 42 }), + txTimeoutMs: faker.number.int({ min: 30000, max: 120000 }), reputationNetworkChainId: generateTestnetChainId(), getRpcUrlByChainId: () => faker.internet.url(), }; 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 adef07f9e6..f3b58e84d0 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 @@ -54,7 +54,7 @@ describe('Web3Service', () => { }); }); - describe('calculateGasPrice', () => { + describe('calculateTxFees', () => { const mockProvider = createMock(); let spyOnGetSigner: jest.SpyInstance; @@ -75,32 +75,61 @@ describe('Web3Service', () => { mockProvider.getFeeData.mockReset(); }); - it('should use multiplier for gas price', async () => { + it('should use multiplier for transaction fees', async () => { const testChainId = generateTestnetChainId(); - const randomGasPrice = faker.number.bigInt({ min: 1n }); + const randomMaxFeePerGas = faker.number.bigInt(); + const randomMaxPriorityFeePerGas = faker.number.bigInt(); mockProvider.getFeeData.mockResolvedValueOnce({ - gasPrice: randomGasPrice, + maxFeePerGas: randomMaxFeePerGas, + maxPriorityFeePerGas: randomMaxPriorityFeePerGas, } as FeeData); - const gasPrice = await web3Service.calculateGasPrice(testChainId); - - const expectedGasPrice = - randomGasPrice * BigInt(mockWeb3ConfigService.gasPriceMultiplier); - expect(gasPrice).toEqual(expectedGasPrice); + const fees = await web3Service.calculateTxFees(testChainId); + + const expectedMaxFeePerGas = + randomMaxFeePerGas * BigInt(mockWeb3ConfigService.gasPriceMultiplier); + const expectedMaxPriorityFeePerGas = + randomMaxPriorityFeePerGas * + BigInt(mockWeb3ConfigService.gasPriceMultiplier); + expect(fees).toEqual({ + maxFeePerGas: expectedMaxFeePerGas, + maxPriorityFeePerGas: expectedMaxPriorityFeePerGas, + }); }); - it('should throw if no gas price from provider', async () => { + it('should throw if transaction fees are missing', async () => { const testChainId = generateTestnetChainId(); mockProvider.getFeeData.mockResolvedValueOnce({ - gasPrice: null, + maxFeePerGas: null, + maxPriorityFeePerGas: null, } as FeeData); - await expect(web3Service.calculateGasPrice(testChainId)).rejects.toThrow( - `No gas price data for chain id: ${testChainId}`, + await expect(web3Service.calculateTxFees(testChainId)).rejects.toThrow( + `No transaction fee data for chain id: ${testChainId}`, ); }); + + it('should fallback to legacy gasPrice data', async () => { + const testChainId = generateTestnetChainId(); + const randomGasPrice = faker.number.bigInt(); + + mockProvider.getFeeData.mockResolvedValueOnce({ + gasPrice: randomGasPrice, + maxFeePerGas: null, + maxPriorityFeePerGas: null, + } as FeeData); + + const fees = await web3Service.calculateTxFees(testChainId); + const expectedFee = + randomGasPrice * BigInt(mockWeb3ConfigService.gasPriceMultiplier); + + expect(fees).toEqual({ + maxFeePerGas: expectedFee, + maxPriorityFeePerGas: expectedFee, + }); + }); }); }); 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 768196b62a..5b81ab2c6f 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 @@ -85,15 +85,26 @@ export class Web3Service { throw new Error(`No signer for provided chain id: ${chainId}`); } - async calculateGasPrice(chainId: number): Promise { + async calculateTxFees(chainId: number): Promise<{ + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + }> { const signer = this.getSigner(chainId); - const { gasPrice } = await signer.provider.getFeeData(); - - if (gasPrice) { - return gasPrice * BigInt(this.web3ConfigService.gasPriceMultiplier); + const feeData = await signer.provider.getFeeData(); + const multiplier = BigInt(this.web3ConfigService.gasPriceMultiplier); + + const maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice; + const maxPriorityFeePerGas = + feeData.maxPriorityFeePerGas ?? feeData.gasPrice; + + if (maxFeePerGas && maxPriorityFeePerGas) { + return { + maxFeePerGas: maxFeePerGas * multiplier, + maxPriorityFeePerGas: maxPriorityFeePerGas * multiplier, + }; } - throw new Error(`No gas price data for chain id: ${chainId}`); + throw new Error(`No transaction fee data for chain id: ${chainId}`); } async getTokenDecimals( diff --git a/packages/apps/staking/eslint.config.mjs b/packages/apps/staking/eslint.config.mjs index 6515b17715..72bf9bf8bd 100644 --- a/packages/apps/staking/eslint.config.mjs +++ b/packages/apps/staking/eslint.config.mjs @@ -29,6 +29,8 @@ export default tseslint.config( 'react-refresh': reactRefreshPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', 'react-refresh/only-export-components': [ diff --git a/packages/apps/staking/package.json b/packages/apps/staking/package.json index efd250ad55..0401dffed8 100644 --- a/packages/apps/staking/package.json +++ b/packages/apps/staking/package.json @@ -31,7 +31,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@human-protocol/sdk": "*", - "@mui/icons-material": "^7.0.1", + "@mui/icons-material": "^7.3.8", "@mui/material": "^5.16.7", "@mui/x-data-grid": "^8.7.0", "@tanstack/query-sync-storage-persister": "^5.68.0", @@ -41,7 +41,7 @@ "ethers": "^6.15.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.24.1", + "react-router-dom": "^7.13.0", "serve": "^14.2.4", "simplebar-react": "^3.3.2", "viem": "2.x", @@ -57,7 +57,7 @@ "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.11", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "sass": "^1.89.2", "typescript": "^5.6.3", "vite": "^6.2.4", diff --git a/packages/core/.openzeppelin/bsc-testnet.json b/packages/core/.openzeppelin/bsc-testnet.json index 7079e24781..ea249118dd 100644 --- a/packages/core/.openzeppelin/bsc-testnet.json +++ b/packages/core/.openzeppelin/bsc-testnet.json @@ -3113,6 +3113,171 @@ }, "namespaces": {} } + }, + "0f95cf567f8918b75255368d276f9d1d3ff01313866d4084b4a734938d09a6e6": { + "address": "0x2163e3A40032Af1C359ac731deaB48258b317890", + "txHash": "0x25407903a1977a9875a28b51f4a97808e9fb0e414d527bfb5286c0d05306fe4e", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "counter", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:17" + }, + { + "label": "escrowCounters", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_uint256)", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:18" + }, + { + "label": "lastEscrow", + "offset": 0, + "slot": "203", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:19" + }, + { + "label": "staking", + "offset": 0, + "slot": "204", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:20" + }, + { + "label": "minimumStake", + "offset": 0, + "slot": "205", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:21" + }, + { + "label": "admin", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:22" + }, + { + "label": "kvstore", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:195" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/packages/core/.openzeppelin/sepolia.json b/packages/core/.openzeppelin/sepolia.json index 8c7acb1181..d34290bbf8 100644 --- a/packages/core/.openzeppelin/sepolia.json +++ b/packages/core/.openzeppelin/sepolia.json @@ -4020,6 +4020,171 @@ }, "namespaces": {} } + }, + "0f95cf567f8918b75255368d276f9d1d3ff01313866d4084b4a734938d09a6e6": { + "address": "0x85123A7E31a7b60F6f9D38c9f968cF641A3C9E4E", + "txHash": "0x61a4f8b38ec3c868ebbd198a9fc77f61708a51a9d131b82a988971d4416f56f2", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "counter", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:17" + }, + { + "label": "escrowCounters", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_uint256)", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:18" + }, + { + "label": "lastEscrow", + "offset": 0, + "slot": "203", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:19" + }, + { + "label": "staking", + "offset": 0, + "slot": "204", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:20" + }, + { + "label": "minimumStake", + "offset": 0, + "slot": "205", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:21" + }, + { + "label": "admin", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:22" + }, + { + "label": "kvstore", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:195" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/packages/core/.openzeppelin/unknown-80002.json b/packages/core/.openzeppelin/unknown-80002.json index 0d69cc975a..b7209eab96 100644 --- a/packages/core/.openzeppelin/unknown-80002.json +++ b/packages/core/.openzeppelin/unknown-80002.json @@ -2122,6 +2122,171 @@ }, "namespaces": {} } + }, + "0f95cf567f8918b75255368d276f9d1d3ff01313866d4084b4a734938d09a6e6": { + "address": "0x1d372f1771f7a87Bc2C67B79e78355940fAaE1F5", + "txHash": "0xecb82f55621a5a836db78cce6b2dac34a9057446d224a4ba401874d8b44c8c74", + "layout": { + "solcVersion": "0.8.23", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "151", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "counter", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:17" + }, + { + "label": "escrowCounters", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_uint256)", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:18" + }, + { + "label": "lastEscrow", + "offset": 0, + "slot": "203", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:19" + }, + { + "label": "staking", + "offset": 0, + "slot": "204", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:20" + }, + { + "label": "minimumStake", + "offset": 0, + "slot": "205", + "type": "t_uint256", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:21" + }, + { + "label": "admin", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:22" + }, + { + "label": "kvstore", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:23" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "EscrowFactory", + "src": "contracts/EscrowFactory.sol:195" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/packages/core/contracts/Escrow.sol b/packages/core/contracts/Escrow.sol index 0025f402a2..d4ab130a6f 100644 --- a/packages/core/contracts/Escrow.sol +++ b/packages/core/contracts/Escrow.sol @@ -8,6 +8,13 @@ import '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import './interfaces/IEscrow.sol'; +interface IKVStore { + function get( + address _account, + string memory _key + ) external view returns (string memory); +} + struct Fees { uint256 reputation; uint256 recording; @@ -23,6 +30,8 @@ contract Escrow is IEscrow, ReentrancyGuard { using SafeERC20 for IERC20; string private constant ERROR_ZERO_ADDRESS = 'Zero address'; + string private constant FEE_KEY = 'fee'; + uint8 private constant MAX_ORACLE_FEE = 25; uint32 private constant BULK_MAX_COUNT = 100; event IntermediateStorage(string url, string hash); @@ -34,6 +43,16 @@ contract Escrow is IEscrow, ReentrancyGuard { address recordingOracle, address exchangeOracle ); + event PendingV3( + string manifest, + string hash, + address reputationOracle, + address recordingOracle, + address exchangeOracle, + uint8 reputationOracleFeePercentage, + uint8 recordingOracleFeePercentage, + uint8 exchangeOracleFeePercentage + ); event BulkTransfer( uint256 indexed txId, address[] recipients, @@ -72,12 +91,13 @@ contract Escrow is IEscrow, ReentrancyGuard { address public immutable admin; address public immutable escrowFactory; address public immutable token; + address public immutable kvstore; uint8 public reputationOracleFeePercentage; uint8 public recordingOracleFeePercentage; uint8 public exchangeOracleFeePercentage; - string public manifestUrl; + string public manifest; string public manifestHash; string public intermediateResultsUrl; string public intermediateResultsHash; @@ -94,21 +114,25 @@ contract Escrow is IEscrow, ReentrancyGuard { * @param _launcher Creator of the escrow. * @param _admin Admin address for the escrow. * @param _duration Escrow lifetime (seconds). + * @param _kvstore KVStore contract address. */ constructor( address _token, address _launcher, address _admin, - uint256 _duration + uint256 _duration, + address _kvstore ) { require(_launcher != address(0), ERROR_ZERO_ADDRESS); require(_admin != address(0), ERROR_ZERO_ADDRESS); require(_token != address(0), ERROR_ZERO_ADDRESS); + require(_kvstore != address(0), ERROR_ZERO_ADDRESS); require(_duration > 0, 'Duration is 0'); token = _token; launcher = _launcher; admin = _admin; + kvstore = _kvstore; escrowFactory = msg.sender; status = EscrowStatuses.Launched; @@ -135,30 +159,24 @@ contract Escrow is IEscrow, ReentrancyGuard { * @param _reputationOracle Address of the reputation oracle. * @param _recordingOracle Address of the recording oracle. * @param _exchangeOracle Address of the exchange oracle. - * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle. - * @param _recordingOracleFeePercentage Fee percentage for the recording oracle. - * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle. - * @param _url URL for the escrow manifest. - * @param _hash Hash of the escrow manifest. + * @param _manifest Manifest or URL for the escrow manifest. + * @param _manifestHash Hash of the escrow manifest. */ function setup( address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, - string calldata _url, - string calldata _hash + string calldata _manifest, + string calldata _manifestHash ) external override adminLauncherOrFactory notExpired { require(_reputationOracle != address(0), 'Invalid reputation oracle'); require(_recordingOracle != address(0), 'Invalid recording oracle'); require(_exchangeOracle != address(0), 'Invalid exchange oracle'); - uint256 totalFeePercentage = _reputationOracleFeePercentage + - _recordingOracleFeePercentage + - _exchangeOracleFeePercentage; - require(totalFeePercentage <= 100, 'Percentage out of bounds'); + uint8 _reputationOracleFeePercentage = _getOracleFee(_reputationOracle); + uint8 _recordingOracleFeePercentage = _getOracleFee(_recordingOracle); + uint8 _exchangeOracleFeePercentage = _getOracleFee(_exchangeOracle); + require(status == EscrowStatuses.Launched, 'Wrong status'); reputationOracle = _reputationOracle; @@ -169,23 +187,45 @@ contract Escrow is IEscrow, ReentrancyGuard { recordingOracleFeePercentage = _recordingOracleFeePercentage; exchangeOracleFeePercentage = _exchangeOracleFeePercentage; - manifestUrl = _url; - manifestHash = _hash; + manifest = _manifest; + manifestHash = _manifestHash; status = EscrowStatuses.Pending; remainingFunds = getBalance(); require(remainingFunds > 0, 'Zero balance'); - emit PendingV2( - _url, - _hash, + emit PendingV3( + _manifest, + _manifestHash, _reputationOracle, _recordingOracle, - _exchangeOracle + _exchangeOracle, + _reputationOracleFeePercentage, + _recordingOracleFeePercentage, + _exchangeOracleFeePercentage ); emit Fund(remainingFunds); } + function _getOracleFee(address _oracle) private view returns (uint8) { + return _parseUint8(IKVStore(kvstore).get(_oracle, FEE_KEY)); + } + + function _parseUint8(string memory _value) private pure returns (uint8) { + bytes memory valueBytes = bytes(_value); + require(valueBytes.length > 0, 'Invalid oracle fee'); + + uint256 parsedValue = 0; + for (uint256 i = 0; i < valueBytes.length; i++) { + uint8 c = uint8(valueBytes[i]); + require(c >= 48 && c <= 57, 'Invalid oracle fee'); + parsedValue = parsedValue * 10 + (c - 48); + require(parsedValue <= MAX_ORACLE_FEE, 'Percentage out of bounds'); + } + + return uint8(parsedValue); + } + /** * @dev Initiates a cancellation request. If expired, it finalizes immediately. */ diff --git a/packages/core/contracts/EscrowFactory.sol b/packages/core/contracts/EscrowFactory.sol index 81341b07bb..edebf52d97 100644 --- a/packages/core/contracts/EscrowFactory.sol +++ b/packages/core/contracts/EscrowFactory.sol @@ -20,6 +20,7 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { address public staking; uint256 public minimumStake; address public admin; + address public kvstore; using SafeERC20 for IERC20; @@ -27,6 +28,7 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { event LaunchedV2(address token, address escrow, string jobRequesterId); event SetStakingAddress(address indexed stakingAddress); event SetMinumumStake(uint256 indexed minimumStake); + event SetKVStoreAddress(address indexed kvStoreAddress); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -35,10 +37,12 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { function initialize( address _staking, - uint256 _minimumStake + uint256 _minimumStake, + address _kvstore ) external payable virtual initializer { __Ownable_init_unchained(); require(_staking != address(0), ERROR_ZERO_ADDRESS); + _setKVStoreAddress(_kvstore); _setStakingAddress(_staking); _setMinimumStake(_minimumStake); _setEscrowAdmin(msg.sender); @@ -58,7 +62,8 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { _token, msg.sender, admin, - STANDARD_DURATION + STANDARD_DURATION, + kvstore ); counter++; escrowCounters[address(escrow)] = counter; @@ -92,9 +97,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { * @param _reputationOracle Address of the reputation oracle. * @param _recordingOracle Address of the recording oracle. * @param _exchangeOracle Address of the exchange oracle. - * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle. - * @param _recordingOracleFeePercentage Fee percentage for the recording oracle. - * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle. * @param _url URL for the escrow manifest. * @param _hash Hash of the escrow manifest. * @return The address of the newly created Escrow contract. @@ -106,9 +108,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, string calldata _url, string calldata _hash ) external returns (address) { @@ -124,9 +123,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { _reputationOracle, _recordingOracle, _exchangeOracle, - _reputationOracleFeePercentage, - _recordingOracleFeePercentage, - _exchangeOracleFeePercentage, _url, _hash ); @@ -179,6 +175,16 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { admin = _admin; } + function setKVStoreAddress(address _kvstore) external onlyOwner { + _setKVStoreAddress(_kvstore); + } + + function _setKVStoreAddress(address _kvstore) private { + require(_kvstore != address(0), ERROR_ZERO_ADDRESS); + kvstore = _kvstore; + emit SetKVStoreAddress(_kvstore); + } + function _authorizeUpgrade(address) internal override onlyOwner {} /** @@ -186,5 +192,5 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/packages/core/contracts/interfaces/IEscrow.sol b/packages/core/contracts/interfaces/IEscrow.sol index 33d61c429c..c7cdbf9dcb 100644 --- a/packages/core/contracts/interfaces/IEscrow.sol +++ b/packages/core/contracts/interfaces/IEscrow.sol @@ -18,11 +18,8 @@ interface IEscrow { address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, - string calldata _url, - string calldata _hash + string calldata _manifest, + string calldata _manifestHash ) external; function requestCancellation() external; diff --git a/packages/core/contracts/test/EscrowFactoryV0.sol b/packages/core/contracts/test/EscrowFactoryV0.sol index 4d474639b6..193a791c41 100644 --- a/packages/core/contracts/test/EscrowFactoryV0.sol +++ b/packages/core/contracts/test/EscrowFactoryV0.sol @@ -12,6 +12,7 @@ contract EscrowFactoryV0 is OwnableUpgradeable, UUPSUpgradeable { // all Escrows will have this duration. uint256 constant STANDARD_DURATION = 8640000; string constant ERROR_ZERO_ADDRESS = 'EscrowFactory: Zero Address'; + address constant DUMMY_KVSTORE = address(1); uint256 public counter; mapping(address => uint256) public escrowCounters; @@ -64,8 +65,9 @@ contract EscrowFactoryV0 is OwnableUpgradeable, UUPSUpgradeable { Escrow escrow = new Escrow( token, msg.sender, - payable(msg.sender), - STANDARD_DURATION + msg.sender, + STANDARD_DURATION, + DUMMY_KVSTORE ); counter++; escrowCounters[address(escrow)] = counter; diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs index 35f4c1108c..77a2c72261 100644 --- a/packages/core/eslint.config.mjs +++ b/packages/core/eslint.config.mjs @@ -26,6 +26,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', 'prefer-const': 'warn', 'no-extra-semi': 'off', diff --git a/packages/core/package.json b/packages/core/package.json index d23371ccf0..908f7cdc6a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -80,13 +80,14 @@ "hardhat-dependency-compiler": "^1.2.1", "hardhat-gas-reporter": "^2.0.2", "openpgp": "6.2.2", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "prettier-plugin-solidity": "^1.3.1", "solidity-coverage": "^0.8.17", "tenderly": "^0.9.1", "ts-node": "^10.9.2", "typechain": "^8.3.2", "typescript": "^5.8.3", + "typescript-eslint": "^8.39.1", "xdeployer": "3.1.6" }, "peerDependencies": { diff --git a/packages/core/scripts/deploy-proxies.ts b/packages/core/scripts/deploy-proxies.ts index a5510394d2..08e6a8bb61 100644 --- a/packages/core/scripts/deploy-proxies.ts +++ b/packages/core/scripts/deploy-proxies.ts @@ -2,24 +2,24 @@ import { ethers, upgrades } from 'hardhat'; async function main() { - const hmtAddress = process.env.HMT_ADDRESS; - if (!hmtAddress) { - console.error('HMT_ADDRESS env variable missing'); - return; - } - const stakingAddress = process.env.STAKING_ADDRESS; if (!stakingAddress) { console.error('STAKING_ADDRESS env variable missing'); return; } + const kvStoreAddress = process.env.KVSTORE_ADDRESS; + if (!kvStoreAddress) { + console.error('KVSTORE_ADDRESS env variable missing'); + return; + } + const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); const escrowFactoryContract = await upgrades.deployProxy( EscrowFactory, - [stakingAddress, 1], + [stakingAddress, 1, kvStoreAddress], { initializer: 'initialize', kind: 'uups' } ); await escrowFactoryContract.waitForDeployment(); @@ -33,11 +33,6 @@ async function main() { await escrowFactoryContract.getAddress() ) ); - - const KVStore = await ethers.getContractFactory('KVStore'); - const kvStoreContract = await KVStore.deploy(); - - console.log('KVStore Address: ', await kvStoreContract.getAddress()); } main().catch((error) => { diff --git a/packages/core/scripts/deploy.ts b/packages/core/scripts/deploy.ts index 7ef9c22a5c..3cfa410f35 100644 --- a/packages/core/scripts/deploy.ts +++ b/packages/core/scripts/deploy.ts @@ -23,12 +23,18 @@ async function main() { await stakingContract.waitForDeployment(); console.log('Staking Address: ', await stakingContract.getAddress()); + const KVStore = await ethers.getContractFactory('KVStore'); + const kvStoreContract = await KVStore.deploy(); + await kvStoreContract.waitForDeployment(); + const kvStoreAddress = await kvStoreContract.getAddress(); + console.log('KVStore Address: ', kvStoreAddress); + const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); const escrowFactoryContract = await upgrades.deployProxy( EscrowFactory, - [await stakingContract.getAddress(), 1], + [await stakingContract.getAddress(), 1, kvStoreAddress], { initializer: 'initialize', kind: 'uups' } ); await escrowFactoryContract.waitForDeployment(); @@ -43,12 +49,6 @@ async function main() { ) ); - const KVStore = await ethers.getContractFactory('KVStore'); - const kvStoreContract = await KVStore.deploy(); - await kvStoreContract.waitForDeployment(); - - console.log('KVStore Address: ', await kvStoreContract.getAddress()); - for (const account of accounts) { await (HMTokenContract as HMToken).transfer( account.address, diff --git a/packages/core/scripts/upgrade-proxies.ts b/packages/core/scripts/upgrade-proxies.ts index bd1cdbe41d..8e83dacdd6 100644 --- a/packages/core/scripts/upgrade-proxies.ts +++ b/packages/core/scripts/upgrade-proxies.ts @@ -1,16 +1,17 @@ /* eslint-disable no-console */ import { ethers, upgrades } from 'hardhat'; +import type { EscrowFactory } from '../typechain-types'; async function main() { const escrowFactoryAddress = process.env.ESCROW_FACTORY_ADDRESS; - const deployEscrowFactory = process.env.DEPLOY_ESCROW_FACTORY; + const kvStoreAddress = process.env.KVSTORE_ADDRESS; if (!escrowFactoryAddress) { console.error('Env variable missing'); return; } - if (deployEscrowFactory == 'true' && escrowFactoryAddress) { + if (escrowFactoryAddress) { const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); @@ -25,16 +26,38 @@ async function main() { await ethers.provider.getTransactionReceipt(hash); } + const escrowFactory = (await ethers.getContractAt( + 'contracts/EscrowFactory.sol:EscrowFactory', + await escrowFactoryContract.getAddress() + )) as unknown as EscrowFactory; + console.log( 'Escrow Factory Proxy Address: ', - await escrowFactoryContract.getAddress() + await escrowFactory.getAddress() ); console.log( 'New Escrow Factory Implementation Address: ', await upgrades.erc1967.getImplementationAddress( - await escrowFactoryContract.getAddress() + await escrowFactory.getAddress() ) ); + + const currentKvStoreAddress = await escrowFactory.kvstore(); + if (currentKvStoreAddress === ethers.ZeroAddress) { + if (!kvStoreAddress) { + console.error( + 'KVSTORE_ADDRESS env variable missing and factory kvstore is not set' + ); + return; + } + + const setKvStoreTx = + await escrowFactory.setKVStoreAddress(kvStoreAddress); + await setKvStoreTx.wait(); + console.log('KVStore Address initialized to: ', kvStoreAddress); + } else { + console.log('KVStore Address already set: ', currentKvStoreAddress); + } } } diff --git a/packages/core/test/Escrow.ts b/packages/core/test/Escrow.ts index 1a4cbd9eb2..ff6f2c3af5 100644 --- a/packages/core/test/Escrow.ts +++ b/packages/core/test/Escrow.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { Signer, ZeroAddress } from 'ethers'; -import { Escrow, HMToken } from '../typechain-types'; +import { Escrow, HMToken, KVStore } from '../typechain-types'; import { faker } from '@faker-js/faker'; const BULK_MAX_COUNT = 100; @@ -41,8 +41,10 @@ let adminAddress: string; let token: HMToken; let token2: HMToken; let escrow: Escrow; +let kvStore: KVStore; let tokenAddress: string; let tokenAddress2: string; +let kvStoreAddress: string; async function deployEscrow( tokenAddr: string = tokenAddress, @@ -55,7 +57,8 @@ async function deployEscrow( tokenAddr, launcherAddr, adminAddr, - duration + duration, + kvStoreAddress )) as Escrow; } @@ -69,9 +72,6 @@ async function fundEscrow( } async function setupEscrow( - repFee = 3, - recFee = 3, - excFee = 3, url: string = FIXTURE_URL, hash: string = FIXTURE_HASH ) { @@ -81,9 +81,6 @@ async function setupEscrow( reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - repFee, - recFee, - excFee, url, hash ); @@ -133,6 +130,14 @@ describe('Escrow', function () { tokenAddress = await token.getAddress(); token2 = (await HMToken.deploy(1000000000, 'Token2', 18, 'TK2')) as HMToken; tokenAddress2 = await token2.getAddress(); + + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; + kvStoreAddress = await kvStore.getAddress(); + + await kvStore.connect(reputationOracle).set('fee', '3'); + await kvStore.connect(recordingOracle).set('fee', '3'); + await kvStore.connect(exchangeOracle).set('fee', '3'); }); describe('deployment', () => { @@ -192,9 +197,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -206,9 +208,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -220,9 +219,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -234,9 +230,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -251,9 +244,6 @@ describe('Escrow', function () { ethers.ZeroAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -268,9 +258,6 @@ describe('Escrow', function () { reputationOracleAddress, ethers.ZeroAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -285,16 +272,16 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, ethers.ZeroAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) ).to.be.revertedWith('Invalid exchange oracle'); }); - it('reverts when total fee > 100', async () => { + it('reverts when an oracle fee > 25', async () => { + await kvStore.connect(reputationOracle).set('fee', '50'); + await kvStore.connect(recordingOracle).set('fee', '20'); + await kvStore.connect(exchangeOracle).set('fee', '20'); await expect( escrow .connect(launcher) @@ -302,13 +289,13 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 60, - 30, - 20, FIXTURE_URL, FIXTURE_HASH ) ).to.be.revertedWith('Percentage out of bounds'); + await kvStore.connect(reputationOracle).set('fee', '3'); + await kvStore.connect(recordingOracle).set('fee', '3'); + await kvStore.connect(exchangeOracle).set('fee', '3'); }); }); describe('succeeds', () => { @@ -321,26 +308,26 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 5, - 5, - 5, FIXTURE_URL, FIXTURE_HASH ) ) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( FIXTURE_URL, FIXTURE_HASH, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 3, + 3, + 3 ) .to.emit(escrow, 'Fund') .withArgs(amount); expect(await escrow.status()).to.equal(Status.Pending); - expect(await escrow.manifestUrl()).to.equal(FIXTURE_URL); + expect(await escrow.manifest()).to.equal(FIXTURE_URL); expect(await escrow.manifestHash()).to.equal(FIXTURE_HASH); }); @@ -353,26 +340,26 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 5, - 5, - 5, FIXTURE_URL, FIXTURE_HASH ) ) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( FIXTURE_URL, FIXTURE_HASH, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 3, + 3, + 3 ) .to.emit(escrow, 'Fund') .withArgs(amount); expect(await escrow.status()).to.equal(Status.Pending); - expect(await escrow.manifestUrl()).to.equal(FIXTURE_URL); + expect(await escrow.manifest()).to.equal(FIXTURE_URL); expect(await escrow.manifestHash()).to.equal(FIXTURE_HASH); }); }); diff --git a/packages/core/test/EscrowFactory.ts b/packages/core/test/EscrowFactory.ts index ab4941afb8..e7275670f8 100644 --- a/packages/core/test/EscrowFactory.ts +++ b/packages/core/test/EscrowFactory.ts @@ -2,7 +2,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { expect } from 'chai'; import { EventLog, Signer, ZeroAddress } from 'ethers'; import { ethers, upgrades } from 'hardhat'; -import { EscrowFactory, HMToken, Staking } from '../typechain-types'; +import { EscrowFactory, HMToken, KVStore, Staking } from '../typechain-types'; import { faker } from '@faker-js/faker'; let owner: Signer, @@ -16,8 +16,11 @@ let exchangeOracleAddress: string, recordingOracleAddress: string, reputationOracleAddress: string; -let token: HMToken, escrowFactory: EscrowFactory, staking: Staking; -let stakingAddress: string, tokenAddress: string; +let token: HMToken, + escrowFactory: EscrowFactory, + staking: Staking, + kvStore: KVStore; +let stakingAddress: string, tokenAddress: string, kvStoreAddress: string; const MINIMUM_STAKE = ethers.parseEther('10'); const LOCK_PERIOD = 2; @@ -77,11 +80,19 @@ describe('EscrowFactory', function () { 'contracts/EscrowFactory.sol:EscrowFactory' ); + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; + kvStoreAddress = await kvStore.getAddress(); + escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [await staking.getAddress(), MINIMUM_STAKE], + [await staking.getAddress(), MINIMUM_STAKE, kvStoreAddress], { kind: 'uups', initializer: 'initialize' } )) as unknown as EscrowFactory; + + await kvStore.connect(reputationOracle).set('fee', '5'); + await kvStore.connect(recordingOracle).set('fee', '5'); + await kvStore.connect(exchangeOracle).set('fee', '5'); }); describe('deployment', () => { @@ -92,10 +103,14 @@ describe('EscrowFactory', function () { ); await expect( - upgrades.deployProxy(EscrowFactory, [ZeroAddress, MINIMUM_STAKE], { - kind: 'uups', - initializer: 'initialize', - }) as unknown as EscrowFactory + upgrades.deployProxy( + EscrowFactory, + [ZeroAddress, MINIMUM_STAKE, kvStoreAddress], + { + kind: 'uups', + initializer: 'initialize', + } + ) as unknown as EscrowFactory ).revertedWith('Zero Address'); }); }); @@ -108,7 +123,7 @@ describe('EscrowFactory', function () { const escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [stakingAddress, MINIMUM_STAKE], + [stakingAddress, MINIMUM_STAKE, kvStoreAddress], { kind: 'uups', initializer: 'initialize', @@ -252,7 +267,6 @@ describe('EscrowFactory', function () { }); describe('createFundAndSetupEscrow()', () => { - const fee = faker.number.int({ min: 1, max: 5 }); const manifestUrl = faker.internet.url(); const manifestHash = faker.string.alphanumeric(46); const fundAmount = ethers.parseEther( @@ -270,9 +284,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -290,9 +301,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -315,9 +323,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -342,9 +347,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ); @@ -367,13 +369,16 @@ describe('EscrowFactory', function () { await expect(tx) .to.emit(escrowFactory, 'LaunchedV2') .withArgs(tokenAddress, escrowAddress, FIXTURE_REQUESTER_ID) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( manifestUrl, manifestHash, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 5, + 5, + 5 ) .to.emit(escrow, 'Fund') .withArgs(fundAmount); diff --git a/packages/core/test/Staking.ts b/packages/core/test/Staking.ts index a012a69d7d..18ea049ffc 100644 --- a/packages/core/test/Staking.ts +++ b/packages/core/test/Staking.ts @@ -2,7 +2,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { expect } from 'chai'; import { ethers, upgrades } from 'hardhat'; import { EventLog, Signer } from 'ethers'; -import { EscrowFactory, HMToken, Staking } from '../typechain-types'; +import { EscrowFactory, HMToken, KVStore, Staking } from '../typechain-types'; const mineNBlocks = async (n: number) => { await Promise.all( @@ -30,6 +30,7 @@ describe('Staking', function () { recordingOracle: Signer; let token: HMToken, escrowFactory: EscrowFactory, staking: Staking; + let kvStore: KVStore; this.beforeAll(async () => { [ @@ -91,10 +92,12 @@ describe('Staking', function () { const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [await staking.getAddress(), minimumStake], + [await staking.getAddress(), minimumStake, await kvStore.getAddress()], { kind: 'uups', initializer: 'initialize' } )) as unknown as EscrowFactory; diff --git a/packages/examples/gcv/eslint.config.mjs b/packages/examples/gcv/eslint.config.mjs index df1b4ffb4b..c127f82b7f 100644 --- a/packages/examples/gcv/eslint.config.mjs +++ b/packages/examples/gcv/eslint.config.mjs @@ -30,6 +30,8 @@ const config = tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/packages/libs/logger/eslint.config.mjs b/packages/libs/logger/eslint.config.mjs index 3dda18fe0c..0e913843d1 100644 --- a/packages/libs/logger/eslint.config.mjs +++ b/packages/libs/logger/eslint.config.mjs @@ -33,6 +33,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn', diff --git a/packages/libs/logger/package.json b/packages/libs/logger/package.json index ca74a9a0ff..89e1b97f86 100644 --- a/packages/libs/logger/package.json +++ b/packages/libs/logger/package.json @@ -20,7 +20,7 @@ "pino-pretty": "^13.1.3" }, "devDependencies": { - "@eslint/js": "^9.30.1", + "@eslint/js": "^10.0.1", "@types/node": "^22.10.5", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.5", @@ -28,7 +28,7 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.5", "globals": "^16.3.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "ts-node": "^10.9.2", "typescript": "^5.8.3", "typescript-eslint": "^8.35.1" diff --git a/packages/sdk/python/human-protocol-sdk/Makefile b/packages/sdk/python/human-protocol-sdk/Makefile index 89c7f9141d..2af2032ef9 100644 --- a/packages/sdk/python/human-protocol-sdk/Makefile +++ b/packages/sdk/python/human-protocol-sdk/Makefile @@ -11,10 +11,10 @@ format: pipenv run black . unit-test: - make build-contracts ./scripts/run-unit-test.sh run-test: + make build-contracts make unit-test build-package: diff --git a/packages/sdk/python/human-protocol-sdk/README.md b/packages/sdk/python/human-protocol-sdk/README.md index ecd654d90d..141ed461fa 100644 --- a/packages/sdk/python/human-protocol-sdk/README.md +++ b/packages/sdk/python/human-protocol-sdk/README.md @@ -129,10 +129,7 @@ escrow_config = EscrowConfig( recording_oracle_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", reputation_oracle_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", exchange_oracle_address = "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", - recording_oracle_fee = 10, - reputation_oracle_fee = 10, - exchange_oracle_fee = 10, - manifest_url = "http://localhost:9000/manifests/manifest.json", + manifest = "http://localhost:9000/manifests/manifest.json", hash = "s3ca3basd132bafcdas234243.json" ) ``` diff --git a/packages/sdk/python/human-protocol-sdk/docs/index.md b/packages/sdk/python/human-protocol-sdk/docs/index.md index 54585e09c3..6ee7e810c2 100644 --- a/packages/sdk/python/human-protocol-sdk/docs/index.md +++ b/packages/sdk/python/human-protocol-sdk/docs/index.md @@ -117,9 +117,6 @@ config = EscrowConfig( recording_oracle_address="0x...", reputation_oracle_address="0x...", exchange_oracle_address="0x...", - recording_oracle_fee=10, - reputation_oracle_fee=10, - exchange_oracle_fee=10, manifest="https://example.com/manifest.json", hash="manifest_hash", ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py index a059cc86d4..2324426ef7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py @@ -54,10 +54,10 @@ class OperatorCategory(Enum): "title": "Ethereum", "scan_url": "https://etherscan.io", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/ethereum/version/latest" + "https://api.studio.thegraph.com/query/74256/human-ethereum/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmNhLQEfBJQ46fngBh4YCttk8kNkveFy5uvAeUmyAdX1kD" + "https://gateway.thegraph.com/api/deployments/id/QmQZ3yL1FzydDwaB56ozgTiBESciTNFsMLyTBfHXzNd1gg" ), "hmt_address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867", "factory_address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", @@ -65,15 +65,17 @@ class OperatorCategory(Enum): "kvstore_address": "0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D", "old_subgraph_url": "", "old_factory_address": "", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/human-ethereum/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmdcTW7XhgULq5yJZGA65mjTwQogdyzLJaZ69ttDJz5JeE", }, ChainId.SEPOLIA: { "title": "Sepolia", "scan_url": "https://sepolia.etherscan.io", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/sepolia/version/latest" + "https://api.studio.thegraph.com/query/74256/human-sepolia/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmQghdr7hxqrjFde8DN15TzfrJLCfwvzmUH9RzWwH1mKzk" + "https://gateway.thegraph.com/api/deployments/id/QmQEbyGSf8VG9pdskowsK6C977wkcyJSxw9q6EDctKEkUw" ), "hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", "factory_address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf", @@ -81,15 +83,17 @@ class OperatorCategory(Enum): "kvstore_address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", "old_subgraph_url": (""), "old_factory_address": "0x98108c28B7767a52BE38B4860832dd4e11A7ecad", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/hmt-stats-sepolia/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmYkPnYxbZ5ZTtKz7PTyxh6x5sK2H6yzx6vXDCrErpa9gB", }, ChainId.BSC_MAINNET: { "title": "Binance Smart Chain", "scan_url": "https://bscscan.com", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/bsc/version/latest" + "https://api.studio.thegraph.com/query/74256/human-bsc/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmTioC9Z1HzKSCnEKL3BP9iHqbgZt1ceLU2VE4Mv6sxNkd" + "https://gateway.thegraph.com/api/deployments/id/QmV3nZUM51NGt7F8RpZP9GxKPpKox3F24iJ8H1ekPb6ha4" ), "hmt_address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527", "factory_address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714", @@ -97,15 +101,17 @@ class OperatorCategory(Enum): "kvstore_address": "0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1", "old_subgraph_url": "https://api.thegraph.com/subgraphs/name/humanprotocol/bsc", "old_factory_address": "0xc88bC422cAAb2ac8812de03176402dbcA09533f4", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/hmt-stats-bsc/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmUnHgNShyv45S2dQ21W9jLvRA8xWT6XfeuP25fXJoNoEa", }, ChainId.BSC_TESTNET: { "title": "Binance Smart Chain (Testnet)", "scan_url": "https://testnet.bscscan.com", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/bsc-testnet/version/latest" + "https://api.studio.thegraph.com/query/74256/human-bsc-testnet/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmPyUYRjAvzDdeenXMGHcCRD2v4qwZbKMEkVkY3Jq6VLwn" + "https://gateway.thegraph.com/api/deployments/id/QmdQJog9vMghK2o39U9YJL8vpU9VZnkJa7jGg7wnsod7qV" ), "hmt_address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d", "factory_address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18", @@ -115,15 +121,17 @@ class OperatorCategory(Enum): "https://api.thegraph.com/subgraphs/name/humanprotocol/bsctest" ), "old_factory_address": "0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/hmt-stats-bsc-testnet/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmPMRbrXKp7a1i5x27dzBHbomxqKYDg5eU3djv1DXB5heq", }, ChainId.POLYGON: { "title": "Polygon", "scan_url": "https://polygonscan.com", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/polygon/version/latest" + "https://api.studio.thegraph.com/query/74256/human-polygon/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmQNkWNE5FPtqbtSkdtR6AEBJz9N7WDz1X7pfvQqYAcUZJ" + "https://gateway.thegraph.com/api/deployments/id/QmSu7B48kgGgMgXaD6Yb4fXjbnsJNmobKAU9qZgescZXid" ), "hmt_address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571BF", "factory_address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB", @@ -133,15 +141,17 @@ class OperatorCategory(Enum): "https://api.thegraph.com/subgraphs/name/humanprotocol/polygon" ), "old_factory_address": "0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/hmt-stats-polygon/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmcTTTq2reYDBP5j5P2eToc5FmYbJ5ZSMhPaRB6NLriNjR", }, ChainId.POLYGON_AMOY: { "title": "Polygon Amoy", "scan_url": "https://amoy.polygonscan.com", "subgraph_url": ( - "https://api.studio.thegraph.com/query/74256/amoy/version/latest" + "https://api.studio.thegraph.com/query/74256/human-amoy/version/latest" ), "subgraph_url_api_key": ( - "https://gateway.thegraph.com/api/deployments/id/QmcLwLMw3UzCSbNbjegrpNu6PB3kAd67xquuyaVWvc5Q7Q" + "https://gateway.thegraph.com/api/deployments/id/QmRx5WSi7o9FtENWnE5eEz8Dr7kYUazk4TNPRt65UZkKkB" ), "hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", "factory_address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", @@ -149,6 +159,8 @@ class OperatorCategory(Enum): "kvstore_address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", "old_subgraph_url": "", "old_factory_address": "", + "hmt_subgraph_url": "https://api.studio.thegraph.com/query/74256/hmt-stats-amoy/version/latest", + "hmt_subgraph_url_api_key": "https://gateway.thegraph.com/api/deployments/id/QmZze6UWMJna8dQ183TVounSfGwc9C36RgQCGWhFzgrW3y", }, ChainId.LOCALHOST: { "title": "Localhost", diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py index 188816998e..31cc4ad29c 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py @@ -53,6 +53,7 @@ def get_w3_with_priv_key(priv_key: str): ) from human_protocol_sdk.utils import ( TransactionOptions, + get_error_message, get_escrow_interface, get_factory_interface, get_erc20_interface, @@ -111,9 +112,6 @@ class EscrowConfig: recording_oracle_address (str): Address of the recording oracle. reputation_oracle_address (str): Address of the reputation oracle. exchange_oracle_address (str): Address of the exchange oracle. - recording_oracle_fee (int): Recording oracle fee percentage (0-100). - reputation_oracle_fee (int): Reputation oracle fee percentage (0-100). - exchange_oracle_fee (int): Exchange oracle fee percentage (0-100). manifest (str): Manifest payload (URL or JSON string). hash (str): Manifest file hash. """ @@ -123,16 +121,12 @@ def __init__( recording_oracle_address: str, reputation_oracle_address: str, exchange_oracle_address: str, - recording_oracle_fee: int, - reputation_oracle_fee: int, - exchange_oracle_fee: int, - manifest: str, - hash: str, + manifest: str = "", + hash: str = "", ): """ Raises: - EscrowClientError: If addresses are invalid, fees are out of range, - total fees exceed 100%, or manifest data is invalid. + EscrowClientError: If addresses are invalid or manifest data is invalid. """ if not Web3.is_address(recording_oracle_address): raise EscrowClientError( @@ -147,14 +141,6 @@ def __init__( f"Invalid exchange oracle address: {exchange_oracle_address}" ) - if ( - not (0 <= recording_oracle_fee <= 100) - or not (0 <= reputation_oracle_fee <= 100) - or not (0 <= exchange_oracle_fee <= 100) - ): - raise EscrowClientError("Fee must be between 0 and 100") - if recording_oracle_fee + reputation_oracle_fee + exchange_oracle_fee > 100: - raise EscrowClientError("Total fee must be less than 100") if not validate_url(manifest) and not validate_json(manifest): raise EscrowClientError("Invalid empty manifest") if not hash: @@ -163,9 +149,6 @@ def __init__( self.recording_oracle_address = recording_oracle_address self.reputation_oracle_address = reputation_oracle_address self.exchange_oracle_address = exchange_oracle_address - self.recording_oracle_fee = recording_oracle_fee - self.reputation_oracle_fee = reputation_oracle_fee - self.exchange_oracle_fee = exchange_oracle_fee self.manifest = manifest self.hash = hash @@ -289,7 +272,7 @@ def create_fund_and_setup_escrow( amount (int): Token amount to fund (in token's smallest unit). job_requester_id (str): Off-chain identifier for the job requester. escrow_config (EscrowConfig): Escrow configuration parameters including - oracle addresses, fees, and manifest data. + oracle addresses and manifest data. tx_options (Optional[TransactionOptions]): Optional transaction parameters such as gas limit. Returns: @@ -321,9 +304,6 @@ def create_fund_and_setup_escrow( escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ), @@ -349,14 +329,14 @@ def setup( escrow_config: EscrowConfig, tx_options: Optional[TransactionOptions] = None, ) -> None: - """Set escrow roles, fees, and manifest metadata. + """Set escrow roles and manifest metadata. - Configures the escrow with oracle addresses, fee percentages, and manifest information. + Configures the escrow with oracle addresses and manifest information. Args: escrow_address (str): Address of the escrow contract to configure. escrow_config (EscrowConfig): Escrow configuration parameters including - oracle addresses, fees, and manifest data. + oracle addresses and manifest data. tx_options (Optional[TransactionOptions]): Optional transaction parameters such as gas limit. Returns: @@ -380,9 +360,6 @@ def setup( escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ), @@ -1031,7 +1008,20 @@ def get_manifest(self, escrow_address: str) -> str: if not Web3.is_address(escrow_address): raise EscrowClientError(f"Invalid escrow address: {escrow_address}") - return self._get_escrow_contract(escrow_address).functions.manifestUrl().call() + escrow_contract = self._get_escrow_contract(escrow_address) + + try: + return escrow_contract.functions.manifest().call() + except Exception as manifest_error: + # Backward compatibility with legacy escrows exposing manifestUrl(). + try: + return escrow_contract.functions.manifestUrl().call() + except Exception as fallback_error: + raise EscrowClientError( + "Failed to fetch manifest using both manifest() and manifestUrl(). " + f"manifest() error: {get_error_message(manifest_error)}. " + f"manifestUrl() error: {get_error_message(fallback_error)}." + ) from fallback_error def get_results_url(self, escrow_address: str) -> str: """Get the final results file URL. diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py index 223b88aafb..6d5ddf36db 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py @@ -70,4 +70,6 @@ def get_cancellation_refund_by_escrow_query(): }} }} {cancellation_refund_fragment} -""".format(cancellation_refund_fragment=cancellation_refund_fragment) +""".format( + cancellation_refund_fragment=cancellation_refund_fragment + ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py index 5d1b01f6e6..38a0ca8919 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py @@ -97,7 +97,9 @@ def get_escrow_query(): }} }} {escrow_fragment} -""".format(escrow_fragment=escrow_fragment) +""".format( + escrow_fragment=escrow_fragment + ) def get_status_query( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py index 09c06278ae..3f43bbff9e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py @@ -14,4 +14,6 @@ }} }} {reward_added_event_fragment} -""".format(reward_added_event_fragment=reward_added_event_fragment) +""".format( + reward_added_event_fragment=reward_added_event_fragment +) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py index e726402117..988914d436 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py @@ -41,7 +41,12 @@ dailyEscrowCount dailyWorkerCount dailyPayoutCount - dailyHMTPayoutAmount +} +""" + +hmt_event_day_data_fragment = """ +fragment HMTEventDayDataFields on EventDayData { + timestamp dailyHMTTransferCount dailyHMTTransferAmount dailyUniqueSenders @@ -100,3 +105,33 @@ def get_event_day_data_query(filter: StatisticsFilter) -> str: from_clause="timestamp_gte: $from" if filter.date_from else "", to_clause="timestamp_lte: $to" if filter.date_to else "", ) + + +def get_hmt_event_day_data_query(filter: StatisticsFilter) -> str: + return """ +query GetHMTDayData( + $from: Int + $to: Int + $orderDirection: String + $first: Int + $skip: Int +) {{ + eventDayDatas( + where: {{ + {from_clause} + {to_clause} + }}, + orderBy: timestamp, + orderDirection: $orderDirection + first: $first + skip: $skip + ) {{ + ...HMTEventDayDataFields + }} +}} +{hmt_event_day_data_fragment} +""".format( + hmt_event_day_data_fragment=hmt_event_day_data_fragment, + from_clause="timestamp_gte: $from" if filter.date_from else "", + to_clause="timestamp_lte: $to" if filter.date_to else "", + ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py index f17cc61876..ef637424b2 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py @@ -103,4 +103,6 @@ def get_transaction_query() -> str: }} }} {transaction_fragment} -""".format(transaction_fragment=transaction_fragment) +""".format( + transaction_fragment=transaction_fragment + ) 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 index ee21af1ef7..9788272612 100644 --- 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 @@ -18,7 +18,9 @@ def get_worker_query() -> str: }} }} {worker_fragment} -""".format(worker_fragment=worker_fragment) +""".format( + worker_fragment=worker_fragment + ) def get_workers_query(filter: WorkerFilter) -> str: diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py index 2672136569..d27476c509 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py @@ -89,7 +89,7 @@ def get_kvstore_data( from human_protocol_sdk.gql.kvstore import get_kvstore_by_address_query if chain_id.value not in set(chain_id.value for chain_id in ChainId): - raise KVStoreClientError(f"Invalid ChainId") + raise KVStoreClientError("Invalid ChainId") if not Web3.is_address(address): raise KVStoreClientError(f"Invalid KVStore address: {address}") @@ -181,7 +181,7 @@ def get( or "kvstores" not in kvstore_data["data"] or len(kvstore_data["data"]["kvstores"]) == 0 ): - raise KVStoreClientError(f"Key '{key}' not found for address {address}") + return "" return kvstore_data["data"]["kvstores"][0]["value"] @@ -232,10 +232,13 @@ def get_file_url_and_verify_hash( raise KVStoreClientError(f"Invalid address: {address}") url = KVStoreUtils.get(chain_id, address, key, options=options) + if url == "": + raise KVStoreClientError("No URL found for the given address and key") + hash = KVStoreUtils.get(chain_id, address, key + "_hash", options=options) - if len(url) == 0: - return url + if hash == "": + raise KVStoreClientError("No hash found for the given address and url") content = requests.get(url).text content_hash = Web3.keccak(text=content).hex() diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py index 5d79cb567a..32c1c0ab04 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py @@ -127,22 +127,16 @@ class DailyPaymentData: Attributes: timestamp (datetime): Day boundary timestamp. - total_amount_paid (int): Total amount paid out on this day. total_count (int): Number of payment transactions. - average_amount_per_worker (int): Average payout amount per worker. """ def __init__( self, timestamp: datetime, - total_amount_paid: int, total_count: int, - average_amount_per_worker: int, ): self.timestamp = timestamp - self.total_amount_paid = total_amount_paid self.total_count = total_count - self.average_amount_per_worker = average_amount_per_worker class PaymentStatistics: @@ -446,8 +440,7 @@ def get_payment_statistics( ) ) for day_data in stats.daily_payments_data: - print(f"{day_data.timestamp}: {day_data.total_amount_paid} paid") - print(f" Average per worker: {day_data.average_amount_per_worker}") + print(f"{day_data.total_count}: {day_data.total_count} payments") ``` """ if chain_id.value not in [cid.value for cid in ChainId]: @@ -481,16 +474,7 @@ def get_payment_statistics( timestamp=datetime.fromtimestamp( int(event_day_data.get("timestamp", 0)) ), - total_amount_paid=int( - event_day_data.get("dailyHMTPayoutAmount", 0) - ), total_count=int(event_day_data.get("dailyPayoutCount", 0)), - average_amount_per_worker=( - int(event_day_data.get("dailyHMTPayoutAmount", 0)) - / int(event_day_data.get("dailyWorkerCount")) - if event_day_data.get("dailyWorkerCount", "0") != "0" - else 0 - ), ) for event_day_data in event_day_datas ], @@ -540,6 +524,7 @@ def get_hmt_statistics( network, query=get_hmtoken_statistics_query, options=options, + use_hmt_subgraph=True, ) hmtoken_statistics = hmtoken_statistics_data["data"]["hmtokenStatistics"] @@ -612,6 +597,7 @@ def get_hmt_holders( "orderDirection": param.order_direction, }, options=options, + use_hmt_subgraph=True, ) holders = holders_data["data"]["holders"] @@ -675,12 +661,12 @@ def get_hmt_daily_data( raise StatisticsUtilsError("Empty network configuration") from human_protocol_sdk.gql.statistics import ( - get_event_day_data_query, + get_hmt_event_day_data_query, ) event_day_datas_data = custom_gql_fetch( network, - query=get_event_day_data_query(filter), + query=get_hmt_event_day_data_query(filter), params={ "from": int(filter.date_from.timestamp()) if filter.date_from else None, "to": int(filter.date_to.timestamp()) if filter.date_to else None, @@ -689,6 +675,7 @@ def get_hmt_daily_data( "orderDirection": filter.order_direction.value, }, options=options, + use_hmt_subgraph=True, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index a8f7281d92..3af28758e7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -207,11 +207,30 @@ def is_indexer_error(error: Exception) -> bool: return "bad indexers" in message.lower() +def _resolve_subgraph_urls( + network: Dict[str, Any], use_hmt_subgraph: bool = False +) -> Tuple[str, str]: + """Resolve public/API-key URLs for either default or HMT subgraph.""" + if not use_hmt_subgraph: + return network["subgraph_url"], network["subgraph_url_api_key"] + + hmt_subgraph_url = network.get("hmt_subgraph_url") + hmt_subgraph_url_api_key = network.get("hmt_subgraph_url_api_key") + + public_url = hmt_subgraph_url or network["subgraph_url"] + api_key_url = ( + hmt_subgraph_url_api_key or hmt_subgraph_url or network["subgraph_url_api_key"] + ) + + return public_url, api_key_url + + def custom_gql_fetch( network: Dict[str, Any], query: str, params: Optional[Dict[str, Any]] = None, options: Optional[SubgraphOptions] = None, + use_hmt_subgraph: bool = False, ) -> Dict[str, Any]: """Fetch data from the subgraph with optional retry logic and indexer routing. @@ -220,6 +239,7 @@ def custom_gql_fetch( query (str): GraphQL query string to execute. params (Optional[Dict[str, Any]]): Optional query parameters/variables dictionary. options (Optional[SubgraphOptions]): Optional subgraph configuration for retries and indexer selection. + use_hmt_subgraph (bool): If true, resolves URL using HMT subgraph keys with fallback. Returns: JSON response from the subgraph containing the query results. @@ -250,7 +270,12 @@ def custom_gql_fetch( subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") if not options: - return _fetch_subgraph_data(network, query, params) + return _fetch_subgraph_data( + network, + query, + params, + use_hmt_subgraph=use_hmt_subgraph, + ) has_max_retries = options.max_retries is not None has_base_delay = options.base_delay is not None @@ -272,7 +297,13 @@ def custom_gql_fetch( for attempt in range(max_retries + 1): try: - return _fetch_subgraph_data(network, query, params, options.indexer_id) + return _fetch_subgraph_data( + network, + query, + params, + options.indexer_id, + use_hmt_subgraph, + ) except Exception as error: last_error = error @@ -290,6 +321,7 @@ def _fetch_subgraph_data( query: str, params: Optional[Dict[str, Any]] = None, indexer_id: Optional[str] = None, + use_hmt_subgraph: bool = False, ) -> Dict[str, Any]: """Internal function to fetch data from the subgraph API. @@ -298,6 +330,7 @@ def _fetch_subgraph_data( query (str): GraphQL query string to execute. params (Optional[Dict[str, Any]]): Optional query parameters/variables dictionary. indexer_id (Optional[str]): Optional indexer ID to route the request to. + use_hmt_subgraph (bool): If true, resolves URL using HMT subgraph keys with fallback. Returns: JSON response from the subgraph. @@ -305,14 +338,17 @@ def _fetch_subgraph_data( Raises: Exception: If the HTTP request fails or returns a non-200 status code. """ + default_subgraph_url, default_subgraph_url_api_key = _resolve_subgraph_urls( + network, use_hmt_subgraph + ) subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") if subgraph_api_key: - subgraph_url = network["subgraph_url_api_key"] + subgraph_url = default_subgraph_url_api_key else: logger.warning( "Warning: SUBGRAPH_API_KEY is not provided. It might cause issues with the subgraph." ) - subgraph_url = network["subgraph_url"] + subgraph_url = default_subgraph_url subgraph_url = _attach_indexer_id(subgraph_url, indexer_id) @@ -596,6 +632,25 @@ def extract_reason(msg): raise exception_class(f"Transaction failed: {msg}") +def get_error_message(error: Exception) -> str: + """Extract a readable error message from an exception-like object.""" + + message = getattr(error, "message", None) + if isinstance(message, str) and message: + return message + + error_args = getattr(error, "args", None) + if ( + error_args + and isinstance(error_args, tuple) + and isinstance(error_args[0], dict) + and "message" in error_args[0] + ): + return str(error_args[0]["message"]) + + return str(error) + + def validate_url(url: str) -> bool: """Validate whether a string is a properly formatted URL. diff --git a/packages/sdk/python/human-protocol-sdk/scripts/build-contracts.sh b/packages/sdk/python/human-protocol-sdk/scripts/build-contracts.sh index c83fbb9a4d..448f4bb849 100755 --- a/packages/sdk/python/human-protocol-sdk/scripts/build-contracts.sh +++ b/packages/sdk/python/human-protocol-sdk/scripts/build-contracts.sh @@ -6,5 +6,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../../.." && pwd)" yarn --cwd "$REPO_ROOT" workspaces focus @human-protocol/python-sdk +yarn --cwd "$REPO_ROOT" workspaces foreach -Rpt --from @human-protocol/python-sdk run build + rm -rf artifacts cp -r "${REPO_ROOT}/node_modules/@human-protocol/core/artifacts" . diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py index 6b1fc49cb5..1dfc881c5d 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py @@ -115,9 +115,6 @@ def test_escrow_config_valid_params(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "https://www.example.com/result" hash = "test" @@ -125,9 +122,6 @@ def test_escrow_config_valid_params(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -139,9 +133,6 @@ def test_escrow_config_valid_params(self): escrow_config.reputation_oracle_address, reputation_oracle_address ) self.assertEqual(escrow_config.exchange_oracle_address, exchange_oracle_address) - self.assertEqual(escrow_config.recording_oracle_fee, recording_oracle_fee) - self.assertEqual(escrow_config.reputation_oracle_fee, reputation_oracle_fee) - self.assertEqual(escrow_config.exchange_oracle_fee, exchange_oracle_fee) self.assertEqual(escrow_config.manifest, manifest) self.assertEqual(escrow_config.hash, hash) @@ -149,9 +140,6 @@ def test_escrow_config_valid_params_with_json_manifest(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = '{"foo": "bar"}' hash = "test" @@ -159,9 +147,6 @@ def test_escrow_config_valid_params_with_json_manifest(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -171,9 +156,6 @@ def test_escrow_config_valid_params_with_docker_network_url(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "http://test:6000" hash = "test" @@ -181,9 +163,6 @@ def test_escrow_config_valid_params_with_docker_network_url(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -195,18 +174,12 @@ def test_escrow_config_valid_params_with_docker_network_url(self): escrow_config.reputation_oracle_address, reputation_oracle_address ) self.assertEqual(escrow_config.exchange_oracle_address, exchange_oracle_address) - self.assertEqual(escrow_config.recording_oracle_fee, recording_oracle_fee) - self.assertEqual(escrow_config.reputation_oracle_fee, reputation_oracle_fee) - self.assertEqual(escrow_config.exchange_oracle_fee, exchange_oracle_fee) self.assertEqual(escrow_config.manifest, manifest) self.assertEqual(escrow_config.hash, hash) def test_escrow_config_invalid_address(self): invalid_address = "invalid_address" valid_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest_url = "https://www.example.com/result" hash = "test" @@ -215,9 +188,6 @@ def test_escrow_config_invalid_address(self): invalid_address, valid_address, valid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -230,9 +200,6 @@ def test_escrow_config_invalid_address(self): valid_address, invalid_address, valid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -245,9 +212,6 @@ def test_escrow_config_invalid_address(self): valid_address, valid_address, invalid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -255,112 +219,10 @@ def test_escrow_config_invalid_address(self): f"Invalid exchange oracle address: {invalid_address}", str(cm.exception) ) - def test_escrow_config_invalid_fee(self): - recording_oracle_address = "0x1234567890123456789012345678901234567890" - reputation_oracle_address = "0x1234567890123456789012345678901234567890" - exchange_oracle_address = "0x1234567890123456789012345678901234567890" - valid_oracle_fee = 10 - manifest_url = "https://www.example.com/result" - hash = "test" - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - (-2), - valid_oracle_fee, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - 1000, - valid_oracle_fee, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - (-2), - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - 1000, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - valid_oracle_fee, - (-2), - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - valid_oracle_fee, - 1000, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - 40, - 40, - 40, - manifest_url, - hash, - ) - self.assertEqual("Total fee must be less than 100", str(cm.exception)) - def test_escrow_config_invalid_manifest(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "" hash = "test" @@ -369,9 +231,6 @@ def test_escrow_config_invalid_manifest(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -381,9 +240,6 @@ def test_escrow_config_invalid_hash(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest_url = "https://www.example.com/result" hash = "" @@ -392,9 +248,6 @@ def test_escrow_config_invalid_hash(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -559,9 +412,6 @@ def test_create_fund_and_setup_escrow(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -577,9 +427,6 @@ def test_create_fund_and_setup_escrow(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -599,9 +446,6 @@ def test_create_fund_and_setup_escrow_invalid_token(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -627,9 +471,6 @@ def test_create_fund_and_setup_escrow_without_account(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -667,9 +508,6 @@ def test_create_fund_and_setup_escrow_with_tx_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -685,9 +523,6 @@ def test_create_fund_and_setup_escrow_with_tx_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -726,9 +561,6 @@ def test_create_fund_and_setup_escrow_with_wait_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -744,9 +576,6 @@ def test_create_fund_and_setup_escrow_with_wait_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -774,9 +603,6 @@ def test_setup(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -788,9 +614,6 @@ def test_setup(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -806,9 +629,6 @@ def test_setup_invalid_address(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -833,9 +653,6 @@ def test_get_manifest(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -856,9 +673,6 @@ def test_setup_without_account(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -878,9 +692,6 @@ def test_setup_invalid_status(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -904,9 +715,6 @@ def test_setup_invalid_caller(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -931,9 +739,6 @@ def test_setup_with_tx_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -946,9 +751,6 @@ def test_setup_with_tx_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -972,9 +774,6 @@ def test_setup_with_wait_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -987,9 +786,6 @@ def test_setup_with_wait_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -2493,9 +2289,26 @@ def test_get_manifest_hash_invalid_address(self): def test_get_manifest(self): mock_contract = MagicMock() + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.return_value = "mock_value" + self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + escrow_address = "0x1234567890123456789012345678901234567890" + + result = self.escrow.get_manifest(escrow_address) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.manifest.assert_called_once_with() + self.assertEqual(result, "mock_value") + + def test_get_manifest_fallback_to_manifest_url(self): + mock_contract = MagicMock() + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.side_effect = Exception( + "manifest() not found" + ) mock_contract.functions.manifestUrl = MagicMock() mock_contract.functions.manifestUrl.return_value.call.return_value = ( - "mock_value" + "legacy_manifest" ) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) escrow_address = "0x1234567890123456789012345678901234567890" @@ -2503,8 +2316,34 @@ def test_get_manifest(self): result = self.escrow.get_manifest(escrow_address) self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.manifest.assert_called_once_with() + mock_contract.functions.manifestUrl.assert_called_once_with() + self.assertEqual(result, "legacy_manifest") + + def test_get_manifest_raises_combined_error_if_manifest_and_fallback_fail(self): + mock_contract = MagicMock() + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.side_effect = Exception( + "manifest() failed" + ) + mock_contract.functions.manifestUrl = MagicMock() + mock_contract.functions.manifestUrl.return_value.call.side_effect = Exception( + "manifestUrl() failed" + ) + self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + escrow_address = "0x1234567890123456789012345678901234567890" + + with self.assertRaises(EscrowClientError) as cm: + self.escrow.get_manifest(escrow_address) + + self.assertEqual( + "Failed to fetch manifest using both manifest() and manifestUrl(). " + "manifest() error: manifest() failed. " + "manifestUrl() error: manifestUrl() failed.", + str(cm.exception), + ) + mock_contract.functions.manifest.assert_called_once_with() mock_contract.functions.manifestUrl.assert_called_once_with() - self.assertEqual(result, "mock_value") def test_get_manifest_invalid_address(self): with self.assertRaises(EscrowClientError) as cm: @@ -2519,17 +2358,15 @@ def test_get_manifest_without_account(self): escrowClient = EscrowClient(w3) mock_contract = MagicMock() - mock_contract.functions.manifestUrl = MagicMock() - mock_contract.functions.manifestUrl.return_value.call.return_value = ( - "mock_value" - ) + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.return_value = "mock_value" escrowClient._get_escrow_contract = MagicMock(return_value=mock_contract) escrow_address = "0x1234567890123456789012345678901234567890" result = escrowClient.get_manifest(escrow_address) escrowClient._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.manifestUrl.assert_called_once_with() + mock_contract.functions.manifest.assert_called_once_with() self.assertEqual(result, "mock_value") def test_get_manifest_invalid_escrow(self): 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 3a61638d6b..c4f22de6bb 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 @@ -176,7 +176,7 @@ def test_get_escrows_with_status_array(self): "intermediateResultsUrl": "https://example.com", "launcher": "0x1234567890123456789012345678901234567891", "manifestHash": "0x1234567890123456789012345678901234567891", - "manifestUrl": "https://example.com", + "manifest": "https://example.com", "recordingOracle": "0x1234567890123456789012345678901234567891", "reputationOracle": "0x1234567890123456789012345678901234567891", "exchangeOracle": "0x1234567890123456789012345678901234567891", @@ -196,7 +196,7 @@ def test_get_escrows_with_status_array(self): "intermediateResultsUrl": "https://example.com", "launcher": "0x1234567890123456789012345678901234567891", "manifestHash": "0x1234567890123456789012345678901234567891", - "manifestUrl": "https://example.com", + "manifest": "https://example.com", "recordingOracle": "0x1234567890123456789012345678901234567891", "reputationOracle": "0x1234567890123456789012345678901234567891", "exchangeOracle": "0x1234567890123456789012345678901234567891", diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py index 2b39eec467..f4fa1709cc 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py @@ -147,11 +147,9 @@ def test_get_empty_value(self, mock_function): address = Web3.to_checksum_address("0x1234567890123456789012345678901234567890") key = "key" - with self.assertRaises(KVStoreClientError) as cm: - KVStoreUtils.get(ChainId.LOCALHOST, address, key) - self.assertEqual( - f"Key '{key}' not found for address {address}", str(cm.exception) - ) + result = KVStoreUtils.get(ChainId.LOCALHOST, address, key) + + self.assertEqual(result, "") mock_function.assert_called_once_with( NETWORKS[ChainId.LOCALHOST], @@ -245,7 +243,7 @@ def test_get_file_url_and_verify_hash_empty_value(self, mock_function): with self.assertRaises(KVStoreClientError) as cm: KVStoreUtils.get_file_url_and_verify_hash(ChainId.LOCALHOST, address) self.assertEqual( - f"Key '{key}' not found for address {address}", str(cm.exception) + "No URL found for the given address and key", str(cm.exception) ) @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") @@ -313,12 +311,10 @@ def test_get_public_key_empty_value(self, mock_function): address = Web3.to_checksum_address("0x1234567890123456789012345678901234567890") - key = "public_key" - with self.assertRaises(KVStoreClientError) as cm: KVStoreUtils.get_public_key(ChainId.LOCALHOST, address) self.assertEqual( - f"Key '{key}' not found for address {address}", str(cm.exception) + "No URL found for the given address and key", str(cm.exception) ) @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py index c17523e5f7..4e53363602 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py @@ -1,12 +1,13 @@ import unittest from datetime import datetime -from unittest.mock import MagicMock, patch +from unittest.mock import patch from human_protocol_sdk.constants import NETWORKS, ChainId from human_protocol_sdk.gql.hmtoken import get_holders_query from human_protocol_sdk.gql.statistics import ( get_event_day_data_query, get_escrow_statistics_query, + get_hmt_event_day_data_query, get_hmtoken_statistics_query, ) from human_protocol_sdk.statistics import ( @@ -147,7 +148,6 @@ def test_get_payment_statistics(self): { "timestamp": 1, "dailyPayoutCount": "4", - "dailyHMTPayoutAmount": "100", "dailyWorkerCount": "4", }, ], @@ -177,13 +177,7 @@ def test_get_payment_statistics(self): payment_statistics.daily_payments_data[0].timestamp, datetime.fromtimestamp(1), ) - self.assertEqual( - payment_statistics.daily_payments_data[0].total_amount_paid, 100 - ) self.assertEqual(payment_statistics.daily_payments_data[0].total_count, 4) - self.assertEqual( - payment_statistics.daily_payments_data[0].average_amount_per_worker, 25 - ) def test_get_hmt_statistics(self): with patch( @@ -207,6 +201,7 @@ def test_get_hmt_statistics(self): NETWORKS[ChainId.LOCALHOST], query=get_hmtoken_statistics_query, options=None, + use_hmt_subgraph=True, ) self.assertEqual(hmt_statistics.total_transfer_amount, 100) @@ -245,6 +240,7 @@ def test_get_hmt_holders(self): "orderDirection": param.order_direction, }, options=None, + use_hmt_subgraph=True, ) self.assertEqual(len(holders), 2) @@ -283,7 +279,7 @@ def test_get_hmt_daily_data(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], - query=get_event_day_data_query(param), + query=get_hmt_event_day_data_query(param), params={ "from": 1683811973, "to": 1683812007, @@ -292,6 +288,7 @@ def test_get_hmt_daily_data(self): "orderDirection": "asc", }, options=None, + use_hmt_subgraph=True, ) self.assertEqual(len(hmt_statistics), 1) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index 54e5bc65bb..9c9fd026d5 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -72,7 +72,12 @@ def test_returns_response_without_options(self): result = custom_gql_fetch(self.network, self.query, self.variables) self.assertEqual(result, expected) - mock_fetch.assert_called_once_with(self.network, self.query, self.variables) + mock_fetch.assert_called_once_with( + self.network, + self.query, + self.variables, + use_hmt_subgraph=False, + ) def test_retries_on_indexer_error_and_succeeds(self): options = SubgraphOptions(max_retries=2, base_delay=100) @@ -151,7 +156,7 @@ def test_routes_requests_to_specific_indexer(self): self.assertEqual(result, expected) mock_fetch.assert_called_once_with( - self.network, self.query, self.variables, "0xabc123" + self.network, self.query, self.variables, "0xabc123", False ) def test_raises_when_indexer_without_api_key(self): @@ -184,6 +189,70 @@ def test_fetch_subgraph_adds_authorization_header(self): {"Authorization": "Bearer token"}, ) + def test_fetch_hmt_subgraph_uses_hmt_public_url_without_api_key(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + "hmt_subgraph_url_api_key": "http://hmt-subgraph-with-key", + } + + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph") + + def test_fetch_hmt_subgraph_uses_hmt_api_key_url_with_api_key(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + "hmt_subgraph_url_api_key": "http://hmt-subgraph-with-key", + } + + with patch.dict(os.environ, {"SUBGRAPH_API_KEY": "token"}, clear=True): + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph-with-key") + + def test_fetch_hmt_subgraph_falls_back_to_hmt_public_url_for_api_key_mode(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + } + + with patch.dict(os.environ, {"SUBGRAPH_API_KEY": "token"}, clear=True): + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph") + class TestAttachIndexerId(unittest.TestCase): def test_converts_subgraph_path_to_deployment(self): diff --git a/packages/sdk/typescript/human-protocol-sdk/eslint.config.mjs b/packages/sdk/typescript/human-protocol-sdk/eslint.config.mjs index 0bf91c4406..ea3fd32317 100644 --- a/packages/sdk/typescript/human-protocol-sdk/eslint.config.mjs +++ b/packages/sdk/typescript/human-protocol-sdk/eslint.config.mjs @@ -31,6 +31,8 @@ export default tseslint.config( jest: jestPlugin, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', '@/quotes': [ 'error', diff --git a/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts index 9a3c0f8f2b..f5979df609 100644 --- a/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts @@ -38,14 +38,7 @@ import { ChainId } from '../src/enums'; const paymentStatistics = await StatisticsUtils.getPaymentStatistics(networkData); - console.log('Payment statistics:', { - ...paymentStatistics, - dailyPaymentsData: paymentStatistics.dailyPaymentsData.map((p) => ({ - ...p, - totalAmountPaid: p.totalAmountPaid.toString(), - averageAmountPerWorker: p.averageAmountPerWorker.toString(), - })), - }); + console.log('Payment statistics:', paymentStatistics); const paymentStatisticsRange = await StatisticsUtils.getPaymentStatistics( networkData, @@ -55,14 +48,7 @@ import { ChainId } from '../src/enums'; } ); - console.log('Payment statistics from 5/8 - 6/8:', { - ...paymentStatisticsRange, - dailyPaymentsData: paymentStatisticsRange.dailyPaymentsData.map((p) => ({ - ...p, - totalAmountPaid: p.totalAmountPaid.toString(), - averageAmountPerWorker: p.averageAmountPerWorker.toString(), - })), - }); + console.log('Payment statistics from 5/8 - 6/8:', paymentStatisticsRange); const hmtStatistics = await StatisticsUtils.getHMTStatistics(networkData); diff --git a/packages/sdk/typescript/human-protocol-sdk/package.json b/packages/sdk/typescript/human-protocol-sdk/package.json index 963489d7c0..e77a006717 100644 --- a/packages/sdk/typescript/human-protocol-sdk/package.json +++ b/packages/sdk/typescript/human-protocol-sdk/package.json @@ -47,7 +47,7 @@ "openpgp": "^6.2.2", "secp256k1": "^5.0.1", "validator": "^13.12.0", - "vitest": "^3.0.9" + "vitest": "^4.0.18" }, "devDependencies": { "@types/validator": "^13.15.4", @@ -55,7 +55,7 @@ "eslint-plugin-jest": "^28.9.0", "eslint-plugin-prettier": "^5.5.5", "glob": "^13.0.0", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "ts-node": "^10.9.2", "typedoc": "^0.28.15", "typedoc-plugin-markdown": "^4.9.0", diff --git a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts index 6a9006c802..48b21a0885 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts @@ -34,11 +34,15 @@ export const NETWORKS: { stakingAddress: '0xEf6Da3aB52c33925Be3F84038193a7e1331F51E6', kvstoreAddress: '0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/ethereum/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-ethereum/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmNhLQEfBJQ46fngBh4YCttk8kNkveFy5uvAeUmyAdX1kD', + 'https://gateway.thegraph.com/api/deployments/id/QmQZ3yL1FzydDwaB56ozgTiBESciTNFsMLyTBfHXzNd1gg', oldSubgraphUrl: '', oldFactoryAddress: '', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-ethereum/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmdcTW7XhgULq5yJZGA65mjTwQogdyzLJaZ69ttDJz5JeE', }, [ChainId.SEPOLIA]: { chainId: ChainId.SEPOLIA, @@ -49,11 +53,15 @@ export const NETWORKS: { stakingAddress: '0x2163e3A40032Af1C359ac731deaB48258b317890', kvstoreAddress: '0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/sepolia/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-sepolia/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmQghdr7hxqrjFde8DN15TzfrJLCfwvzmUH9RzWwH1mKzk', + 'https://gateway.thegraph.com/api/deployments/id/QmQEbyGSf8VG9pdskowsK6C977wkcyJSxw9q6EDctKEkUw', oldSubgraphUrl: '', oldFactoryAddress: '', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-sepolia/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmYkPnYxbZ5ZTtKz7PTyxh6x5sK2H6yzx6vXDCrErpa9gB', }, [ChainId.BSC_MAINNET]: { chainId: ChainId.BSC_MAINNET, @@ -64,11 +72,15 @@ export const NETWORKS: { stakingAddress: '0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98', kvstoreAddress: '0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/bsc/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-bsc/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmTioC9Z1HzKSCnEKL3BP9iHqbgZt1ceLU2VE4Mv6sxNkd', + 'https://gateway.thegraph.com/api/deployments/id/QmV3nZUM51NGt7F8RpZP9GxKPpKox3F24iJ8H1ekPb6ha4', oldSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/humanprotocol/bsc', oldFactoryAddress: '0xc88bC422cAAb2ac8812de03176402dbcA09533f4', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-bsc/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmUnHgNShyv45S2dQ21W9jLvRA8xWT6XfeuP25fXJoNoEa', }, [ChainId.BSC_TESTNET]: { chainId: ChainId.BSC_TESTNET, @@ -79,12 +91,16 @@ export const NETWORKS: { stakingAddress: '0xD6D347ba6987519B4e42EcED43dF98eFf5465a23', kvstoreAddress: '0x32e27177BA6Ea91cf28dfd91a0Da9822A4b74EcF', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/bsc-testnet/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-bsc-testnet/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmPyUYRjAvzDdeenXMGHcCRD2v4qwZbKMEkVkY3Jq6VLwn', + 'https://gateway.thegraph.com/api/deployments/id/QmdQJog9vMghK2o39U9YJL8vpU9VZnkJa7jGg7wnsod7qV', oldSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/humanprotocol/bsctest', oldFactoryAddress: '0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-bsc-testnet/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmPMRbrXKp7a1i5x27dzBHbomxqKYDg5eU3djv1DXB5heq', }, [ChainId.POLYGON]: { chainId: ChainId.POLYGON, @@ -95,12 +111,16 @@ export const NETWORKS: { stakingAddress: '0x01D115E9E8bF0C58318793624CC662a030D07F1D', kvstoreAddress: '0xbcB28672F826a50B03EE91B28145EAbddA73B2eD', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-polygon/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmQNkWNE5FPtqbtSkdtR6AEBJz9N7WDz1X7pfvQqYAcUZJ', + 'https://gateway.thegraph.com/api/deployments/id/QmSu7B48kgGgMgXaD6Yb4fXjbnsJNmobKAU9qZgescZXid', oldSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/humanprotocol/polygon', oldFactoryAddress: '0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-polygon/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmcTTTq2reYDBP5j5P2eToc5FmYbJ5ZSMhPaRB6NLriNjR', }, [ChainId.POLYGON_AMOY]: { chainId: ChainId.POLYGON_AMOY, @@ -111,11 +131,15 @@ export const NETWORKS: { stakingAddress: '0xffE496683F842a923110415b7278ded3F265f2C5', kvstoreAddress: '0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-amoy/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmcLwLMw3UzCSbNbjegrpNu6PB3kAd67xquuyaVWvc5Q7Q', + 'https://gateway.thegraph.com/api/deployments/id/QmRx5WSi7o9FtENWnE5eEz8Dr7kYUazk4TNPRt65UZkKkB', oldSubgraphUrl: '', oldFactoryAddress: '', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-amoy/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmZze6UWMJna8dQ183TVounSfGwc9C36RgQCGWhFzgrW3y', }, [ChainId.LOCALHOST]: { chainId: ChainId.LOCALHOST, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts similarity index 50% rename from packages/sdk/typescript/human-protocol-sdk/src/encryption.ts rename to packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts index 208f3b9513..0329c1dd25 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts @@ -1,21 +1,5 @@ import * as openpgp from 'openpgp'; -import { IKeyPair } from './interfaces'; - -/** - * Type representing the data type of a message. - * It can be either a string or a Uint8Array. - * - * @public - */ -export type MessageDataType = string | Uint8Array; - -function makeMessageDataBinary(message: MessageDataType): Uint8Array { - if (typeof message === 'string') { - return Buffer.from(message); - } - - return message; -} +import { makeMessageDataBinary, MessageDataType } from './types'; /** * Class for signing and decrypting messages. @@ -191,179 +175,3 @@ export class Encryption { return cleartextMessage; } } - -/** - * Utility class for encryption-related operations. - */ -export class EncryptionUtils { - /** - * This function verifies the signature of a signed message using the public key. - * - * @param message - Message to verify. - * @param publicKey - Public key to verify that the message was signed by a specific source. - * @returns True if verified. False if not verified. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const publicKey = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const result = await EncryptionUtils.verify('message', publicKey); - * console.log('Verification result:', result); - * ``` - */ - public static async verify( - message: string, - publicKey: string - ): Promise { - const pgpPublicKey = await openpgp.readKey({ armoredKey: publicKey }); - const signedMessage = await openpgp.readCleartextMessage({ - cleartextMessage: message, - }); - - const verificationResult = await signedMessage.verify([pgpPublicKey]); - const { verified } = verificationResult[0]; - - try { - return await verified; - } catch { - return false; - } - } - - /** - * This function gets signed data from a signed message. - * - * @param message - Message. - * @returns Signed data. - * @throws Error If data could not be extracted from the message - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const signedData = await EncryptionUtils.getSignedData('message'); - * console.log('Signed data:', signedData); - * ``` - */ - public static async getSignedData(message: string): Promise { - const signedMessage = await openpgp.readCleartextMessage({ - cleartextMessage: message, - }); - - try { - return signedMessage.getText(); - } catch (e) { - throw new Error('Could not get data: ' + e.message); - } - } - - /** - * This function generates a key pair for encryption and decryption. - * - * @param name - Name for the key pair. - * @param email - Email for the key pair. - * @param passphrase - Passphrase to encrypt the private key (optional, defaults to empty string). - * @returns Key pair generated. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const name = 'YOUR_NAME'; - * const email = 'YOUR_EMAIL'; - * const passphrase = 'YOUR_PASSPHRASE'; - * const keyPair = await EncryptionUtils.generateKeyPair(name, email, passphrase); - * console.log('Public key:', keyPair.publicKey); - * ``` - */ - public static async generateKeyPair( - name: string, - email: string, - passphrase = '' - ): Promise { - const { privateKey, publicKey, revocationCertificate } = - await openpgp.generateKey({ - type: 'ecc', - curve: 'ed25519Legacy', - userIDs: [{ name: name, email: email }], - passphrase: passphrase, - format: 'armored', - }); - - return { - passphrase: passphrase, - privateKey, - publicKey, - revocationCertificate, - }; - } - - /** - * This function encrypts a message using the specified public keys. - * - * @param message - Message to encrypt. - * @param publicKeys - Array of public keys to use for encryption. - * @returns Message encrypted. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const publicKey1 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const publicKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const publicKeys = [publicKey1, publicKey2]; - * const encryptedMessage = await EncryptionUtils.encrypt('message', publicKeys); - * console.log('Encrypted message:', encryptedMessage); - * ``` - */ - public static async encrypt( - message: MessageDataType, - publicKeys: string[] - ): Promise { - const pgpPublicKeys = await Promise.all( - publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey })) - ); - - const pgpMessage = await openpgp.createMessage({ - binary: makeMessageDataBinary(message), - }); - const encrypted = await openpgp.encrypt({ - message: pgpMessage, - encryptionKeys: pgpPublicKeys, - format: 'armored', - }); - - return encrypted as string; - } - - /** - * Verifies if a message appears to be encrypted with OpenPGP. - * - * @param message - Message to verify. - * @returns `true` if the message appears to be encrypted, `false` if not. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const message = '-----BEGIN PGP MESSAGE-----...'; - * const isEncrypted = EncryptionUtils.isEncrypted(message); - * - * if (isEncrypted) { - * console.log('The message is encrypted with OpenPGP.'); - * } else { - * console.log('The message is not encrypted with OpenPGP.'); - * } - * ``` - */ - public static isEncrypted(message: string): boolean { - const startMarker = '-----BEGIN PGP MESSAGE-----'; - const endMarker = '-----END PGP MESSAGE-----'; - - const hasStartMarker = message.includes(startMarker); - const hasEndMarker = message.includes(endMarker); - - return hasStartMarker && hasEndMarker; - } -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts new file mode 100644 index 0000000000..0d75eaf028 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts @@ -0,0 +1,179 @@ +import * as openpgp from 'openpgp'; +import { IKeyPair } from '../interfaces'; +import { makeMessageDataBinary, MessageDataType } from './types'; + +/** + * Utility class for encryption-related operations. + */ +export class EncryptionUtils { + /** + * This function verifies the signature of a signed message using the public key. + * + * @param message - Message to verify. + * @param publicKey - Public key to verify that the message was signed by a specific source. + * @returns True if verified. False if not verified. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const publicKey = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const result = await EncryptionUtils.verify('message', publicKey); + * console.log('Verification result:', result); + * ``` + */ + public static async verify( + message: string, + publicKey: string + ): Promise { + const pgpPublicKey = await openpgp.readKey({ armoredKey: publicKey }); + const signedMessage = await openpgp.readCleartextMessage({ + cleartextMessage: message, + }); + + const verificationResult = await signedMessage.verify([pgpPublicKey]); + const { verified } = verificationResult[0]; + + try { + return await verified; + } catch { + return false; + } + } + + /** + * This function gets signed data from a signed message. + * + * @param message - Message. + * @returns Signed data. + * @throws Error If data could not be extracted from the message + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const signedData = await EncryptionUtils.getSignedData('message'); + * console.log('Signed data:', signedData); + * ``` + */ + public static async getSignedData(message: string): Promise { + const signedMessage = await openpgp.readCleartextMessage({ + cleartextMessage: message, + }); + + try { + return signedMessage.getText(); + } catch (e) { + throw new Error('Could not get data: ' + e.message); + } + } + + /** + * This function generates a key pair for encryption and decryption. + * + * @param name - Name for the key pair. + * @param email - Email for the key pair. + * @param passphrase - Passphrase to encrypt the private key (optional, defaults to empty string). + * @returns Key pair generated. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const name = 'YOUR_NAME'; + * const email = 'YOUR_EMAIL'; + * const passphrase = 'YOUR_PASSPHRASE'; + * const keyPair = await EncryptionUtils.generateKeyPair(name, email, passphrase); + * console.log('Public key:', keyPair.publicKey); + * ``` + */ + public static async generateKeyPair( + name: string, + email: string, + passphrase = '' + ): Promise { + const { privateKey, publicKey, revocationCertificate } = + await openpgp.generateKey({ + type: 'ecc', + curve: 'ed25519Legacy', + userIDs: [{ name: name, email: email }], + passphrase: passphrase, + format: 'armored', + }); + + return { + passphrase: passphrase, + privateKey, + publicKey, + revocationCertificate, + }; + } + + /** + * This function encrypts a message using the specified public keys. + * + * @param message - Message to encrypt. + * @param publicKeys - Array of public keys to use for encryption. + * @returns Message encrypted. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const publicKey1 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const publicKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const publicKeys = [publicKey1, publicKey2]; + * const encryptedMessage = await EncryptionUtils.encrypt('message', publicKeys); + * console.log('Encrypted message:', encryptedMessage); + * ``` + */ + public static async encrypt( + message: MessageDataType, + publicKeys: string[] + ): Promise { + const pgpPublicKeys = await Promise.all( + publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey })) + ); + + const pgpMessage = await openpgp.createMessage({ + binary: makeMessageDataBinary(message), + }); + const encrypted = await openpgp.encrypt({ + message: pgpMessage, + encryptionKeys: pgpPublicKeys, + format: 'armored', + }); + + return encrypted as string; + } + + /** + * Verifies if a message appears to be encrypted with OpenPGP. + * + * @param message - Message to verify. + * @returns `true` if the message appears to be encrypted, `false` if not. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const message = '-----BEGIN PGP MESSAGE-----...'; + * const isEncrypted = EncryptionUtils.isEncrypted(message); + * + * if (isEncrypted) { + * console.log('The message is encrypted with OpenPGP.'); + * } else { + * console.log('The message is not encrypted with OpenPGP.'); + * } + * ``` + */ + public static isEncrypted(message: string): boolean { + const startMarker = '-----BEGIN PGP MESSAGE-----'; + const endMarker = '-----END PGP MESSAGE-----'; + + const hasStartMarker = message.includes(startMarker); + const hasEndMarker = message.includes(endMarker); + + return hasStartMarker && hasEndMarker; + } +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts new file mode 100644 index 0000000000..78d80c0783 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts @@ -0,0 +1,3 @@ +export { Encryption } from './encryption'; +export { EncryptionUtils } from './encryption_utils'; +export type { MessageDataType } from './types'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts new file mode 100644 index 0000000000..7d7ee473c6 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts @@ -0,0 +1,15 @@ +/** + * Type representing the data type of a message. + * It can be either a string or a Uint8Array. + * + * @public + */ +export type MessageDataType = string | Uint8Array; + +export function makeMessageDataBinary(message: MessageDataType): Uint8Array { + if (typeof message === 'string') { + return Buffer.from(message); + } + + return message; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/error.ts b/packages/sdk/typescript/human-protocol-sdk/src/error.ts index f5cbe8fd4c..d2e4585e43 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/error.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/error.ts @@ -180,20 +180,6 @@ export const ErrorInvalidManifest = new Error('Invalid manifest'); */ export const ErrorNoURLprovided = new Error('No URL provided'); -/** - * @constant {Error} - Fee must be between 0 and 100. - */ -export const ErrorFeeMustBeBetweenZeroAndHundred = new Error( - 'Fee must be between 0 and 100' -); - -/** - * @constant {Error} - Total fee must be less than 100. - */ -export const ErrorTotalFeeMustBeLessThanHundred = new Error( - 'Total fee must be less than 100' -); - /** * @constant {Error} - Recipient cannot be an empty array. */ @@ -344,8 +330,16 @@ export class InvalidEthereumAddressError extends Error { } } -export class InvalidKeyError extends Error { - constructor(key: string, address: string) { - super(`Key "${key}" not found for address ${address}`); +export class SubgraphRequestError extends Error { + public readonly statusCode?: number; + public readonly url: string; + + constructor(message: string, url: string, statusCode?: number) { + super(message); + this.name = this.constructor.name; + this.url = url; + this.statusCode = statusCode; } } + +export class SubgraphBadIndexerError extends SubgraphRequestError {} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts similarity index 75% rename from packages/sdk/typescript/human-protocol-sdk/src/escrow.ts rename to packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts index 8b535a74ac..c00bdd0c93 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts @@ -10,10 +10,10 @@ import { HMToken__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, EventLog, Overrides, Signer, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { ESCROW_BULK_PAYOUT_MAX_ITEMS, NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId, OrderDirection } from './enums'; +import { BaseEthersClient } from '../base'; +import { ESCROW_BULK_PAYOUT_MAX_ITEMS, NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorAmountMustBeGreaterThanZero, ErrorAmountsCannotBeEmptyArray, @@ -21,7 +21,6 @@ import { ErrorEscrowAddressIsNotProvidedByFactory, ErrorEscrowDoesNotHaveEnoughBalance, ErrorHashIsEmptyString, - ErrorInvalidAddress, ErrorInvalidEscrowAddressProvided, ErrorInvalidExchangeOracleAddressProvided, ErrorInvalidManifest, @@ -35,51 +34,19 @@ import { ErrorRecipientCannotBeEmptyArray, ErrorStoreResultsVersion, ErrorTooManyRecipients, - ErrorTotalFeeMustBeLessThanHundred, ErrorTransferEventNotFoundInTransactionLogs, ErrorUnsupportedChainID, InvalidEthereumAddressError, WarnVersionMismatch, -} from './error'; -import { - CancellationRefundData, - EscrowData, - GET_CANCELLATION_REFUNDS_QUERY, - GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY, - GET_ESCROWS_QUERY, - GET_ESCROW_BY_ADDRESS_QUERY, - GET_PAYOUTS_QUERY, - GET_STATUS_UPDATES_QUERY, - PayoutData, - StatusEvent, -} from './graphql'; -import { - IEscrow, - IEscrowConfig, - IEscrowsFilter, - IPayoutFilter, - IStatusEventFilter, - IStatusEvent, - ICancellationRefund, - ICancellationRefundFilter, - IPayout, - IEscrowWithdraw, - SubgraphOptions, -} from './interfaces'; +} from '../error'; +import { IEscrowConfig, IEscrowWithdraw } from '../interfaces'; import { EscrowStatus, NetworkData, TransactionLikeWithNonce, TransactionOverrides, -} from './types'; -import { - getSubgraphUrl, - getUnixTimestamp, - customGqlFetch, - isValidJson, - isValidUrl, - throwError, -} from './utils'; +} from '../types'; +import { getErrorMessage, isValidJson, isValidUrl, throwError } from '../utils'; /** * Client to perform actions on Escrow contracts and obtain information from the contracts. @@ -248,9 +215,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -267,18 +231,6 @@ export class EscrowClient extends BaseEthersClient { throw ErrorInvalidExchangeOracleAddressProvided; } - if ( - recordingOracleFee <= 0 || - reputationOracleFee <= 0 || - exchangeOracleFee <= 0 - ) { - throw ErrorAmountMustBeGreaterThanZero; - } - - if (recordingOracleFee + reputationOracleFee + exchangeOracleFee > 100) { - throw ErrorTotalFeeMustBeLessThanHundred; - } - const isManifestValid = isValidUrl(manifest) || isValidJson(manifest); if (!isManifestValid) { throw ErrorInvalidManifest; @@ -303,8 +255,6 @@ export class EscrowClient extends BaseEthersClient { * @throws ErrorInvalidRecordingOracleAddressProvided If the recording oracle address is invalid * @throws ErrorInvalidReputationOracleAddressProvided If the reputation oracle address is invalid * @throws ErrorInvalidExchangeOracleAddressProvided If the exchange oracle address is invalid - * @throws ErrorAmountMustBeGreaterThanZero If any oracle fee is less than or equal to zero - * @throws ErrorTotalFeeMustBeLessThanHundred If the total oracle fees exceed 100 * @throws ErrorInvalidManifest If the manifest is not a valid URL or JSON string * @throws ErrorHashIsEmptyString If the manifest hash is empty * @throws ErrorLaunchedEventIsNotEmitted If the LaunchedV2 event is not emitted @@ -325,9 +275,6 @@ export class EscrowClient extends BaseEthersClient { * recordingOracle: '0xRecordingOracleAddress', * reputationOracle: '0xReputationOracleAddress', * exchangeOracle: '0xExchangeOracleAddress', - * recordingOracleFee: 5n, - * reputationOracleFee: 5n, - * exchangeOracleFee: 5n, * manifest: 'https://example.com/manifest.json', * manifestHash: 'manifestHash-123', * }; @@ -359,9 +306,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -376,9 +320,6 @@ export class EscrowClient extends BaseEthersClient { reputationOracle, recordingOracle, exchangeOracle, - reputationOracleFee, - recordingOracleFee, - exchangeOracleFee, manifest, manifestHash, overrides @@ -414,8 +355,6 @@ export class EscrowClient extends BaseEthersClient { * @throws ErrorInvalidRecordingOracleAddressProvided If the recording oracle address is invalid * @throws ErrorInvalidReputationOracleAddressProvided If the reputation oracle address is invalid * @throws ErrorInvalidExchangeOracleAddressProvided If the exchange oracle address is invalid - * @throws ErrorAmountMustBeGreaterThanZero If any oracle fee is less than or equal to zero - * @throws ErrorTotalFeeMustBeLessThanHundred If the total oracle fees exceed 100 * @throws ErrorInvalidManifest If the manifest is not a valid URL or JSON string * @throws ErrorHashIsEmptyString If the manifest hash is empty * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid @@ -429,9 +368,6 @@ export class EscrowClient extends BaseEthersClient { * recordingOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', * reputationOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', * exchangeOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - * recordingOracleFee: 10n, - * reputationOracleFee: 10n, - * exchangeOracleFee: 10n, * manifest: 'http://localhost/manifest.json', * manifestHash: 'b5dad76bf6772c0f07fd5e048f6e75a5f86ee079', * }; @@ -448,9 +384,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -474,9 +407,6 @@ export class EscrowClient extends BaseEthersClient { reputationOracle, recordingOracle, exchangeOracle, - reputationOracleFee, - recordingOracleFee, - exchangeOracleFee, manifest, manifestHash, overrides @@ -1354,7 +1284,34 @@ export class EscrowClient extends BaseEthersClient { try { const escrowContract = this.getEscrowContract(escrowAddress); - return escrowContract.manifestUrl(); + try { + return await escrowContract.manifest(); + } catch (manifestError) { + // Fallback for legacy escrows exposing `manifestUrl()` instead of `manifest()`. + try { + const provider = this.runner.provider; + if (!provider) { + throw ErrorProviderDoesNotExist; + } + + const manifestInterface = new ethers.Interface([ + 'function manifestUrl() view returns (string)', + ]); + const target = escrowContract.target as string; + const data = manifestInterface.encodeFunctionData('manifestUrl'); + const result = await provider.call({ to: target, data }); + return manifestInterface.decodeFunctionResult( + 'manifestUrl', + result + )[0] as string; + } catch (fallbackError) { + throw new Error( + `Failed to fetch manifest using both manifest() and manifestUrl(). ` + + `manifest() error: ${getErrorMessage(manifestError)}. ` + + `manifestUrl() error: ${getErrorMessage(fallbackError)}.` + ); + } + } } catch (e) { return throwError(e); } @@ -1682,480 +1639,3 @@ export class EscrowClient extends BaseEthersClient { } } } -/** - * Utility helpers for escrow-related queries. - * - * @example - * ```ts - * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; - * - * const escrows = await EscrowUtils.getEscrows({ - * chainId: ChainId.POLYGON_AMOY - * }); - * console.log('Escrows:', escrows); - * ``` - */ -export class EscrowUtils { - /** - * This function returns an array of escrows based on the specified filter parameters. - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of escrows that match the filter. - * @throws ErrorInvalidAddress If any filter address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; - * - * const filters = { - * status: EscrowStatus.Pending, - * from: new Date(2023, 4, 8), - * to: new Date(2023, 5, 8), - * chainId: ChainId.POLYGON_AMOY - * }; - * const escrows = await EscrowUtils.getEscrows(filters); - * console.log('Found escrows:', escrows.length); - * ``` - */ - public static async getEscrows( - filter: IEscrowsFilter, - options?: SubgraphOptions - ): Promise { - if (filter.launcher && !ethers.isAddress(filter.launcher)) { - throw ErrorInvalidAddress; - } - - if (filter.recordingOracle && !ethers.isAddress(filter.recordingOracle)) { - throw ErrorInvalidAddress; - } - - if (filter.reputationOracle && !ethers.isAddress(filter.reputationOracle)) { - throw ErrorInvalidAddress; - } - - if (filter.exchangeOracle && !ethers.isAddress(filter.exchangeOracle)) { - 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 networkData = NETWORKS[filter.chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - let statuses; - if (filter.status !== undefined) { - statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; - statuses = statuses.map((status) => EscrowStatus[status]); - } - const { escrows } = await customGqlFetch<{ escrows: EscrowData[] }>( - getSubgraphUrl(networkData), - GET_ESCROWS_QUERY(filter), - { - ...filter, - launcher: filter.launcher?.toLowerCase(), - reputationOracle: filter.reputationOracle?.toLowerCase(), - recordingOracle: filter.recordingOracle?.toLowerCase(), - exchangeOracle: filter.exchangeOracle?.toLowerCase(), - status: statuses, - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }, - options - ); - return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); - } - - /** - * This function returns the escrow data for a given address. - * - * > This uses Subgraph - * - * @param chainId - Network in which the escrow has been deployed - * @param escrowAddress - Address of the escrow - * @param options - Optional configuration for subgraph requests. - * @returns Escrow data or null if not found. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidAddress If the escrow address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const escrow = await EscrowUtils.getEscrow( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * if (escrow) { - * console.log('Escrow status:', escrow.status); - * } - * ``` - */ - public static async getEscrow( - chainId: ChainId, - escrowAddress: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - if (escrowAddress && !ethers.isAddress(escrowAddress)) { - throw ErrorInvalidAddress; - } - - const { escrow } = await customGqlFetch<{ escrow: EscrowData | null }>( - getSubgraphUrl(networkData), - GET_ESCROW_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() }, - options - ); - if (!escrow) return null; - - return mapEscrow(escrow, networkData.chainId); - } - - /** - * This function returns the status events for a given set of networks within an optional date range. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns Array of status events with their corresponding statuses. - * @throws ErrorInvalidAddress If the launcher address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; - * - * const fromDate = new Date('2023-01-01'); - * const toDate = new Date('2023-12-31'); - * const statusEvents = await EscrowUtils.getStatusEvents({ - * chainId: ChainId.POLYGON, - * statuses: [EscrowStatus.Pending, EscrowStatus.Complete], - * from: fromDate, - * to: toDate - * }); - * console.log('Status events:', statusEvents.length); - * ``` - */ - public static async getStatusEvents( - filter: IStatusEventFilter, - options?: SubgraphOptions - ): Promise { - const { - chainId, - statuses, - from, - to, - launcher, - first = 10, - skip = 0, - orderDirection = OrderDirection.DESC, - } = filter; - - if (launcher && !ethers.isAddress(launcher)) { - throw ErrorInvalidAddress; - } - - const networkData = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - // If statuses are not provided, use all statuses except Launched - const effectiveStatuses = statuses ?? [ - EscrowStatus.Launched, - EscrowStatus.Pending, - EscrowStatus.Partial, - EscrowStatus.Paid, - EscrowStatus.Complete, - EscrowStatus.Cancelled, - ]; - - const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); - - const data = await customGqlFetch<{ - escrowStatusEvents: StatusEvent[]; - }>( - getSubgraphUrl(networkData), - GET_STATUS_UPDATES_QUERY(from, to, launcher), - { - status: statusNames, - from: from ? getUnixTimestamp(from) : undefined, - to: to ? getUnixTimestamp(to) : undefined, - launcher: launcher || undefined, - orderDirection, - first: Math.min(first, 1000), - skip, - }, - options - ); - - if (!data || !data['escrowStatusEvents']) { - return []; - } - - return data['escrowStatusEvents'].map((event) => ({ - timestamp: Number(event.timestamp) * 1000, - escrowAddress: event.escrowAddress, - status: EscrowStatus[event.status as keyof typeof EscrowStatus], - chainId, - })); - } - - /** - * This function returns the payouts for a given set of networks. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of payouts matching the filters. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidAddress If any filter address is invalid - * - * @example - * ```ts - * import { ChainId } 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:', payouts.length); - * ``` - */ - public static async getPayouts( - filter: IPayoutFilter, - options?: SubgraphOptions - ): 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 customGqlFetch<{ payouts: PayoutData[] }>( - 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, - }, - options - ); - if (!payouts) { - return []; - } - - return payouts.map((payout) => ({ - id: payout.id, - escrowAddress: payout.escrowAddress, - recipient: payout.recipient, - amount: BigInt(payout.amount), - createdAt: Number(payout.createdAt) * 1000, - })); - } - - /** - * This function returns the cancellation refunds for a given set of networks. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of cancellation refunds matching the filters. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid - * @throws ErrorInvalidAddress If the receiver address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const cancellationRefunds = await EscrowUtils.getCancellationRefunds({ - * chainId: ChainId.POLYGON_AMOY, - * escrowAddress: '0x1234567890123456789012345678901234567890', - * }); - * console.log('Cancellation refunds:', cancellationRefunds.length); - * ``` - */ - public static async getCancellationRefunds( - filter: ICancellationRefundFilter, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[filter.chainId]; - if (!networkData) throw ErrorUnsupportedChainID; - if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { - throw ErrorInvalidEscrowAddressProvided; - } - if (filter.receiver && !ethers.isAddress(filter.receiver)) { - 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 { cancellationRefundEvents } = await customGqlFetch<{ - cancellationRefundEvents: CancellationRefundData[]; - }>( - getSubgraphUrl(networkData), - GET_CANCELLATION_REFUNDS_QUERY(filter), - { - escrowAddress: filter.escrowAddress?.toLowerCase(), - receiver: filter.receiver?.toLowerCase(), - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - first, - skip, - orderDirection, - }, - options - ); - - if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { - return []; - } - - return cancellationRefundEvents.map((event) => ({ - id: event.id, - escrowAddress: event.escrowAddress, - receiver: event.receiver, - amount: BigInt(event.amount), - block: Number(event.block), - timestamp: Number(event.timestamp) * 1000, - txHash: event.txHash, - })); - } - - /** - * This function returns the cancellation refund for a given escrow address. - * - * > This uses Subgraph - * - * @param chainId - Network in which the escrow has been deployed - * @param escrowAddress - Address of the escrow - * @param options - Optional configuration for subgraph requests. - * @returns Cancellation refund data or null if not found. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * - * const cancellationRefund = await EscrowUtils.getCancellationRefund( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * if (cancellationRefund) { - * console.log('Refund amount:', cancellationRefund.amount); - * } - * ``` - */ - public static async getCancellationRefund( - chainId: ChainId, - escrowAddress: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - if (!networkData) throw ErrorUnsupportedChainID; - - if (!ethers.isAddress(escrowAddress)) { - throw ErrorInvalidEscrowAddressProvided; - } - - const { cancellationRefundEvents } = await customGqlFetch<{ - cancellationRefundEvents: CancellationRefundData[]; - }>( - getSubgraphUrl(networkData), - GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() }, - options - ); - - if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { - return null; - } - - return { - id: cancellationRefundEvents[0].id, - escrowAddress: cancellationRefundEvents[0].escrowAddress, - receiver: cancellationRefundEvents[0].receiver, - amount: BigInt(cancellationRefundEvents[0].amount), - block: Number(cancellationRefundEvents[0].block), - timestamp: Number(cancellationRefundEvents[0].timestamp) * 1000, - txHash: cancellationRefundEvents[0].txHash, - }; - } -} - -function mapEscrow(e: EscrowData, chainId: ChainId | number): IEscrow { - return { - id: e.id, - address: e.address, - amountPaid: BigInt(e.amountPaid), - balance: BigInt(e.balance), - count: Number(e.count), - factoryAddress: e.factoryAddress, - finalResultsUrl: e.finalResultsUrl, - finalResultsHash: e.finalResultsHash, - intermediateResultsUrl: e.intermediateResultsUrl, - intermediateResultsHash: e.intermediateResultsHash, - launcher: e.launcher, - jobRequesterId: e.jobRequesterId, - manifestHash: e.manifestHash, - manifest: e.manifest, - recordingOracle: e.recordingOracle, - reputationOracle: e.reputationOracle, - exchangeOracle: e.exchangeOracle, - recordingOracleFee: e.recordingOracleFee - ? Number(e.recordingOracleFee) - : null, - reputationOracleFee: e.reputationOracleFee - ? Number(e.reputationOracleFee) - : null, - exchangeOracleFee: e.exchangeOracleFee ? Number(e.exchangeOracleFee) : null, - status: e.status, - token: e.token, - totalFundedAmount: BigInt(e.totalFundedAmount), - createdAt: Number(e.createdAt) * 1000, - chainId: Number(chainId), - }; -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts new file mode 100644 index 0000000000..7d3639cdfa --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts @@ -0,0 +1,510 @@ +import { ethers } from 'ethers'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { + ErrorInvalidAddress, + ErrorInvalidEscrowAddressProvided, + ErrorUnsupportedChainID, +} from '../error'; +import { + CancellationRefundData, + EscrowData, + GET_CANCELLATION_REFUNDS_QUERY, + GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY, + GET_ESCROWS_QUERY, + GET_ESCROW_BY_ADDRESS_QUERY, + GET_PAYOUTS_QUERY, + GET_STATUS_UPDATES_QUERY, + PayoutData, + StatusEvent, +} from '../graphql'; +import { + ICancellationRefund, + ICancellationRefundFilter, + IEscrow, + IEscrowsFilter, + IPayout, + IPayoutFilter, + IStatusEvent, + IStatusEventFilter, + SubgraphOptions, +} from '../interfaces'; +import { EscrowStatus } from '../types'; +import { customGqlFetch, getSubgraphUrl, getUnixTimestamp } from '../utils'; +/** + * Utility helpers for escrow-related queries. + * + * @example + * ```ts + * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; + * + * const escrows = await EscrowUtils.getEscrows({ + * chainId: ChainId.POLYGON_AMOY + * }); + * console.log('Escrows:', escrows); + * ``` + */ +export class EscrowUtils { + /** + * This function returns an array of escrows based on the specified filter parameters. + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of escrows that match the filter. + * @throws ErrorInvalidAddress If any filter address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; + * + * const filters = { + * status: EscrowStatus.Pending, + * from: new Date(2023, 4, 8), + * to: new Date(2023, 5, 8), + * chainId: ChainId.POLYGON_AMOY + * }; + * const escrows = await EscrowUtils.getEscrows(filters); + * console.log('Found escrows:', escrows.length); + * ``` + */ + public static async getEscrows( + filter: IEscrowsFilter, + options?: SubgraphOptions + ): Promise { + if (filter.launcher && !ethers.isAddress(filter.launcher)) { + throw ErrorInvalidAddress; + } + + if (filter.recordingOracle && !ethers.isAddress(filter.recordingOracle)) { + throw ErrorInvalidAddress; + } + + if (filter.reputationOracle && !ethers.isAddress(filter.reputationOracle)) { + throw ErrorInvalidAddress; + } + + if (filter.exchangeOracle && !ethers.isAddress(filter.exchangeOracle)) { + 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 networkData = NETWORKS[filter.chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + let statuses; + if (filter.status !== undefined) { + statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; + statuses = statuses.map((status) => EscrowStatus[status]); + } + const { escrows } = await customGqlFetch<{ escrows: EscrowData[] }>( + getSubgraphUrl(networkData), + GET_ESCROWS_QUERY(filter), + { + ...filter, + launcher: filter.launcher?.toLowerCase(), + reputationOracle: filter.reputationOracle?.toLowerCase(), + recordingOracle: filter.recordingOracle?.toLowerCase(), + exchangeOracle: filter.exchangeOracle?.toLowerCase(), + status: statuses, + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + options + ); + return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); + } + + /** + * This function returns the escrow data for a given address. + * + * > This uses Subgraph + * + * @param chainId - Network in which the escrow has been deployed + * @param escrowAddress - Address of the escrow + * @param options - Optional configuration for subgraph requests. + * @returns Escrow data or null if not found. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidAddress If the escrow address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const escrow = await EscrowUtils.getEscrow( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * if (escrow) { + * console.log('Escrow status:', escrow.status); + * } + * ``` + */ + public static async getEscrow( + chainId: ChainId, + escrowAddress: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + if (escrowAddress && !ethers.isAddress(escrowAddress)) { + throw ErrorInvalidAddress; + } + + const { escrow } = await customGqlFetch<{ escrow: EscrowData | null }>( + getSubgraphUrl(networkData), + GET_ESCROW_BY_ADDRESS_QUERY(), + { escrowAddress: escrowAddress.toLowerCase() }, + options + ); + if (!escrow) return null; + + return mapEscrow(escrow, networkData.chainId); + } + + /** + * This function returns the status events for a given set of networks within an optional date range. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns Array of status events with their corresponding statuses. + * @throws ErrorInvalidAddress If the launcher address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; + * + * const fromDate = new Date('2023-01-01'); + * const toDate = new Date('2023-12-31'); + * const statusEvents = await EscrowUtils.getStatusEvents({ + * chainId: ChainId.POLYGON, + * statuses: [EscrowStatus.Pending, EscrowStatus.Complete], + * from: fromDate, + * to: toDate + * }); + * console.log('Status events:', statusEvents.length); + * ``` + */ + public static async getStatusEvents( + filter: IStatusEventFilter, + options?: SubgraphOptions + ): Promise { + const { + chainId, + statuses, + from, + to, + launcher, + first = 10, + skip = 0, + orderDirection = OrderDirection.DESC, + } = filter; + + if (launcher && !ethers.isAddress(launcher)) { + throw ErrorInvalidAddress; + } + + const networkData = NETWORKS[chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + // If statuses are not provided, use all statuses except Launched + const effectiveStatuses = statuses ?? [ + EscrowStatus.Launched, + EscrowStatus.Pending, + EscrowStatus.Partial, + EscrowStatus.Paid, + EscrowStatus.Complete, + EscrowStatus.Cancelled, + ]; + + const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); + + const data = await customGqlFetch<{ + escrowStatusEvents: StatusEvent[]; + }>( + getSubgraphUrl(networkData), + GET_STATUS_UPDATES_QUERY(from, to, launcher), + { + status: statusNames, + from: from ? getUnixTimestamp(from) : undefined, + to: to ? getUnixTimestamp(to) : undefined, + launcher: launcher || undefined, + orderDirection, + first: Math.min(first, 1000), + skip, + }, + options + ); + + if (!data || !data['escrowStatusEvents']) { + return []; + } + + return data['escrowStatusEvents'].map((event) => ({ + timestamp: Number(event.timestamp) * 1000, + escrowAddress: event.escrowAddress, + status: EscrowStatus[event.status as keyof typeof EscrowStatus], + chainId, + })); + } + + /** + * This function returns the payouts for a given set of networks. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of payouts matching the filters. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidAddress If any filter address is invalid + * + * @example + * ```ts + * import { ChainId } 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:', payouts.length); + * ``` + */ + public static async getPayouts( + filter: IPayoutFilter, + options?: SubgraphOptions + ): 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 customGqlFetch<{ payouts: PayoutData[] }>( + 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, + }, + options + ); + if (!payouts) { + return []; + } + + return payouts.map((payout) => ({ + id: payout.id, + escrowAddress: payout.escrowAddress, + recipient: payout.recipient, + amount: BigInt(payout.amount), + createdAt: Number(payout.createdAt) * 1000, + })); + } + + /** + * This function returns the cancellation refunds for a given set of networks. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of cancellation refunds matching the filters. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid + * @throws ErrorInvalidAddress If the receiver address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const cancellationRefunds = await EscrowUtils.getCancellationRefunds({ + * chainId: ChainId.POLYGON_AMOY, + * escrowAddress: '0x1234567890123456789012345678901234567890', + * }); + * console.log('Cancellation refunds:', cancellationRefunds.length); + * ``` + */ + public static async getCancellationRefunds( + filter: ICancellationRefundFilter, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[filter.chainId]; + if (!networkData) throw ErrorUnsupportedChainID; + if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { + throw ErrorInvalidEscrowAddressProvided; + } + if (filter.receiver && !ethers.isAddress(filter.receiver)) { + 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 { cancellationRefundEvents } = await customGqlFetch<{ + cancellationRefundEvents: CancellationRefundData[]; + }>( + getSubgraphUrl(networkData), + GET_CANCELLATION_REFUNDS_QUERY(filter), + { + escrowAddress: filter.escrowAddress?.toLowerCase(), + receiver: filter.receiver?.toLowerCase(), + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + first, + skip, + orderDirection, + }, + options + ); + + if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { + return []; + } + + return cancellationRefundEvents.map((event) => ({ + id: event.id, + escrowAddress: event.escrowAddress, + receiver: event.receiver, + amount: BigInt(event.amount), + block: Number(event.block), + timestamp: Number(event.timestamp) * 1000, + txHash: event.txHash, + })); + } + + /** + * This function returns the cancellation refund for a given escrow address. + * + * > This uses Subgraph + * + * @param chainId - Network in which the escrow has been deployed + * @param escrowAddress - Address of the escrow + * @param options - Optional configuration for subgraph requests. + * @returns Cancellation refund data or null if not found. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * + * const cancellationRefund = await EscrowUtils.getCancellationRefund( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * if (cancellationRefund) { + * console.log('Refund amount:', cancellationRefund.amount); + * } + * ``` + */ + public static async getCancellationRefund( + chainId: ChainId, + escrowAddress: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + if (!networkData) throw ErrorUnsupportedChainID; + + if (!ethers.isAddress(escrowAddress)) { + throw ErrorInvalidEscrowAddressProvided; + } + + const { cancellationRefundEvents } = await customGqlFetch<{ + cancellationRefundEvents: CancellationRefundData[]; + }>( + getSubgraphUrl(networkData), + GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), + { escrowAddress: escrowAddress.toLowerCase() }, + options + ); + + if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { + return null; + } + + return { + id: cancellationRefundEvents[0].id, + escrowAddress: cancellationRefundEvents[0].escrowAddress, + receiver: cancellationRefundEvents[0].receiver, + amount: BigInt(cancellationRefundEvents[0].amount), + block: Number(cancellationRefundEvents[0].block), + timestamp: Number(cancellationRefundEvents[0].timestamp) * 1000, + txHash: cancellationRefundEvents[0].txHash, + }; + } +} + +function mapEscrow(e: EscrowData, chainId: ChainId | number): IEscrow { + return { + id: e.id, + address: e.address, + amountPaid: BigInt(e.amountPaid), + balance: BigInt(e.balance), + count: Number(e.count), + factoryAddress: e.factoryAddress, + finalResultsUrl: e.finalResultsUrl, + finalResultsHash: e.finalResultsHash, + intermediateResultsUrl: e.intermediateResultsUrl, + intermediateResultsHash: e.intermediateResultsHash, + launcher: e.launcher, + jobRequesterId: e.jobRequesterId, + manifestHash: e.manifestHash, + manifest: e.manifest, + recordingOracle: e.recordingOracle, + reputationOracle: e.reputationOracle, + exchangeOracle: e.exchangeOracle, + recordingOracleFee: e.recordingOracleFee + ? Number(e.recordingOracleFee) + : null, + reputationOracleFee: e.reputationOracleFee + ? Number(e.reputationOracleFee) + : null, + exchangeOracleFee: e.exchangeOracleFee ? Number(e.exchangeOracleFee) : null, + status: e.status, + token: e.token, + totalFundedAmount: BigInt(e.totalFundedAmount), + createdAt: Number(e.createdAt) * 1000, + chainId: Number(chainId), + }; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts new file mode 100644 index 0000000000..4424ee3911 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts @@ -0,0 +1,2 @@ +export { EscrowClient } from './escrow_client'; +export { EscrowUtils } from './escrow_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts index 2135d3ca7c..2490d561ed 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts @@ -42,7 +42,12 @@ const EVENT_DAY_DATA_FRAGMENT = gql` dailyEscrowCount dailyWorkerCount dailyPayoutCount - dailyHMTPayoutAmount + } +`; + +const HMT_EVENT_DAY_DATA_FRAGMENT = gql` + fragment HMTEventDayDataFields on EventDayData { + timestamp dailyHMTTransferCount dailyHMTTransferAmount dailyUniqueSenders @@ -102,3 +107,34 @@ export const GET_EVENT_DAY_DATA_QUERY = (params: IStatisticsFilter) => { ${EVENT_DAY_DATA_FRAGMENT} `; }; + +export const GET_HMT_EVENT_DAY_DATA_QUERY = (params: IStatisticsFilter) => { + const { from, to } = params; + const WHERE_CLAUSE = ` + where: { + ${from !== undefined ? `timestamp_gte: $from` : ''} + ${to !== undefined ? `timestamp_lte: $to` : ''} + } + `; + + return gql` + query GetHMTDayData( + $from: Int, + $to: Int, + $orderDirection: String + $first: Int + $skip: Int + ) { + eventDayDatas( + ${WHERE_CLAUSE}, + orderBy: timestamp, + orderDirection: $orderDirection, + first: $first, + skip: $skip + ) { + ...HMTEventDayDataFields + } + } + ${HMT_EVENT_DAY_DATA_FRAGMENT} + `; +}; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts index fed501218f..295acaac4a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts @@ -95,7 +95,10 @@ export type EventDayData = { dailyEscrowCount: string; dailyWorkerCount: string; dailyPayoutCount: string; - dailyHMTPayoutAmount: string; +}; + +export type HMTEventDayData = { + timestamp: string; dailyHMTTransferCount: string; dailyHMTTransferAmount: string; dailyUniqueSenders: string; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/index.ts index 0e07f51949..0f8326f101 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/index.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/index.ts @@ -1,12 +1,3 @@ -import { StakingClient, StakingUtils } from './staking'; -import { KVStoreClient, KVStoreUtils } from './kvstore'; -import { EscrowClient, EscrowUtils } from './escrow'; -import { StatisticsUtils } from './statistics'; -import { Encryption, EncryptionUtils, MessageDataType } from './encryption'; -import { OperatorUtils } from './operator'; -import { TransactionUtils } from './transaction'; -import { WorkerUtils } from './worker'; - export * from './constants'; export * from './types'; export * from './enums'; @@ -21,21 +12,16 @@ export { TransactionReplaced, ContractExecutionError, InvalidEthereumAddressError, - InvalidKeyError, + SubgraphBadIndexerError, + SubgraphRequestError, } from './error'; -export { - StakingClient, - KVStoreClient, - KVStoreUtils, - EscrowClient, - EscrowUtils, - StatisticsUtils, - Encryption, - EncryptionUtils, - OperatorUtils, - TransactionUtils, - WorkerUtils, - StakingUtils, - MessageDataType, -}; +export { StakingClient, StakingUtils } from './staking'; +export { KVStoreClient, KVStoreUtils } from './kvstore'; +export { EscrowClient, EscrowUtils } from './escrow'; +export { StatisticsUtils } from './statistics'; +export { Encryption, EncryptionUtils } from './encryption'; +export type { MessageDataType } from './encryption'; +export { OperatorUtils } from './operator'; +export { TransactionUtils } from './transaction'; +export { WorkerUtils } from './worker'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index e9bca41378..cac654ae4c 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -87,9 +87,6 @@ export interface IEscrowConfig { recordingOracle: string; reputationOracle: string; exchangeOracle: string; - recordingOracleFee: bigint; - reputationOracleFee: bigint; - exchangeOracleFee: bigint; manifest: string; manifestHash: string; } @@ -254,9 +251,7 @@ export interface IWorkerStatistics { export interface IDailyPayment { timestamp: number; - totalAmountPaid: bigint; totalCount: number; - averageAmountPerWorker: bigint; } export interface IPaymentStatistics { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts new file mode 100644 index 0000000000..507a0f6d62 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts @@ -0,0 +1,2 @@ +export { KVStoreClient } from './kvstore_client'; +export { KVStoreUtils } from './kvstore_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts similarity index 52% rename from packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts rename to packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts index 22635167d3..83e8e7beff 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts @@ -3,28 +3,20 @@ import { KVStore__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { KVStoreKeys, NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId } from './enums'; +import { BaseEthersClient } from '../base'; +import { NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorInvalidAddress, - ErrorInvalidHash, ErrorInvalidUrl, ErrorKVStoreArrayLength, ErrorKVStoreEmptyKey, ErrorProviderDoesNotExist, ErrorUnsupportedChainID, - InvalidKeyError, -} from './error'; -import { NetworkData, TransactionOverrides } from './types'; -import { getSubgraphUrl, customGqlFetch, isValidUrl } from './utils'; -import { - GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, - GET_KVSTORE_BY_ADDRESS_QUERY, -} from './graphql/queries/kvstore'; -import { KVStoreData } from './graphql'; -import { IKVStore, SubgraphOptions } from './interfaces'; +} from '../error'; +import { NetworkData, TransactionOverrides } from '../types'; +import { isValidUrl } from '../utils'; /** * Client for interacting with the KVStore contract. * @@ -159,13 +151,17 @@ export class KVStoreClient extends BaseEthersClient { txOptions: TransactionOverrides = {} ): Promise { if (key === '') throw ErrorKVStoreEmptyKey; + try { await this.sendTxAndWait( (overrides) => this.contract.set(key, value, overrides), txOptions ); } catch (e) { - if (e instanceof Error) throw Error(`Failed to set value: ${e.message}`); + if (e instanceof Error) { + throw Error(`Failed to set value: ${e.message}`); + } + throw e; } } @@ -202,8 +198,10 @@ export class KVStoreClient extends BaseEthersClient { txOptions ); } catch (e) { - if (e instanceof Error) + if (e instanceof Error) { throw Error(`Failed to set bulk values: ${e.message}`); + } + throw e; } } @@ -249,8 +247,10 @@ export class KVStoreClient extends BaseEthersClient { txOptions ); } catch (e) { - if (e instanceof Error) + if (e instanceof Error) { throw Error(`Failed to set URL and hash: ${e.message}`); + } + throw e; } } /** @@ -270,236 +270,22 @@ export class KVStoreClient extends BaseEthersClient { * ``` */ public async get(address: string, key: string): Promise { - if (key === '') throw ErrorKVStoreEmptyKey; - if (!ethers.isAddress(address)) throw ErrorInvalidAddress; - - try { - const result = await this.contract?.get(address, key); - return result; - } catch (e) { - if (e instanceof Error) throw Error(`Failed to get value: ${e.message}`); - return e; + if (key === '') { + throw ErrorKVStoreEmptyKey; } - } -} - -/** - * Utility helpers for KVStore-related queries. - * - * @example - * ```ts - * import { ChainId, KVStoreUtils } from '@human-protocol/sdk'; - * - * const kvStoreData = await KVStoreUtils.getKVStoreData( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * console.log('KVStore data:', kvStoreData); - * ``` - */ -export class KVStoreUtils { - /** - * This function returns the KVStore data for a given address. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address of the KVStore - * @param options - Optional configuration for subgraph requests. - * @returns KVStore data - * @throws ErrorUnsupportedChainID If the network's chainId is not supported - * @throws ErrorInvalidAddress If the address is invalid - * - * @example - * ```ts - * const kvStoreData = await KVStoreUtils.getKVStoreData( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * console.log('KVStore data:', kvStoreData); - * ``` - */ - public static async getKVStoreData( - chainId: ChainId, - address: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - if (address && !ethers.isAddress(address)) { + if (!ethers.isAddress(address)) { throw ErrorInvalidAddress; } - const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( - getSubgraphUrl(networkData), - GET_KVSTORE_BY_ADDRESS_QUERY(), - { address: address.toLowerCase() }, - options - ); - - const kvStoreData = kvstores.map((item) => ({ - key: item.key, - value: item.value, - })); - - return kvStoreData || []; - } - - /** - * Gets the value of a key-value pair in the KVStore using the subgraph. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the key value. - * @param key - Key to obtain the value. - * @param options - Optional configuration for subgraph requests. - * @returns Value of the key. - * @throws ErrorUnsupportedChainID If the network's chainId is not supported - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorKVStoreEmptyKey If the key is empty - * @throws InvalidKeyError If the key is not found - * - * @example - * ```ts - * const value = await KVStoreUtils.get( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890', - * 'role' - * ); - * console.log('Value:', value); - * ``` - */ - public static async get( - chainId: ChainId, - address: string, - key: string, - options?: SubgraphOptions - ): Promise { - if (key === '') throw ErrorKVStoreEmptyKey; - if (!ethers.isAddress(address)) throw ErrorInvalidAddress; - - const networkData = NETWORKS[chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( - getSubgraphUrl(networkData), - GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), - { address: address.toLowerCase(), key }, - options - ); - - if (!kvstores || kvstores.length === 0) { - throw new InvalidKeyError(key, address); - } - - return kvstores[0].value; - } - - /** - * Gets the URL value of the given entity, and verifies its hash. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the URL value. - * @param urlKey - Configurable URL key. `url` by default. - * @param options - Optional configuration for subgraph requests. - * @returns URL value for the given address if it exists, and the content is valid - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorInvalidHash If the hash verification fails - * @throws Error If fetching URL or hash fails - * - * @example - * ```ts - * const url = await KVStoreUtils.getFileUrlAndVerifyHash( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890' - * ); - * console.log('Verified URL:', url); - * ``` - */ - public static async getFileUrlAndVerifyHash( - chainId: ChainId, - address: string, - urlKey = 'url', - options?: SubgraphOptions - ): Promise { - if (!ethers.isAddress(address)) throw ErrorInvalidAddress; - const hashKey = urlKey + '_hash'; - - let url = '', - hash = ''; - - try { - url = await this.get(chainId, address, urlKey, options); - } catch (e) { - if (e instanceof Error) throw Error(`Failed to get URL: ${e.message}`); - } - - // Return empty string - if (!url?.length) { - return ''; - } - try { - hash = await this.get(chainId, address, hashKey); + const result = await this.contract?.get(address, key); + return result; } catch (e) { - if (e instanceof Error) throw Error(`Failed to get Hash: ${e.message}`); - } - - const content = await fetch(url).then((res) => res.text()); - const contentHash = ethers.keccak256(ethers.toUtf8Bytes(content)); - - const formattedHash = hash?.replace(/^0x/, ''); - const formattedContentHash = contentHash?.replace(/^0x/, ''); - - if (formattedHash !== formattedContentHash) { - throw ErrorInvalidHash; + if (e instanceof Error) { + throw Error(`Failed to get value: ${e.message}`); + } + throw e; } - - return url; - } - - /** - * Gets the public key of the given entity, and verifies its hash. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the public key. - * @param options - Optional configuration for subgraph requests. - * @returns Public key for the given address if it exists, and the content is valid - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorInvalidHash If the hash verification fails - * @throws Error If fetching the public key fails - * - * @example - * ```ts - * const publicKey = await KVStoreUtils.getPublicKey( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890' - * ); - * console.log('Public key:', publicKey); - * ``` - */ - public static async getPublicKey( - chainId: ChainId, - address: string, - options?: SubgraphOptions - ): Promise { - const publicKeyUrl = await this.getFileUrlAndVerifyHash( - chainId, - address, - KVStoreKeys.publicKey, - options - ); - - if (publicKeyUrl === '') { - return ''; - } - - const publicKey = await fetch(publicKeyUrl).then((res) => res.text()); - - return publicKey; } } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts new file mode 100644 index 0000000000..0b232f7819 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts @@ -0,0 +1,244 @@ +import { ethers } from 'ethers'; +import { KVStoreKeys, NETWORKS } from '../constants'; +import { ChainId } from '../enums'; +import { + ErrorInvalidAddress, + ErrorInvalidHash, + ErrorKVStoreEmptyKey, + ErrorUnsupportedChainID, +} from '../error'; +import { KVStoreData } from '../graphql'; +import { + GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, + GET_KVSTORE_BY_ADDRESS_QUERY, +} from '../graphql/queries/kvstore'; +import { IKVStore, SubgraphOptions } from '../interfaces'; +import { customGqlFetch, getSubgraphUrl } from '../utils'; +/** + * Utility helpers for KVStore-related queries. + * + * @example + * ```ts + * import { ChainId, KVStoreUtils } from '@human-protocol/sdk'; + * + * const kvStoreData = await KVStoreUtils.getKVStoreData( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * console.log('KVStore data:', kvStoreData); + * ``` + */ +export class KVStoreUtils { + /** + * This function returns the KVStore data for a given address. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address of the KVStore + * @param options - Optional configuration for subgraph requests. + * @returns KVStore data + * @throws ErrorUnsupportedChainID If the network's chainId is not supported + * @throws ErrorInvalidAddress If the address is invalid + * + * @example + * ```ts + * const kvStoreData = await KVStoreUtils.getKVStoreData( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * console.log('KVStore data:', kvStoreData); + * ``` + */ + public static async getKVStoreData( + chainId: ChainId, + address: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + if (address && !ethers.isAddress(address)) { + throw ErrorInvalidAddress; + } + + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( + getSubgraphUrl(networkData), + GET_KVSTORE_BY_ADDRESS_QUERY(), + { address: address.toLowerCase() }, + options + ); + + const kvStoreData = kvstores.map((item) => ({ + key: item.key, + value: item.value, + })); + + return kvStoreData || []; + } + + /** + * Gets the value of a key-value pair in the KVStore using the subgraph. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the key value. + * @param key - Key to obtain the value. + * @param options - Optional configuration for subgraph requests. + * @returns Value of the key. + * @throws ErrorUnsupportedChainID If the network's chainId is not supported + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorKVStoreEmptyKey If the key is empty + * + * @example + * ```ts + * const value = await KVStoreUtils.get( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890', + * 'role' + * ); + * console.log('Value:', value); + * ``` + */ + public static async get( + chainId: ChainId, + address: string, + key: string, + options?: SubgraphOptions + ): Promise { + if (key === '') throw ErrorKVStoreEmptyKey; + if (!ethers.isAddress(address)) throw ErrorInvalidAddress; + + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( + getSubgraphUrl(networkData), + GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), + { address: address.toLowerCase(), key }, + options + ); + + if (!kvstores || kvstores.length === 0) { + return ''; + } + + return kvstores[0].value; + } + + /** + * Gets the URL value of the given entity, and verifies its hash. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the URL value. + * @param urlKey - Configurable URL key. `url` by default. + * @param options - Optional configuration for subgraph requests. + * @returns URL value for the given address if it exists, and the content is valid + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorInvalidHash If the hash verification fails + * @throws Error If fetching URL or hash fails + * + * @example + * ```ts + * const url = await KVStoreUtils.getFileUrlAndVerifyHash( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890' + * ); + * console.log('Verified URL:', url); + * ``` + */ + public static async getFileUrlAndVerifyHash( + chainId: ChainId, + address: string, + urlKey = 'url', + options?: SubgraphOptions + ): Promise { + if (!ethers.isAddress(address)) throw ErrorInvalidAddress; + const hashKey = urlKey + '_hash'; + + let url: string; + + try { + url = await this.get(chainId, address, urlKey, options); + } catch (e) { + if (e instanceof Error) { + throw Error(`Failed to get URL: ${e.message}`); + } + throw e; + } + + if (!url) { + throw new Error('No URL found for the given address and key'); + } + + let hash: string; + try { + hash = await this.get(chainId, address, hashKey); + } catch (e) { + if (e instanceof Error) { + throw Error(`Failed to get Hash: ${e.message}`); + } + throw e; + } + + if (!hash) { + throw new Error('No hash found for the given address and url'); + } + + const content = await fetch(url).then((res) => res.text()); + const contentHash = ethers.keccak256(ethers.toUtf8Bytes(content)); + + const formattedHash = hash?.replace(/^0x/, ''); + const formattedContentHash = contentHash?.replace(/^0x/, ''); + + if (formattedHash !== formattedContentHash) { + throw ErrorInvalidHash; + } + + return url; + } + + /** + * Gets the public key of the given entity, and verifies its hash. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the public key. + * @param options - Optional configuration for subgraph requests. + * @returns Public key for the given address if it exists, and the content is valid + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorInvalidHash If the hash verification fails + * @throws Error If fetching the public key fails + * + * @example + * ```ts + * const publicKey = await KVStoreUtils.getPublicKey( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890' + * ); + * console.log('Public key:', publicKey); + * ``` + */ + public static async getPublicKey( + chainId: ChainId, + address: string, + options?: SubgraphOptions + ): Promise { + const publicKeyUrl = await this.getFileUrlAndVerifyHash( + chainId, + address, + KVStoreKeys.publicKey, + options + ); + + if (publicKeyUrl === '') { + return ''; + } + + const publicKey = await fetch(publicKeyUrl).then((res) => res.text()); + + return publicKey; + } +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts new file mode 100644 index 0000000000..9cb7c0df41 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts @@ -0,0 +1 @@ +export { OperatorUtils } from './operator_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts similarity index 96% rename from packages/sdk/typescript/human-protocol-sdk/src/operator.ts rename to packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts index ad0dd150d7..b7a84b9668 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts @@ -4,27 +4,27 @@ import { IOperatorsFilter, IReward, SubgraphOptions, -} from './interfaces'; -import { GET_REWARD_ADDED_EVENTS_QUERY } from './graphql/queries/reward'; +} from '../interfaces'; +import { GET_REWARD_ADDED_EVENTS_QUERY } from '../graphql/queries/reward'; import { IOperatorSubgraph, IReputationNetworkSubgraph, RewardAddedEventData, -} from './graphql'; +} from '../graphql'; import { GET_LEADER_QUERY, GET_LEADERS_QUERY, GET_REPUTATION_NETWORK_QUERY, -} from './graphql/queries/operator'; +} from '../graphql/queries/operator'; import { ethers } from 'ethers'; import { ErrorInvalidSlasherAddressProvided, ErrorInvalidStakerAddressProvided, ErrorUnsupportedChainID, -} from './error'; -import { getSubgraphUrl, customGqlFetch } from './utils'; -import { ChainId, OrderDirection } from './enums'; -import { NETWORKS } from './constants'; +} from '../error'; +import { getSubgraphUrl, customGqlFetch } from '../utils'; +import { ChainId, OrderDirection } from '../enums'; +import { NETWORKS } from '../constants'; /** * Utility helpers for operator-related queries. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts new file mode 100644 index 0000000000..714d1b4561 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts @@ -0,0 +1,2 @@ +export { StakingClient } from './staking_client'; +export { StakingUtils } from './staking_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts similarity index 71% rename from packages/sdk/typescript/human-protocol-sdk/src/staking.ts rename to packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts index b0f0284db6..65c747140a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts @@ -7,10 +7,10 @@ import { Staking__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId, OrderDirection } from './enums'; +import { BaseEthersClient } from '../base'; +import { NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorEscrowAddressIsNotProvidedByFactory, ErrorInvalidEscrowAddressProvided, @@ -19,22 +19,11 @@ import { ErrorInvalidStakingValueSign, ErrorInvalidStakingValueType, ErrorProviderDoesNotExist, - ErrorStakerNotFound, ErrorUnsupportedChainID, -} from './error'; -import { - IStaker, - IStakersFilter, - StakerInfo, - SubgraphOptions, -} from './interfaces'; -import { StakerData } from './graphql'; -import { NetworkData, TransactionOverrides } from './types'; -import { getSubgraphUrl, customGqlFetch, throwError } from './utils'; -import { - GET_STAKER_BY_ADDRESS_QUERY, - GET_STAKERS_QUERY, -} from './graphql/queries/staking'; +} from '../error'; +import { StakerInfo } from '../interfaces'; +import { NetworkData, TransactionOverrides } from '../types'; +import { throwError } from '../utils'; /** * Client for staking actions on HUMAN Protocol. @@ -456,158 +445,3 @@ export class StakingClient extends BaseEthersClient { } } } - -/** - * Utility helpers for Staking-related queries. - * - * @example - * ```ts - * import { StakingUtils, ChainId } from '@human-protocol/sdk'; - * - * const staker = await StakingUtils.getStaker( - * ChainId.POLYGON_AMOY, - * '0xYourStakerAddress' - * ); - * console.log('Staked amount:', staker.stakedAmount); - * ``` - */ -export class StakingUtils { - /** - * Gets staking info for a staker from the subgraph. - * - * @param chainId - Network in which the staking contract is deployed - * @param stakerAddress - Address of the staker - * @param options - Optional configuration for subgraph requests. - * @returns Staker info from subgraph - * @throws ErrorInvalidStakerAddressProvided If the staker address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorStakerNotFound If the staker is not found - * - * @example - * ```ts - * import { StakingUtils, ChainId } from '@human-protocol/sdk'; - * - * const staker = await StakingUtils.getStaker( - * ChainId.POLYGON_AMOY, - * '0xYourStakerAddress' - * ); - * console.log('Staked amount:', staker.stakedAmount); - * ``` - */ - public static async getStaker( - chainId: ChainId, - stakerAddress: string, - options?: SubgraphOptions - ): Promise { - if (!ethers.isAddress(stakerAddress)) { - throw ErrorInvalidStakerAddressProvided; - } - - const networkData: NetworkData | undefined = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { staker } = await customGqlFetch<{ staker: StakerData }>( - getSubgraphUrl(networkData), - GET_STAKER_BY_ADDRESS_QUERY, - { id: stakerAddress.toLowerCase() }, - options - ); - - if (!staker) { - throw ErrorStakerNotFound; - } - - return mapStaker(staker); - } - - /** - * Gets all stakers from the subgraph with filters, pagination and ordering. - * - * @param filter - Stakers filter with pagination and ordering - * @param options - Optional configuration for subgraph requests. - * @returns Array of stakers - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const filter = { - * chainId: ChainId.POLYGON_AMOY, - * minStakedAmount: '1000000000000000000', // 1 token in WEI - * }; - * const stakers = await StakingUtils.getStakers(filter); - * console.log('Stakers:', stakers.length); - * ``` - */ - public static async getStakers( - filter: IStakersFilter, - options?: SubgraphOptions - ): Promise { - const first = - filter.first !== undefined ? Math.min(filter.first, 1000) : 10; - const skip = filter.skip || 0; - const orderDirection = filter.orderDirection || OrderDirection.DESC; - const orderBy = filter.orderBy || 'lastDepositTimestamp'; - - const networkData = NETWORKS[filter.chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { stakers } = await customGqlFetch<{ stakers: StakerData[] }>( - getSubgraphUrl(networkData), - GET_STAKERS_QUERY(filter), - { - minStakedAmount: filter.minStakedAmount - ? filter.minStakedAmount - : undefined, - maxStakedAmount: filter.maxStakedAmount - ? filter.maxStakedAmount - : undefined, - minLockedAmount: filter.minLockedAmount - ? filter.minLockedAmount - : undefined, - maxLockedAmount: filter.maxLockedAmount - ? filter.maxLockedAmount - : undefined, - minWithdrawnAmount: filter.minWithdrawnAmount - ? filter.minWithdrawnAmount - : undefined, - maxWithdrawnAmount: filter.maxWithdrawnAmount - ? filter.maxWithdrawnAmount - : undefined, - minSlashedAmount: filter.minSlashedAmount - ? filter.minSlashedAmount - : undefined, - maxSlashedAmount: filter.maxSlashedAmount - ? filter.maxSlashedAmount - : undefined, - orderBy: orderBy, - orderDirection: orderDirection, - first: first, - skip: skip, - }, - options - ); - if (!stakers) { - return []; - } - - return stakers.map((s) => mapStaker(s)); - } -} - -function mapStaker(s: StakerData): IStaker { - return { - address: s.address, - stakedAmount: BigInt(s.stakedAmount), - lockedAmount: BigInt(s.lockedAmount), - withdrawableAmount: BigInt(s.withdrawnAmount), - slashedAmount: BigInt(s.slashedAmount), - lockedUntil: Number(s.lockedUntilTimestamp) * 1000, - lastDepositTimestamp: Number(s.lastDepositTimestamp) * 1000, - }; -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts new file mode 100644 index 0000000000..3c17984bd7 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts @@ -0,0 +1,170 @@ +import { ethers } from 'ethers'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { + ErrorInvalidStakerAddressProvided, + ErrorStakerNotFound, + ErrorUnsupportedChainID, +} from '../error'; +import { StakerData } from '../graphql'; +import { + GET_STAKER_BY_ADDRESS_QUERY, + GET_STAKERS_QUERY, +} from '../graphql/queries/staking'; +import { IStaker, IStakersFilter, SubgraphOptions } from '../interfaces'; +import { NetworkData } from '../types'; +import { customGqlFetch, getSubgraphUrl } from '../utils'; +/** + * Utility helpers for Staking-related queries. + * + * @example + * ```ts + * import { StakingUtils, ChainId } from '@human-protocol/sdk'; + * + * const staker = await StakingUtils.getStaker( + * ChainId.POLYGON_AMOY, + * '0xYourStakerAddress' + * ); + * console.log('Staked amount:', staker.stakedAmount); + * ``` + */ +export class StakingUtils { + /** + * Gets staking info for a staker from the subgraph. + * + * @param chainId - Network in which the staking contract is deployed + * @param stakerAddress - Address of the staker + * @param options - Optional configuration for subgraph requests. + * @returns Staker info from subgraph + * @throws ErrorInvalidStakerAddressProvided If the staker address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorStakerNotFound If the staker is not found + * + * @example + * ```ts + * import { StakingUtils, ChainId } from '@human-protocol/sdk'; + * + * const staker = await StakingUtils.getStaker( + * ChainId.POLYGON_AMOY, + * '0xYourStakerAddress' + * ); + * console.log('Staked amount:', staker.stakedAmount); + * ``` + */ + public static async getStaker( + chainId: ChainId, + stakerAddress: string, + options?: SubgraphOptions + ): Promise { + if (!ethers.isAddress(stakerAddress)) { + throw ErrorInvalidStakerAddressProvided; + } + + const networkData: NetworkData | undefined = NETWORKS[chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { staker } = await customGqlFetch<{ staker: StakerData }>( + getSubgraphUrl(networkData), + GET_STAKER_BY_ADDRESS_QUERY, + { id: stakerAddress.toLowerCase() }, + options + ); + + if (!staker) { + throw ErrorStakerNotFound; + } + + return mapStaker(staker); + } + + /** + * Gets all stakers from the subgraph with filters, pagination and ordering. + * + * @param filter - Stakers filter with pagination and ordering + * @param options - Optional configuration for subgraph requests. + * @returns Array of stakers + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const filter = { + * chainId: ChainId.POLYGON_AMOY, + * minStakedAmount: '1000000000000000000', // 1 token in WEI + * }; + * const stakers = await StakingUtils.getStakers(filter); + * console.log('Stakers:', stakers.length); + * ``` + */ + public static async getStakers( + filter: IStakersFilter, + options?: SubgraphOptions + ): Promise { + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + const orderBy = filter.orderBy || 'lastDepositTimestamp'; + + const networkData = NETWORKS[filter.chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { stakers } = await customGqlFetch<{ stakers: StakerData[] }>( + getSubgraphUrl(networkData), + GET_STAKERS_QUERY(filter), + { + minStakedAmount: filter.minStakedAmount + ? filter.minStakedAmount + : undefined, + maxStakedAmount: filter.maxStakedAmount + ? filter.maxStakedAmount + : undefined, + minLockedAmount: filter.minLockedAmount + ? filter.minLockedAmount + : undefined, + maxLockedAmount: filter.maxLockedAmount + ? filter.maxLockedAmount + : undefined, + minWithdrawnAmount: filter.minWithdrawnAmount + ? filter.minWithdrawnAmount + : undefined, + maxWithdrawnAmount: filter.maxWithdrawnAmount + ? filter.maxWithdrawnAmount + : undefined, + minSlashedAmount: filter.minSlashedAmount + ? filter.minSlashedAmount + : undefined, + maxSlashedAmount: filter.maxSlashedAmount + ? filter.maxSlashedAmount + : undefined, + orderBy: orderBy, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + options + ); + if (!stakers) { + return []; + } + + return stakers.map((s) => mapStaker(s)); + } +} + +function mapStaker(s: StakerData): IStaker { + return { + address: s.address, + stakedAmount: BigInt(s.stakedAmount), + lockedAmount: BigInt(s.lockedAmount), + withdrawableAmount: BigInt(s.withdrawnAmount), + slashedAmount: BigInt(s.slashedAmount), + lockedUntil: Number(s.lockedUntilTimestamp) * 1000, + lastDepositTimestamp: Number(s.lastDepositTimestamp) * 1000, + }; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts new file mode 100644 index 0000000000..9e5d6b2493 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts @@ -0,0 +1 @@ +export { StatisticsUtils } from './statistics_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts similarity index 93% rename from packages/sdk/typescript/human-protocol-sdk/src/statistics.ts rename to packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts index fd1192a449..aceb9e63e0 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts @@ -1,15 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { OrderDirection } from './enums'; +import { OrderDirection } from '../enums'; import { EscrowStatisticsData, EventDayData, + HMTEventDayData, GET_ESCROW_STATISTICS_QUERY, GET_EVENT_DAY_DATA_QUERY, + GET_HMT_EVENT_DAY_DATA_QUERY, GET_HMTOKEN_STATISTICS_QUERY, GET_HOLDERS_QUERY, HMTHolderData, HMTStatisticsData, -} from './graphql'; +} from '../graphql'; import { IDailyHMT, IEscrowStatistics, @@ -20,14 +22,15 @@ import { IStatisticsFilter, IWorkerStatistics, SubgraphOptions, -} from './interfaces'; -import { NetworkData } from './types'; +} from '../interfaces'; +import { NetworkData } from '../types'; import { + getHMTSubgraphUrl, getSubgraphUrl, getUnixTimestamp, customGqlFetch, throwError, -} from './utils'; +} from '../utils'; /** * Utility class for statistics-related queries. @@ -253,9 +256,7 @@ export class StatisticsUtils { * ```ts * interface IDailyPayment { * timestamp: number; - * totalAmountPaid: bigint; * totalCount: number; - * averageAmountPerWorker: bigint; * }; * * interface IPaymentStatistics { @@ -274,14 +275,7 @@ export class StatisticsUtils { * * const networkData = NETWORKS[ChainId.POLYGON_AMOY]; * const paymentStats = await StatisticsUtils.getPaymentStatistics(networkData); - * console.log( - * 'Payment statistics:', - * paymentStats.dailyPaymentsData.map((p) => ({ - * ...p, - * totalAmountPaid: p.totalAmountPaid.toString(), - * averageAmountPerWorker: p.averageAmountPerWorker.toString(), - * })) - * ); + * console.log('Payment statistics:', paymentStats.dailyPaymentsData); * * const paymentStatsRange = await StatisticsUtils.getPaymentStatistics( * networkData, @@ -323,13 +317,7 @@ export class StatisticsUtils { return { dailyPaymentsData: eventDayDatas.map((eventDayData) => ({ timestamp: +eventDayData.timestamp * 1000, - totalAmountPaid: BigInt(eventDayData.dailyHMTPayoutAmount), totalCount: +eventDayData.dailyPayoutCount, - averageAmountPerWorker: - eventDayData.dailyWorkerCount === '0' - ? BigInt(0) - : BigInt(eventDayData.dailyHMTPayoutAmount) / - BigInt(eventDayData.dailyWorkerCount), })), }; } catch (e: any) { @@ -369,7 +357,7 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const { hmtokenStatistics } = await customGqlFetch<{ hmtokenStatistics: HMTStatisticsData; }>(subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY, options); @@ -412,7 +400,7 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const { address, orderDirection } = params; const query = GET_HOLDERS_QUERY(address); @@ -490,17 +478,17 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; const { eventDayDatas } = await customGqlFetch<{ - eventDayDatas: EventDayData[]; + eventDayDatas: HMTEventDayData[]; }>( subgraphUrl, - GET_EVENT_DAY_DATA_QUERY(filter), + GET_HMT_EVENT_DAY_DATA_QUERY(filter), { from: filter.from ? getUnixTimestamp(filter.from) : undefined, to: filter.to ? getUnixTimestamp(filter.to) : undefined, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts new file mode 100644 index 0000000000..d4461b9f3f --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts @@ -0,0 +1 @@ +export { TransactionUtils } from './transaction_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts similarity index 97% rename from packages/sdk/typescript/human-protocol-sdk/src/transaction.ts rename to packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts index d2802feff7..d055ef27c4 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts @@ -1,23 +1,23 @@ import { ethers } from 'ethers'; -import { NETWORKS } from './constants'; -import { ChainId, OrderDirection } from './enums'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; import { ErrorCannotUseDateAndBlockSimultaneously, ErrorInvalidHashProvided, ErrorUnsupportedChainID, -} from './error'; -import { TransactionData } from './graphql'; +} from '../error'; +import { TransactionData } from '../graphql'; import { GET_TRANSACTION_QUERY, GET_TRANSACTIONS_QUERY, -} from './graphql/queries/transaction'; +} from '../graphql/queries/transaction'; import { InternalTransaction, ITransaction, ITransactionsFilter, SubgraphOptions, -} from './interfaces'; -import { getSubgraphUrl, getUnixTimestamp, customGqlFetch } from './utils'; +} from '../interfaces'; +import { getSubgraphUrl, getUnixTimestamp, customGqlFetch } from '../utils'; /** * Utility class for transaction-related queries. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/types.ts index 9ced185a32..b6ea17852a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/types.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/types.ts @@ -76,6 +76,14 @@ export type NetworkData = { * Subgraph URL API key */ subgraphUrlApiKey: string; + /** + * HMT statistics subgraph URL + */ + hmtSubgraphUrl?: string; + /** + * HMT statistics subgraph URL API key + */ + hmtSubgraphUrlApiKey?: string; /** * Old subgraph URL */ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index 02ed1bd9aa..59a5090850 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -13,6 +13,8 @@ import { NonceExpired, NumericFault, ReplacementUnderpriced, + SubgraphBadIndexerError, + SubgraphRequestError, TransactionReplaced, WarnSubgraphApiKeyNotProvided, } from './error'; @@ -78,6 +80,24 @@ export const isValidJson = (input: string): boolean => { } }; +/** + * Extracts a readable message from unknown error values. + * + * @param error - Unknown error value + * @returns Human-readable error message + */ +export const getErrorMessage = (error: unknown): string => { + if (error instanceof Error) { + return error.message; + } + + try { + return JSON.stringify(error); + } catch { + return String(error); + } +}; + /** * Gets the subgraph URL for the given network, using API key if available. * @@ -96,6 +116,26 @@ export const getSubgraphUrl = (networkData: NetworkData) => { return subgraphUrl; }; +/** + * Gets the HMT statistics subgraph URL for the given network. + * Falls back to the default subgraph URL when a dedicated HMT endpoint is not configured. + * + * @param networkData - The network data containing subgraph URLs + * @returns The HMT statistics subgraph URL with API key if available + */ +export const getHMTSubgraphUrl = (networkData: NetworkData) => { + let subgraphUrl = networkData.hmtSubgraphUrl || networkData.subgraphUrl; + if (process.env.SUBGRAPH_API_KEY) { + subgraphUrl = + networkData.hmtSubgraphUrlApiKey || networkData.subgraphUrlApiKey; + } else if (networkData.chainId !== ChainId.LOCALHOST) { + // eslint-disable-next-line no-console + console.warn(WarnSubgraphApiKeyNotProvided); + } + + return subgraphUrl; +}; + /** * Converts a Date object to Unix timestamp (seconds since epoch). * @@ -117,6 +157,38 @@ export const isIndexerError = (error: any): boolean => { return errorMessage.toLowerCase().includes('bad indexers'); }; +const getSubgraphErrorMessage = (error: any): string => { + return ( + error?.response?.errors?.[0]?.message || + error?.message || + error?.toString?.() || + 'Subgraph request failed' + ); +}; + +const getSubgraphStatusCode = (error: any): number | undefined => { + if (typeof error?.response?.status === 'number') { + return error.response.status; + } + + if (typeof error?.status === 'number') { + return error.status; + } + + return undefined; +}; + +const toSubgraphError = (error: any, url: string): Error => { + const message = getSubgraphErrorMessage(error); + const statusCode = getSubgraphStatusCode(error); + + if (isIndexerError(error)) { + return new SubgraphBadIndexerError(message, url, statusCode); + } + + return new SubgraphRequestError(message, url, statusCode); +}; + const sleep = (ms: number): Promise => { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -154,7 +226,11 @@ export const customGqlFetch = async ( : undefined; if (!options) { - return await gqlFetch(url, query, variables, headers); + try { + return await gqlFetch(url, query, variables, headers); + } catch (error) { + throw toSubgraphError(error, url); + } } const hasMaxRetries = options.maxRetries !== undefined; @@ -177,10 +253,11 @@ export const customGqlFetch = async ( try { return await gqlFetch(targetUrl, query, variables, headers); } catch (error) { - lastError = error; + const wrappedError = toSubgraphError(error, targetUrl); + lastError = wrappedError; if (attempt === maxRetries || !isIndexerError(error)) { - throw error; + throw wrappedError; } const delay = baseDelay * attempt; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts new file mode 100644 index 0000000000..d6dfa1ca7b --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts @@ -0,0 +1 @@ +export { WorkerUtils } from './worker_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts similarity index 91% rename from packages/sdk/typescript/human-protocol-sdk/src/worker.ts rename to packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts index e76278bd3b..d26c847f8d 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts @@ -1,11 +1,11 @@ import { ethers } from 'ethers'; -import { NETWORKS } from './constants'; -import { ChainId, OrderDirection } from './enums'; -import { ErrorInvalidAddress, ErrorUnsupportedChainID } from './error'; -import { WorkerData } from './graphql'; -import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from './graphql/queries/worker'; -import { IWorker, IWorkersFilter, SubgraphOptions } from './interfaces'; -import { getSubgraphUrl, customGqlFetch } from './utils'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { ErrorInvalidAddress, ErrorUnsupportedChainID } from '../error'; +import { WorkerData } from '../graphql'; +import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from '../graphql/queries/worker'; +import { IWorker, IWorkersFilter, SubgraphOptions } from '../interfaces'; +import { getSubgraphUrl, customGqlFetch } from '../utils'; /** * Utility class for worker-related operations. 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 0e1bb87b9d..08579ea475 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts @@ -37,7 +37,6 @@ import { ErrorSigner, ErrorStoreResultsVersion, ErrorTooManyRecipients, - ErrorTotalFeeMustBeLessThanHundred, ErrorUnsupportedChainID, ErrorInvalidManifest, InvalidEthereumAddressError, @@ -75,6 +74,7 @@ describe('EscrowClient', () => { mockProvider = { provider: { getNetwork: vi.fn().mockResolvedValue({ chainId: ChainId.LOCALHOST }), + call: vi.fn(), }, }; mockSigner = { @@ -104,7 +104,7 @@ describe('EscrowClient', () => { remainingFunds: vi.fn(), reservedFunds: vi.fn(), manifestHash: vi.fn(), - manifestUrl: vi.fn(), + manifest: vi.fn(), finalResultsUrl: vi.fn(), token: vi.fn(), status: vi.fn(), @@ -119,6 +119,7 @@ describe('EscrowClient', () => { intermediateResultsHash: vi.fn(), launcher: vi.fn(), escrowFactory: vi.fn(), + target: ethers.ZeroAddress, }; mockEscrowFactoryContract = { @@ -431,94 +432,6 @@ describe('EscrowClient', () => { ).rejects.toThrow(ErrorInvalidExchangeOracleAddressProvided); }); - test('should throw an error if recordingOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 0n, - reputationOracleFee: 10n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if reputationOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 0n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - hash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if exchangeOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 10n, - exchangeOracleFee: 0n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if total fee > 100', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 40n, - reputationOracleFee: 40n, - exchangeOracleFee: 40n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorTotalFeeMustBeLessThanHundred); - }); - test('should throw an error if manifest is an empty string', async () => { const escrowConfig = { recordingOracle: ethers.ZeroAddress, @@ -595,9 +508,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -634,9 +544,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -677,9 +584,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, txOptions @@ -767,9 +671,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -866,82 +767,6 @@ describe('EscrowClient', () => { ).rejects.toThrow(ErrorEscrowAddressIsNotProvidedByFactory); }); - test('should throw an error if recordingOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 0n, - reputationOracleFee: 10n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if reputationOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 0n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if exchangeOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 10n, - exchangeOracleFee: 0n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if total fee > 100', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 40n, - reputationOracleFee: 40n, - exchangeOracleFee: 40n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorTotalFeeMustBeLessThanHundred); - }); - test('should throw an error if manifest is an empty string', async () => { const escrowConfig = { recordingOracle: ethers.ZeroAddress, @@ -986,9 +811,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, '{"foo":"bar"}', FAKE_HASH, {} @@ -1039,9 +861,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, {} @@ -1071,9 +890,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, {} @@ -1107,9 +923,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, txOptions @@ -3052,41 +2865,58 @@ describe('EscrowClient', () => { ); }); - test('should successfully getManifestUrl', async () => { + test('should successfully getManifest via manifest()', async () => { const escrowAddress = ethers.ZeroAddress; const url = FAKE_URL; escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl.mockReturnValue(url); + escrowClient.escrowContract.manifest.mockResolvedValueOnce(url); const manifestUrl = await escrowClient.getManifest(escrowAddress); expect(manifestUrl).toEqual(url); - expect(escrowClient.escrowContract.manifestUrl).toHaveBeenCalledWith(); + expect(escrowClient.escrowContract.manifest).toHaveBeenCalledTimes(1); + expect(mockProvider.provider.call).not.toHaveBeenCalled(); }); - test('should return the manifest string', async () => { + test('should fallback to manifestUrl() for legacy contracts', async () => { const escrowAddress = ethers.ZeroAddress; const manifestString = '{"foo":"bar"}'; + const manifestInterface = new ethers.Interface([ + 'function manifestUrl() view returns (string)', + ]); + escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl = vi - .fn() - .mockReturnValue(manifestString); + escrowClient.escrowContract.manifest.mockRejectedValueOnce( + new Error('manifest() not found') + ); + mockProvider.provider.call.mockResolvedValueOnce( + manifestInterface.encodeFunctionResult('manifestUrl', [manifestString]) + ); + const result = await escrowClient.getManifest(escrowAddress); + expect(result).toBe(manifestString); + expect(escrowClient.escrowContract.manifest).toHaveBeenCalledTimes(1); + expect(mockProvider.provider.call).toHaveBeenCalledTimes(1); }); - test('should throw an error if getManifestUrl fails', async () => { + test('should throw an error if manifest() and manifestUrl() fail', async () => { const escrowAddress = ethers.ZeroAddress; escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl.mockRejectedValueOnce( - new Error() + escrowClient.escrowContract.manifest.mockRejectedValueOnce( + new Error('manifest() failed') + ); + mockProvider.provider.call.mockRejectedValueOnce( + new Error('manifestUrl() failed') ); - await expect(escrowClient.getManifest(escrowAddress)).rejects.toThrow(); - - expect(escrowClient.escrowContract.manifestUrl).toHaveBeenCalledWith(); + await expect(escrowClient.getManifest(escrowAddress)).rejects.toThrow( + /Failed to fetch manifest using both manifest\(\) and manifestUrl\(\). .*manifest\(\) error: manifest\(\) failed\. .*manifestUrl\(\) error: manifestUrl\(\) failed\./ + ); + expect(escrowClient.escrowContract.manifest).toHaveBeenCalledTimes(1); + expect(mockProvider.provider.call).toHaveBeenCalledTimes(1); }); }); @@ -3712,7 +3542,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_ESCROWS_QUERY(filter), { chainId: ChainId.POLYGON_AMOY, @@ -4023,7 +3853,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_ESCROWS_QUERY(filter), { chainId: ChainId.POLYGON_AMOY, @@ -4129,7 +3959,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_ESCROWS_QUERY(filter), { chainId: ChainId.POLYGON_AMOY, @@ -4465,7 +4295,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_PAYOUTS_QUERY(filter), { escrowAddress: undefined, @@ -4514,7 +4344,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_PAYOUTS_QUERY(filter), { escrowAddress: filter.escrowAddress.toLowerCase(), @@ -4568,7 +4398,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_PAYOUTS_QUERY(filter), { escrowAddress: undefined, @@ -4622,7 +4452,7 @@ describe('EscrowUtils', () => { })); expect(result).toEqual(expected); expect(gqlFetchSpy).toHaveBeenCalledWith( - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + NETWORKS[ChainId.POLYGON_AMOY]?.subgraphUrl, GET_PAYOUTS_QUERY(filter), { escrowAddress: undefined, diff --git a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts index 2880fc2f9b..cd7a3bb584 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts @@ -617,14 +617,16 @@ describe('KVStoreUtils', () => { ).rejects.toThrow(ErrorInvalidAddress); }); - test('should return empty string if the URL is not set', async () => { + test('should throw if the URL is not set', async () => { KVStoreUtils.get = vi.fn().mockResolvedValueOnce(''); - const result = await KVStoreUtils.getFileUrlAndVerifyHash( - ChainId.LOCALHOST, - '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71' - ); - expect(result).toBe(''); + await expect( + KVStoreUtils.getFileUrlAndVerifyHash( + ChainId.LOCALHOST, + '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71' + ) + ).rejects.toThrow('No URL found for the given address and key'); + expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', @@ -693,8 +695,10 @@ describe('KVStoreUtils', () => { ethers.toUtf8Bytes('invalid-example') ); - KVStoreUtils.get = vi.fn().mockResolvedValueOnce('example.com'); - KVStoreUtils.get = vi.fn().mockResolvedValueOnce(invalidHash); + KVStoreUtils.get = vi + .fn() + .mockResolvedValueOnce('example.com') + .mockResolvedValueOnce(invalidHash); await expect( KVStoreUtils.getFileUrlAndVerifyHash( @@ -744,14 +748,16 @@ describe('KVStoreUtils', () => { ).rejects.toThrow(ErrorInvalidAddress); }); - test('should return empty string if the public key is not set', async () => { + test('should throw if the public key is not set', async () => { KVStoreUtils.get = vi.fn().mockResolvedValueOnce(''); - const result = await KVStoreUtils.getPublicKey( - ChainId.LOCALHOST, - '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71' - ); - expect(result).toBe(''); + await expect( + KVStoreUtils.getPublicKey( + ChainId.LOCALHOST, + '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71' + ) + ).rejects.toThrow('No URL found for the given address and key'); + expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', diff --git a/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts index 454a6bfd48..636dcc61ab 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/operator.test.ts @@ -6,7 +6,7 @@ vi.mock('graphql-request', () => { import { ethers } from 'ethers'; import * as gqlFetch from 'graphql-request'; -import { describe, expect, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { NETWORKS, Role } from '../src/constants'; import { ChainId, OrderDirection } from '../src/enums'; import { @@ -32,6 +32,10 @@ const stakerAddress = ethers.ZeroAddress; const invalidAddress = 'InvalidAddress'; describe('OperatorUtils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + const mockOperatorSubgraph: IOperatorSubgraph = { id: stakerAddress, address: stakerAddress, diff --git a/packages/sdk/typescript/human-protocol-sdk/test/staking.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/staking.test.ts index f4bbfbec42..7a6acd89a2 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/staking.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/staking.test.ts @@ -668,6 +668,10 @@ describe('StakingClient', () => { }); describe('StakingUtils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + const stakerAddress = '0x1234567890123456789012345678901234567890'; const invalidAddress = 'InvalidAddress'; diff --git a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts index e3b152056f..c1f3fbbc32 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts @@ -6,12 +6,13 @@ vi.mock('graphql-request', () => ({ import { ethers } from 'ethers'; import * as gqlFetch from 'graphql-request'; -import { afterEach, describe, expect, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { OrderDirection } from '../src/enums'; import { StatisticsUtils } from '../src/statistics'; import { GET_ESCROW_STATISTICS_QUERY, GET_EVENT_DAY_DATA_QUERY, + GET_HMT_EVENT_DAY_DATA_QUERY, GET_HOLDERS_QUERY, } from '../src/graphql/queries'; @@ -21,8 +22,8 @@ describe('StatisticsUtils', () => { 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', } as any; - afterEach(() => { - vi.restoreAllMocks(); + beforeEach(() => { + vi.clearAllMocks(); }); describe('getEscrowStatistics', () => { @@ -171,8 +172,6 @@ describe('StatisticsUtils', () => { { timestamp: 1, dailyPayoutCount: '4', - dailyHMTPayoutAmount: '100', - dailyWorkerCount: '4', }, ], }); @@ -202,9 +201,7 @@ describe('StatisticsUtils', () => { dailyPaymentsData: [ { timestamp: 1000, - totalAmountPaid: ethers.toBigInt(100), totalCount: 4, - averageAmountPerWorker: ethers.toBigInt(25), }, ], }); @@ -447,7 +444,7 @@ describe('StatisticsUtils', () => { expect(gqlFetchSpy).toHaveBeenCalledWith( 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', - GET_EVENT_DAY_DATA_QUERY({ from, to }), + GET_HMT_EVENT_DAY_DATA_QUERY({ from, to }), { from: Math.floor(from.getTime() / 1000), to: Math.floor(to.getTime() / 1000), 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 1e5649bcdc..b481cee58e 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/transaction.test.ts @@ -5,7 +5,7 @@ vi.mock('graphql-request', () => { }); import * as gqlFetch from 'graphql-request'; -import { describe, expect, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { NETWORKS } from '../src/constants'; import { ChainId, OrderDirection } from '../src/enums'; import { @@ -18,6 +18,10 @@ import { ITransaction, ITransactionsFilter } from '../src/interfaces'; import { TransactionUtils } from '../src/transaction'; describe('TransactionUtils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + describe('getTransaction', () => { const txHash = '0x62dD51230A30401C455c8398d06F85e4EaB6309f'; const invalidHash = 'InvalidHash'; diff --git a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts index ac9d4cb0cf..d2f05e34c8 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts @@ -19,6 +19,8 @@ import { WarnSubgraphApiKeyNotProvided, ErrorRetryParametersMissing, ErrorRoutingRequestsToIndexerRequiresApiKey, + SubgraphBadIndexerError, + SubgraphRequestError, } from '../src/error'; import { getSubgraphUrl, @@ -340,7 +342,7 @@ describe('customGqlFetch', () => { maxRetries: 3, baseDelay: 10, }) - ).rejects.toThrow('Regular GraphQL error'); + ).rejects.toThrow(SubgraphRequestError); expect(gqlFetchSpy).toHaveBeenCalledTimes(1); }); @@ -360,7 +362,7 @@ describe('customGqlFetch', () => { maxRetries: 2, baseDelay: 10, }) - ).rejects.toEqual(badIndexerError); + ).rejects.toThrow(SubgraphBadIndexerError); expect(gqlFetchSpy).toHaveBeenCalledTimes(3); }); @@ -400,8 +402,20 @@ describe('customGqlFetch', () => { maxRetries: 1, baseDelay: 10, }) - ).rejects.toEqual(badIndexerError); + ).rejects.toThrow(SubgraphBadIndexerError); expect(gqlFetchSpy).toHaveBeenCalledTimes(2); }); + + test('wraps subgraph request errors even when no retry config is provided', async () => { + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValue(new Error('fetch failed')); + + await expect( + customGqlFetch(mockUrl, mockQuery, mockVariables) + ).rejects.toThrow(SubgraphRequestError); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts index 9fd801dc17..f50f2da8f4 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/worker.test.ts @@ -1,5 +1,5 @@ import * as gqlFetch from 'graphql-request'; -import { describe, expect, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { NETWORKS } from '../src/constants'; import { ChainId, OrderDirection } from '../src/enums'; import { ErrorInvalidAddress } from '../src/error'; @@ -18,6 +18,10 @@ vi.mock('graphql-request', () => { }); describe('WorkerUtils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + describe('getWorker', () => { const workerAddress = '0x1234567890abcdef1234567890abcdef12345678'; const mockWorker: WorkerData = { diff --git a/packages/sdk/typescript/human-protocol-sdk/tsconfig.eslint.json b/packages/sdk/typescript/human-protocol-sdk/tsconfig.eslint.json index 653985b892..8b5d21f386 100644 --- a/packages/sdk/typescript/human-protocol-sdk/tsconfig.eslint.json +++ b/packages/sdk/typescript/human-protocol-sdk/tsconfig.eslint.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["src", "test", "example", "vitest.config.ts", "scripts"], + "include": ["src", "test", "example", "vitest.config.ts", "scripts"] } diff --git a/packages/sdk/typescript/human-protocol-sdk/typedoc.json b/packages/sdk/typescript/human-protocol-sdk/typedoc.json index 36912e847b..3e45bfb9d7 100644 --- a/packages/sdk/typescript/human-protocol-sdk/typedoc.json +++ b/packages/sdk/typescript/human-protocol-sdk/typedoc.json @@ -1,46 +1,32 @@ { - "$schema": "https://typedoc-plugin-markdown.org/schema.json", - "entryPoints": [ - "src/index.ts" - ], - "entryPointStrategy": "expand", - "out": "docs", - "plugin": [ - "typedoc-plugin-markdown" - ], - "readme": "none", - "cleanOutputDir": false, - "excludePrivate": true, - "excludeInternal": true, - "excludeProtected": false, - "excludeExternals": false, - "excludeNotDocumented": true, - "categorizeByGroup": false, - "mergeReadme": false, - "blockTags": [ - "@param", - "@returns", - "@throws", - "@example", - "@remarks" - ], - "parametersFormat": "table", - "classPropertiesFormat": "table", - "interfacePropertiesFormat": "table", - "propertyMembersFormat": "table", - "enumMembersFormat": "table", - "typeDeclarationFormat": "table", - "useCodeBlocks": true, - "expandParameters": true, - "hidePageHeader": true, - "hidePageTitle": true, - "hideBreadcrumbs": true, - "disableSources": true, - "excludeTags": [ - "@overload" - ], - "sort": [ - "source-order" - ], - "includeVersion": true -} \ No newline at end of file + "$schema": "https://typedoc-plugin-markdown.org/schema.json", + "entryPoints": ["src/index.ts"], + "entryPointStrategy": "expand", + "out": "docs", + "plugin": ["typedoc-plugin-markdown"], + "readme": "none", + "cleanOutputDir": false, + "excludePrivate": true, + "excludeInternal": true, + "excludeProtected": false, + "excludeExternals": false, + "excludeNotDocumented": true, + "categorizeByGroup": false, + "mergeReadme": false, + "blockTags": ["@param", "@returns", "@throws", "@example", "@remarks"], + "parametersFormat": "table", + "classPropertiesFormat": "table", + "interfacePropertiesFormat": "table", + "propertyMembersFormat": "table", + "enumMembersFormat": "table", + "typeDeclarationFormat": "table", + "useCodeBlocks": true, + "expandParameters": true, + "hidePageHeader": true, + "hidePageTitle": true, + "hideBreadcrumbs": true, + "disableSources": true, + "excludeTags": ["@overload"], + "sort": ["source-order"], + "includeVersion": true +} diff --git a/packages/sdk/typescript/subgraph/config/amoy.json b/packages/sdk/typescript/subgraph/config/amoy.json deleted file mode 100644 index 2253b3495f..0000000000 --- a/packages/sdk/typescript/subgraph/config/amoy.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "network": "polygon-amoy", - "description": "HUMAN subgraph on Amoy Testnet", - "EscrowFactory": { - "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", - "startBlock": 5773000, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", - "startBlock": 5769546, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xF3D9a0ba9FA14273C515e519DFD0826Ff87d5164" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", - "startBlock": 5773002, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0xffE496683F842a923110415b7278ded3F265f2C5", - "startBlock": 14983952, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/bsc-testnet.json b/packages/sdk/typescript/subgraph/config/bsc-testnet.json deleted file mode 100644 index aaf5db6347..0000000000 --- a/packages/sdk/typescript/subgraph/config/bsc-testnet.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "chapel", - "description": "Human subgraph on Binance Smart Chain testnet", - "EscrowFactory": { - "address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18", - "startBlock": 26716359, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d", - "startBlock": 26716354, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xf183b3B34E70Dd17859455389A3aB54D49D41e6f" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xD6D347ba6987519B4e42EcED43dF98eFf5465a23", - "startBlock": 45938762, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0x32e27177BA6Ea91cf28dfd91a0Da9822A4b74EcF", - "startBlock": 34883905, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f", - "startBlock": 23632686, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/bsc.json b/packages/sdk/typescript/subgraph/config/bsc.json deleted file mode 100644 index cfbb32fd9b..0000000000 --- a/packages/sdk/typescript/subgraph/config/bsc.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "bsc", - "description": "Human subgraph on BSC mainnet", - "EscrowFactory": { - "address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714", - "startBlock": 28745625, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527", - "startBlock": 25383172, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "0", - "contractDeployer": "bridge" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98", - "startBlock": 45120420, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1", - "startBlock": 33941535, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0xc88bC422cAAb2ac8812de03176402dbcA09533f4", - "startBlock": 20689161, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/ethereum.json b/packages/sdk/typescript/subgraph/config/ethereum.json deleted file mode 100644 index 4cf773b86c..0000000000 --- a/packages/sdk/typescript/subgraph/config/ethereum.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "mainnet", - "description": "Human subgraph on Ethereum Mainnet", - "EscrowFactory": { - "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", - "startBlock": 16924057, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867", - "startBlock": 12184475, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0x3895d913a9A231d2B215F402c528511B569C676D" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D", - "startBlock": 18683644, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0xEf6Da3aB52c33925Be3F84038193a7e1331F51E6", - "startBlock": 21464165, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "LegacyEscrowFactory": { - "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", - "startBlock": 16924057, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/localhost.json b/packages/sdk/typescript/subgraph/config/localhost.json deleted file mode 100644 index 0f617bae82..0000000000 --- a/packages/sdk/typescript/subgraph/config/localhost.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "network": "localhost", - "description": "Human subgraph on localhost", - "EscrowFactory": { - "address": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", - "startBlock": 5, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "startBlock": 1, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "startBlock": 3, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", - "startBlock": 6, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/polygon.json b/packages/sdk/typescript/subgraph/config/polygon.json deleted file mode 100644 index 82ea7715ad..0000000000 --- a/packages/sdk/typescript/subgraph/config/polygon.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "matic", - "description": "Human subgraph on Polygon mainnet", - "EscrowFactory": { - "address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB", - "startBlock": 38858552, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf", - "startBlock": 20181701, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "0", - "contractDeployer": "bridge" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0x01D115E9E8bF0C58318793624CC662a030D07F1D", - "startBlock": 65832028, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0xbcB28672F826a50B03EE91B28145EAbddA73B2eD", - "startBlock": 50567977, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794", - "startBlock": 25426566, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/sepolia.json b/packages/sdk/typescript/subgraph/config/sepolia.json deleted file mode 100644 index 544eec59ef..0000000000 --- a/packages/sdk/typescript/subgraph/config/sepolia.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "network": "sepolia", - "description": "HUMAN subgraph on Sepolia Ethereum Testnet", - "EscrowFactory": { - "address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf", - "startBlock": 7067993, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", - "startBlock": 5716225, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xF3D9a0ba9FA14273C515e519DFD0826Ff87d5164" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", - "startBlock": 5716238, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0x2163e3A40032Af1C359ac731deaB48258b317890", - "startBlock": 7062708, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/matchstick.yaml b/packages/sdk/typescript/subgraph/matchstick.yaml deleted file mode 100644 index b6dd695aba..0000000000 --- a/packages/sdk/typescript/subgraph/matchstick.yaml +++ /dev/null @@ -1 +0,0 @@ -libsFolder: ../../../../node_modules diff --git a/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts b/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts deleted file mode 100644 index cb47c48fa0..0000000000 --- a/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { Address, BigInt, Bytes, dataSource } from '@graphprotocol/graph-ts'; - -import { - Approval, - BulkApproval, - BulkTransfer, - Transfer, -} from '../../generated/HMToken/HMToken'; -import { - Escrow, - HMTApprovalEvent, - HMTBulkApprovalEvent, - HMTBulkTransferEvent, - HMTTransferEvent, - HMTokenStatistics, - Holder, - UniqueReceiver, - UniqueSender, -} from '../../generated/schema'; -import { toEventId } from './utils/event'; -import { ONE_BI, ONE_DAY, ZERO_BI } from './utils/number'; -import { getEventDayData } from './utils/dayUpdates'; -import { createTransaction } from './utils/transaction'; -import { toBytes } from './utils/string'; - -export const HMT_STATISTICS_ENTITY_ID = toBytes('hmt-statistics-id'); -export const TOTAL_SUPPLY = BigInt.fromString('{{ HMToken.totalSupply }}'); -export const CONTRACT_DEPLOYER = '{{ HMToken.contractDeployer }}'; - -function constructStatsEntity(): HMTokenStatistics { - const entity = new HMTokenStatistics(HMT_STATISTICS_ENTITY_ID); - - entity.totalTransferEventCount = BigInt.fromI32(0); - entity.totalApprovalEventCount = BigInt.fromI32(0); - entity.totalBulkApprovalEventCount = BigInt.fromI32(0); - entity.totalBulkTransferEventCount = BigInt.fromI32(0); - entity.totalValueTransfered = BigInt.fromI32(0); - entity.holders = BigInt.fromI32(0); - - return entity; -} - -export function createOrLoadUniqueSender( - dayStartTimestamp: BigInt, - timestamp: BigInt, - address: Address -): UniqueSender { - const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); - let uniqueSender = UniqueSender.load(id); - - if (!uniqueSender) { - uniqueSender = new UniqueSender(id); - uniqueSender.address = address; - uniqueSender.transferCount = ZERO_BI; - uniqueSender.timestamp = timestamp; - uniqueSender.save(); - } - - return uniqueSender; -} - -export function createOrLoadUniqueReceiver( - dayStartTimestamp: BigInt, - timestamp: BigInt, - address: Address -): UniqueReceiver { - const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); - let uniqueReceiver = UniqueReceiver.load(id); - - if (!uniqueReceiver) { - uniqueReceiver = new UniqueReceiver(id); - uniqueReceiver.address = address; - uniqueReceiver.receiveCount = ZERO_BI; - uniqueReceiver.timestamp = timestamp; - uniqueReceiver.save(); - } - - return uniqueReceiver; -} - -function updateHolders( - holderAddress: Address, - value: BigInt, - increase: boolean -): BigInt { - if ( - holderAddress.toHexString() == '0x0000000000000000000000000000000000000000' - ) - return ZERO_BI; - let count = ZERO_BI; - const id = holderAddress; - let holder = Holder.load(id); - - if (holder == null) { - holder = new Holder(id); - holder.address = holderAddress; - if (holderAddress.toHex() == CONTRACT_DEPLOYER) { - holder.balance = TOTAL_SUPPLY; - } else { - holder.balance = BigInt.fromI32(0); - } - } - const balanceBeforeTransfer = holder.balance; - holder.balance = increase - ? holder.balance.plus(value) - : holder.balance.minus(value); - - if (balanceBeforeTransfer.isZero() && !holder.balance.isZero()) { - count = ONE_BI; - } else if (!balanceBeforeTransfer.isZero() && holder.balance.isZero()) { - count = ONE_BI.neg(); - } - - holder.save(); - - return count; -} - -export function createOrLoadHMTStatistics(): HMTokenStatistics { - let statsEntity = HMTokenStatistics.load(HMT_STATISTICS_ENTITY_ID); - - if (!statsEntity) { - statsEntity = constructStatsEntity(); - } - - return statsEntity; -} - -export function handleTransfer(event: Transfer): void { - // Create HMTTransferEvent entity - const eventEntity = new HMTTransferEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.from = event.params._from; - eventEntity.to = event.params._to; - eventEntity.amount = event.params._value; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalTransferEventCount = - statsEntity.totalTransferEventCount.plus(ONE_BI); - statsEntity.totalValueTransfered = statsEntity.totalValueTransfered.plus( - event.params._value - ); - - const eventDayData = getEventDayData(event); - const escrow = Escrow.load(event.params._to); - if (escrow) { - // Update escrow balance - escrow.balance = escrow.balance.plus(event.params._value); - escrow.totalFundedAmount = escrow.totalFundedAmount.plus( - event.params._value - ); - escrow.save(); - - createTransaction( - event, - 'fund', - event.params._from, - dataSource.address(), - event.params._to, - event.params._to, - event.params._value, - dataSource.address() - ); - } else { - createTransaction( - event, - 'transfer', - event.params._from, - dataSource.address(), - event.params._to, - null, - event.params._value, - dataSource.address() - ); - } - - // Update holders - const diffHolders = updateHolders( - event.params._from, - event.params._value, - false - ).plus(updateHolders(event.params._to, event.params._value, true)); - statsEntity.holders = statsEntity.holders.plus(diffHolders); - - // Update event day data for HMT transfer - eventDayData.dailyHMTTransferCount = - eventDayData.dailyHMTTransferCount.plus(ONE_BI); - eventDayData.dailyHMTTransferAmount = - eventDayData.dailyHMTTransferAmount.plus(event.params._value); - - const timestamp = event.block.timestamp.toI32(); - const dayID = timestamp / ONE_DAY; - const dayStartTimestamp = dayID * ONE_DAY; - - // Update unique sender - const uniqueSender = createOrLoadUniqueSender( - BigInt.fromI32(dayStartTimestamp), - event.block.timestamp, - event.params._from - ); - if (uniqueSender.transferCount === ZERO_BI) { - eventDayData.dailyUniqueSenders = - eventDayData.dailyUniqueSenders.plus(ONE_BI); - } - uniqueSender.transferCount = uniqueSender.transferCount.plus( - event.params._value - ); - uniqueSender.save(); - - // Update unique receiver - const uniqueReceiver = createOrLoadUniqueReceiver( - BigInt.fromI32(dayStartTimestamp), - event.block.timestamp, - event.params._to - ); - if (uniqueReceiver.receiveCount === ZERO_BI) { - eventDayData.dailyUniqueReceivers = - eventDayData.dailyUniqueReceivers.plus(ONE_BI); - } - uniqueReceiver.receiveCount = uniqueReceiver.receiveCount.plus( - event.params._value - ); - uniqueReceiver.save(); - - eventDayData.save(); - statsEntity.save(); -} - -export function handleBulkTransfer(event: BulkTransfer): void { - createTransaction( - event, - 'transferBulk', - event.transaction.from, - dataSource.address(), - null, - null, - null, - dataSource.address() - ); - // Create HMTBulkTransferEvent entity - const eventEntity = new HMTBulkTransferEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.txId = event.params._txId; - eventEntity.bulkCount = event.params._bulkCount; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalBulkTransferEventCount = - statsEntity.totalBulkTransferEventCount.plus(ONE_BI); - statsEntity.save(); -} - -export function handleApproval(event: Approval): void { - createTransaction( - event, - 'approve', - event.params._owner, - dataSource.address(), - event.params._spender, - null, - event.params._value, - dataSource.address() - ); - // Create HMTApprovalEvent entity - const eventEntity = new HMTApprovalEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.owner = event.params._owner; - eventEntity.spender = event.params._spender; - eventEntity.amount = event.params._value; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalApprovalEventCount = - statsEntity.totalApprovalEventCount.plus(ONE_BI); - statsEntity.save(); -} - -export function handleBulkApproval(event: BulkApproval): void { - createTransaction( - event, - 'increaseApprovalBulk', - event.transaction.from, - dataSource.address(), - null, - null, - null, - dataSource.address() - ); - // Create HMTBulkApprovalEvent entity - const eventEntity = new HMTBulkApprovalEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.txId = event.params._txId; - eventEntity.bulkCount = event.params._bulkCount; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalBulkApprovalEventCount = - statsEntity.totalBulkApprovalEventCount.plus(ONE_BI); - statsEntity.save(); -} diff --git a/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts b/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts deleted file mode 100644 index 259a0ac3da..0000000000 --- a/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { newMockEvent } from 'matchstick-as/assembly/index'; -import { Address, ethereum, BigInt, dataSource } from '@graphprotocol/graph-ts'; - -import { - Transfer, - Approval, - BulkTransfer, - BulkApproval, -} from '../../generated/HMToken/HMToken'; -import { generateUniqueHash } from '../../tests/utils'; - -export function createTransferEvent( - from: string, - to: string, - value: i32, - timestamp: BigInt -): Transfer { - const transferEvent = changetype(newMockEvent()); - transferEvent.transaction.hash = generateUniqueHash( - to, - timestamp, - transferEvent.transaction.nonce - ); - - transferEvent.parameters = []; - transferEvent.transaction.from = Address.fromString(from); - transferEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - transferEvent.block.timestamp = timestamp; - const fromParam = new ethereum.EventParam( - '_from', - ethereum.Value.fromAddress(Address.fromString(from)) - ); - const toParam = new ethereum.EventParam( - '_to', - ethereum.Value.fromAddress(Address.fromString(to)) - ); - const valueParam = new ethereum.EventParam( - '_value', - ethereum.Value.fromI32(value) - ); - - transferEvent.parameters.push(fromParam); - transferEvent.parameters.push(toParam); - transferEvent.parameters.push(valueParam); - - return transferEvent; -} - -export function createApprovalEvent( - spender: string, - owner: string, - value: i32, - timestamp: BigInt -): Approval { - const approvalEvent = changetype(newMockEvent()); - approvalEvent.transaction.hash = generateUniqueHash( - owner, - timestamp, - approvalEvent.transaction.nonce - ); - - approvalEvent.transaction.from = Address.fromString(spender); - approvalEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - approvalEvent.parameters = []; - approvalEvent.block.timestamp = timestamp; - const ownerParam = new ethereum.EventParam( - '_spender', - ethereum.Value.fromAddress(Address.fromString(spender)) - ); - const spenderParam = new ethereum.EventParam( - '_owner', - ethereum.Value.fromAddress(Address.fromString(owner)) - ); - const valueParam = new ethereum.EventParam( - '_value', - ethereum.Value.fromI32(value) - ); - - approvalEvent.parameters.push(ownerParam); - approvalEvent.parameters.push(spenderParam); - approvalEvent.parameters.push(valueParam); - - return approvalEvent; -} - -export function createBulkTransferEvent( - txId: i32, - bulkCount: i32, - timestamp: BigInt -): BulkTransfer { - const bulkTransferEvent = changetype(newMockEvent()); - bulkTransferEvent.transaction.hash = generateUniqueHash( - bulkCount.toString(), - timestamp, - bulkTransferEvent.transaction.nonce - ); - bulkTransferEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - - bulkTransferEvent.parameters = []; - bulkTransferEvent.block.timestamp = timestamp; - const txIdParam = new ethereum.EventParam( - '_txId', - ethereum.Value.fromI32(txId) - ); - const bulkCountParam = new ethereum.EventParam( - '_bulkCount', - ethereum.Value.fromI32(bulkCount) - ); - - bulkTransferEvent.parameters.push(txIdParam); - bulkTransferEvent.parameters.push(bulkCountParam); - - return bulkTransferEvent; -} - -export function createBulkApprovalEvent( - txId: i32, - bulkCount: i32, - timestamp: BigInt -): BulkApproval { - const bulkApprovalEvent = changetype(newMockEvent()); - bulkApprovalEvent.transaction.hash = generateUniqueHash( - bulkCount.toString(), - timestamp, - bulkApprovalEvent.transaction.nonce - ); - - bulkApprovalEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - - bulkApprovalEvent.parameters = []; - bulkApprovalEvent.block.timestamp = timestamp; - const txIdParam = new ethereum.EventParam( - '_txId', - ethereum.Value.fromI32(txId) - ); - const bulkCountParam = new ethereum.EventParam( - '_bulkCount', - ethereum.Value.fromI32(bulkCount) - ); - - bulkApprovalEvent.parameters.push(txIdParam); - bulkApprovalEvent.parameters.push(bulkCountParam); - - return bulkApprovalEvent; -} diff --git a/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts b/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts deleted file mode 100644 index d8ac0bce0d..0000000000 --- a/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts +++ /dev/null @@ -1,597 +0,0 @@ -import { Address, BigInt, DataSourceContext } from '@graphprotocol/graph-ts'; -import { - describe, - test, - assert, - clearStore, - dataSourceMock, - beforeAll, - afterAll, - beforeEach, -} from 'matchstick-as/assembly'; - -import { Escrow } from '../../generated/schema'; -import { - HMT_STATISTICS_ENTITY_ID, - handleTransfer, - handleBulkTransfer, - handleApproval, - handleBulkApproval, -} from '../../src/mapping/HMToken'; -import { toEventId } from '../../src/mapping/utils/event'; -import { ZERO_BI } from '../../src/mapping/utils/number'; -import { - createTransferEvent, - createBulkTransferEvent, - createApprovalEvent, - createBulkApprovalEvent, -} from './fixtures'; - -const tokenAddressString = '0xa16081f360e3847006db660bae1c6d1b2e17ffaa'; -const escrowAddressString = '0xa16081f360e3847006db660bae1c6d1b2e17ec2a'; -const escrowAddress = Address.fromString(escrowAddressString); -const operatorAddressString = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'; -const holderAddressString = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'; -const holderAddress = Address.fromString(holderAddressString); -const holder1AddressString = '0x92a2eEF7Ff696BCef98957a0189872680600a959'; -const holder1Address = Address.fromString(holder1AddressString); - -describe('HMToken', () => { - beforeAll(() => { - dataSourceMock.setReturnValues( - tokenAddressString, - 'rinkeby', - new DataSourceContext() - ); - - const escrow = new Escrow(escrowAddress); - escrow.address = escrowAddress; - escrow.token = Address.zero(); - escrow.factoryAddress = Address.zero(); - escrow.launcher = Address.zero(); - escrow.canceler = Address.zero(); - escrow.count = ZERO_BI; - escrow.balance = BigInt.fromI32(100); - escrow.totalFundedAmount = BigInt.fromI32(100); - escrow.amountPaid = ZERO_BI; - escrow.createdAt = BigInt.fromI32(0); - escrow.status = 'Launched'; - - escrow.save(); - }); - - afterAll(() => { - dataSourceMock.resetValues(); - }); - - describe('Transfer', () => { - test('Should properly handle Transfer event to Escrow', () => { - const transfer = createTransferEvent( - operatorAddressString, - escrowAddressString, - 1, - BigInt.fromI32(10) - ); - - handleTransfer(transfer); - - const id = toEventId(transfer).toHex(); - - // HMTTransferEvent - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'timestamp', - transfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTTransferEvent', id, 'from', operatorAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'to', escrowAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'amount', '1'); - - // Escrow - assert.fieldEquals('Escrow', escrowAddressString, 'balance', '101'); - assert.fieldEquals( - 'Escrow', - escrowAddressString, - 'totalFundedAmount', - '101' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'method', - 'fund' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'from', - operatorAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'to', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'receiver', - escrowAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'escrow', - escrowAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'token', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'value', - '1' - ); - }); - - test('Should properly handle Transfer event to Holder', () => { - const transfer = createTransferEvent( - operatorAddressString, - holderAddressString, - 1, - BigInt.fromI32(20) - ); - - handleTransfer(transfer); - - const id = toEventId(transfer).toHex(); - - // HMTTransferEvent - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'timestamp', - transfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTTransferEvent', id, 'from', operatorAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'to', holderAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'amount', '1'); - - // Holder - assert.fieldEquals( - 'Holder', - holderAddressString, - 'address', - holderAddressString - ); - assert.fieldEquals('Holder', holderAddressString, 'balance', '1'); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'method', - 'transfer' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'from', - operatorAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'to', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'receiver', - holderAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'token', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'value', - '1' - ); - }); - }); - - test('Should properly handle BulkTransfer event', () => { - const bulkTransfer = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); - - handleBulkTransfer(bulkTransfer); - - const id = toEventId(bulkTransfer).toHex(); - - // HMTBulkTransferEvent - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'block', - bulkTransfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'timestamp', - bulkTransfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'txHash', - bulkTransfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTBulkTransferEvent', id, 'txId', '1'); - assert.fieldEquals('HMTBulkTransferEvent', id, 'bulkCount', '3'); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'txHash', - bulkTransfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'method', - 'transferBulk' - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'block', - bulkTransfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'from', - bulkTransfer.transaction.from.toHex() - ); - }); - - test('Should properly handle Approval event', () => { - const approval = createApprovalEvent( - holderAddressString, - operatorAddressString, - 1, - BigInt.fromI32(100) - ); - - handleApproval(approval); - - const id = toEventId(approval).toHex(); - - // HMTApprovalEvent - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'block', - approval.block.number.toString() - ); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'timestamp', - approval.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'txHash', - approval.transaction.hash.toHex() - ); - assert.fieldEquals('HMTApprovalEvent', id, 'owner', holderAddressString); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'spender', - operatorAddressString - ); - assert.fieldEquals('HMTApprovalEvent', id, 'amount', '1'); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'txHash', - approval.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'method', - 'approve' - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'block', - approval.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'from', - approval.transaction.from.toHex() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'value', - '1' - ); - }); - - test('Should properly handle BulkApproval event', () => { - const bulkApproval = createBulkApprovalEvent(1, 3, BigInt.fromI32(200)); - - handleBulkApproval(bulkApproval); - - const id = toEventId(bulkApproval).toHex(); - - // HMTBulkApprovalEvent - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'block', - bulkApproval.block.number.toString() - ); - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'timestamp', - bulkApproval.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'txHash', - bulkApproval.transaction.hash.toHex() - ); - assert.fieldEquals('HMTBulkApprovalEvent', id, 'txId', '1'); - assert.fieldEquals('HMTBulkApprovalEvent', id, 'bulkCount', '3'); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'txHash', - bulkApproval.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'method', - 'increaseApprovalBulk' - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'block', - bulkApproval.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'from', - bulkApproval.transaction.from.toHex() - ); - }); - - describe('Statistics', () => { - beforeEach(() => { - clearStore(); - }); - - test('Should properly calculate Transfer event in statistics', () => { - const transfer1 = createTransferEvent( - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - 1, - BigInt.fromI32(10) - ); - const transfer2 = createTransferEvent( - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - 2, - BigInt.fromI32(10) - ); - - handleTransfer(transfer1); - handleTransfer(transfer2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalTransferEventCount', - '2' - ); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalValueTransfered', - '3' - ); - }); - - test('Should properly calculate holders in statistics', () => { - const transfer1 = createTransferEvent( - '0x0000000000000000000000000000000000000000', - holderAddressString, - 10, - BigInt.fromI32(10) - ); - - const transfer2 = createTransferEvent( - holderAddressString, - holder1AddressString, - 0, - BigInt.fromI32(10) - ); - - handleTransfer(transfer1); - handleTransfer(transfer2); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '0'); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '1' - ); - - const transfer3 = createTransferEvent( - '0x0000000000000000000000000000000000000000', - holder1AddressString, - 10, - BigInt.fromI32(10) - ); - - handleTransfer(transfer3); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '10'); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '2' - ); - - const transfer4 = createTransferEvent( - holderAddressString, - holder1AddressString, - 10, - BigInt.fromI32(10) - ); - - handleTransfer(transfer4); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '0'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '20'); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '1' - ); - }); - - test('Should properly calculate BulkTransfer event in statistics', () => { - const bulkTransfer1 = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); - const bulkTransfer2 = createBulkTransferEvent(2, 3, BigInt.fromI32(10)); - - handleBulkTransfer(bulkTransfer1); - handleBulkTransfer(bulkTransfer2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalBulkTransferEventCount', - '2' - ); - }); - - test('Should properly calculate Approval event in statistics', () => { - const approval1 = createApprovalEvent( - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - 10, - BigInt.fromI32(10) - ); - - const approval2 = createApprovalEvent( - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - 10, - BigInt.fromI32(10) - ); - - handleApproval(approval1); - handleApproval(approval2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalApprovalEventCount', - '2' - ); - }); - - test('Should properly calculate BulkApproval event in statistics', () => { - const bulkApproval1 = createBulkApprovalEvent(1, 3, BigInt.fromI32(10)); - const bulkApproval2 = createBulkApprovalEvent(2, 3, BigInt.fromI32(10)); - - handleBulkApproval(bulkApproval1); - handleBulkApproval(bulkApproval2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalBulkApprovalEventCount', - '2' - ); - }); - }); -}); diff --git a/packages/subgraph/hmt/.gitignore b/packages/subgraph/hmt/.gitignore new file mode 100644 index 0000000000..814a4b37a2 --- /dev/null +++ b/packages/subgraph/hmt/.gitignore @@ -0,0 +1,12 @@ +# Graph build +build/ + +# Graph generated +generated/ + +# Mustache generated +subgraph.yaml + +# Graph test generated +tests/.bin +tests/.latest.json diff --git a/packages/sdk/typescript/subgraph/.prettierignore b/packages/subgraph/hmt/.prettierignore similarity index 100% rename from packages/sdk/typescript/subgraph/.prettierignore rename to packages/subgraph/hmt/.prettierignore diff --git a/packages/sdk/typescript/subgraph/.prettierrc b/packages/subgraph/hmt/.prettierrc similarity index 100% rename from packages/sdk/typescript/subgraph/.prettierrc rename to packages/subgraph/hmt/.prettierrc diff --git a/packages/subgraph/hmt/README.md b/packages/subgraph/hmt/README.md new file mode 100644 index 0000000000..a93157249a --- /dev/null +++ b/packages/subgraph/hmt/README.md @@ -0,0 +1,66 @@ +

+ Human Protocol +

+ +

Human HMT statistics subgraph

+

+Dedicated subgraph to index HMToken statistics only. +

+ +## Installation + +Package installation: + +```bash +yarn install && yarn workspace human-protocol build:core +``` + +## Development + +Generate and build artifacts for a network: + +```bash +NETWORK=polygon yarn workspace @tools/subgraph-hmt generate +NETWORK=polygon yarn workspace @tools/subgraph-hmt build +``` + +### Tests + +To run subgraph tests: + +```bash +NETWORK=polygon yarn workspace @tools/subgraph-hmt generate +yarn workspace @tools/subgraph-hmt test +``` + +This subgraph does not use staking/escrow templates. + +## Indexed entities + +- `HMTokenStatistics` +- `Holder` +- `UniqueSender` +- `UniqueReceiver` +- `EventDayData` (HMT daily fields only) + +## Supported networks + +- Ethereum Mainnet +- Sepolia (testnet) +- BSC Mainnet +- BSC Testnet (testnet) +- Polygon Mainnet +- Polygon Amoy (testnet) +- Localhost + +## Add a new network + +1. Add network configuration as `config/NETWORK.json`. +2. Ensure required fields are present: + - `network` + - `description` + - `HMToken.address` + - `HMToken.startBlock` + - `HMToken.abi` +3. Generate artifacts: `NETWORK=NETWORK yarn workspace @tools/subgraph-hmt generate`. +4. Build subgraph: `yarn workspace @tools/subgraph-hmt build`. diff --git a/packages/subgraph/hmt/config/amoy.json b/packages/subgraph/hmt/config/amoy.json new file mode 100644 index 0000000000..9e3ff34fee --- /dev/null +++ b/packages/subgraph/hmt/config/amoy.json @@ -0,0 +1,9 @@ +{ + "network": "polygon-amoy", + "description": "HUMAN subgraph on Amoy Testnet", + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "startBlock": 5769546, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/bsc-testnet.json b/packages/subgraph/hmt/config/bsc-testnet.json new file mode 100644 index 0000000000..bab01016d0 --- /dev/null +++ b/packages/subgraph/hmt/config/bsc-testnet.json @@ -0,0 +1,9 @@ +{ + "network": "chapel", + "description": "Human subgraph on Binance Smart Chain testnet", + "HMToken": { + "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d", + "startBlock": 26716354, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/bsc.json b/packages/subgraph/hmt/config/bsc.json new file mode 100644 index 0000000000..eab6ad0b64 --- /dev/null +++ b/packages/subgraph/hmt/config/bsc.json @@ -0,0 +1,9 @@ +{ + "network": "bsc", + "description": "Human subgraph on BSC mainnet", + "HMToken": { + "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527", + "startBlock": 25383172, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/ethereum.json b/packages/subgraph/hmt/config/ethereum.json new file mode 100644 index 0000000000..9004bd2aca --- /dev/null +++ b/packages/subgraph/hmt/config/ethereum.json @@ -0,0 +1,9 @@ +{ + "network": "mainnet", + "description": "Human subgraph on Ethereum Mainnet", + "HMToken": { + "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867", + "startBlock": 12184475, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/localhost.json b/packages/subgraph/hmt/config/localhost.json new file mode 100644 index 0000000000..e69584771f --- /dev/null +++ b/packages/subgraph/hmt/config/localhost.json @@ -0,0 +1,9 @@ +{ + "network": "localhost", + "description": "Human subgraph on localhost", + "HMToken": { + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "startBlock": 1, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/polygon.json b/packages/subgraph/hmt/config/polygon.json new file mode 100644 index 0000000000..ee304360a6 --- /dev/null +++ b/packages/subgraph/hmt/config/polygon.json @@ -0,0 +1,9 @@ +{ + "network": "matic", + "description": "Human subgraph on Polygon mainnet", + "HMToken": { + "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf", + "startBlock": 20181701, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/sepolia.json b/packages/subgraph/hmt/config/sepolia.json new file mode 100644 index 0000000000..ebc16b0e25 --- /dev/null +++ b/packages/subgraph/hmt/config/sepolia.json @@ -0,0 +1,9 @@ +{ + "network": "sepolia", + "description": "HUMAN subgraph on Sepolia Ethereum Testnet", + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "startBlock": 5716225, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/sdk/typescript/subgraph/eslint.config.mjs b/packages/subgraph/hmt/eslint.config.mjs similarity index 95% rename from packages/sdk/typescript/subgraph/eslint.config.mjs rename to packages/subgraph/hmt/eslint.config.mjs index e3b3ba3aa9..666059c3d0 100644 --- a/packages/sdk/typescript/subgraph/eslint.config.mjs +++ b/packages/subgraph/hmt/eslint.config.mjs @@ -27,6 +27,8 @@ export default tseslint.config( }, }, rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', 'no-console': 'warn', '@/quotes': [ 'error', diff --git a/packages/subgraph/hmt/matchstick.yaml b/packages/subgraph/hmt/matchstick.yaml new file mode 100644 index 0000000000..be49651fb8 --- /dev/null +++ b/packages/subgraph/hmt/matchstick.yaml @@ -0,0 +1 @@ +libsFolder: ../../../node_modules diff --git a/packages/subgraph/hmt/package.json b/packages/subgraph/hmt/package.json new file mode 100644 index 0000000000..e20e04caf4 --- /dev/null +++ b/packages/subgraph/hmt/package.json @@ -0,0 +1,53 @@ +{ + "name": "@tools/subgraph-hmt", + "private": true, + "description": "Human Protocol HMT Statistics Subgraph", + "version": "1.0.0", + "files": [ + "generated" + ], + "scripts": { + "clean": "rm -rf build generated subgraph.yaml", + "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && graph codegen", + "codegen": "graph codegen", + "build": "graph build", + "test": "NETWORK=polygon yarn generate && graph test", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write '**/*.{ts,json,graphql,yaml}'" + }, + "repository": { + "type": "git", + "url": "https://github.com/humanprotocol/human-protocol.git", + "directory": "packages/sdk/typescript/subgraph/hmt" + }, + "keywords": [ + "human-protocol", + "sdk", + "subgraph", + "hmt", + "statistics" + ], + "license": "MIT", + "devDependencies": { + "@graphprotocol/graph-cli": "^0.97.1", + "@graphprotocol/graph-ts": "^0.38.0", + "@graphql-eslint/eslint-plugin": "^3.19.1", + "@human-protocol/core": "workspace:*", + "eslint": "^9.39.1", + "ethers": "~6.15.0", + "graphql": "^16.6.0", + "matchstick-as": "^0.6.0", + "mustache": "^4.2.0", + "prettier": "^3.8.1" + }, + "lint-staged": { + "*.{ts,graphql}": [ + "prettier --write", + "eslint --fix" + ], + "*.{yaml,json}": [ + "prettier --write" + ] + } +} diff --git a/packages/subgraph/hmt/schema.graphql b/packages/subgraph/hmt/schema.graphql new file mode 100644 index 0000000000..75ae97773a --- /dev/null +++ b/packages/subgraph/hmt/schema.graphql @@ -0,0 +1,38 @@ +type HMTokenStatistics @entity(immutable: false) { + id: Bytes! + totalTransferEventCount: BigInt! + totalBulkTransferEventCount: BigInt! + totalApprovalEventCount: BigInt! + totalBulkApprovalEventCount: BigInt! + totalValueTransfered: BigInt! + holders: BigInt! +} + +type Holder @entity(immutable: false) { + id: Bytes! + address: Bytes! + balance: BigInt! +} + +type UniqueSender @entity(immutable: false) { + id: Bytes! + address: Bytes! + transferCount: BigInt! + timestamp: BigInt! +} + +type UniqueReceiver @entity(immutable: false) { + id: Bytes! + address: Bytes! + receiveCount: BigInt! + timestamp: BigInt! +} + +type EventDayData @entity(immutable: false) { + id: Bytes! + timestamp: Int! + dailyHMTTransferCount: BigInt! + dailyHMTTransferAmount: BigInt! + dailyUniqueSenders: BigInt! + dailyUniqueReceivers: BigInt! +} diff --git a/packages/subgraph/hmt/src/mapping/HMToken.ts b/packages/subgraph/hmt/src/mapping/HMToken.ts new file mode 100644 index 0000000000..e94ce96287 --- /dev/null +++ b/packages/subgraph/hmt/src/mapping/HMToken.ts @@ -0,0 +1,203 @@ +import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'; + +import { + Approval, + BulkApproval, + BulkTransfer, + Transfer, +} from '../../generated/HMToken/HMToken'; +import { + HMTokenStatistics, + Holder, + UniqueReceiver, + UniqueSender, +} from '../../generated/schema'; +import { getEventDayData } from './utils/dayUpdates'; +import { ONE_BI, ONE_DAY, ZERO_BI } from './utils/number'; + +export const HMT_STATISTICS_ENTITY_ID = Bytes.fromUTF8('hmt-statistics-id'); +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + +function constructStatsEntity(): HMTokenStatistics { + const entity = new HMTokenStatistics(HMT_STATISTICS_ENTITY_ID); + + entity.totalTransferEventCount = ZERO_BI; + entity.totalApprovalEventCount = ZERO_BI; + entity.totalBulkApprovalEventCount = ZERO_BI; + entity.totalBulkTransferEventCount = ZERO_BI; + entity.totalValueTransfered = ZERO_BI; + entity.holders = ZERO_BI; + + return entity; +} + +function createOrLoadUniqueSender( + dayStartTimestamp: BigInt, + timestamp: BigInt, + address: Address +): UniqueSender { + const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); + let uniqueSender = UniqueSender.load(id); + + if (!uniqueSender) { + uniqueSender = new UniqueSender(id); + uniqueSender.address = address; + uniqueSender.transferCount = ZERO_BI; + uniqueSender.timestamp = timestamp; + uniqueSender.save(); + } + + return uniqueSender; +} + +function createOrLoadUniqueReceiver( + dayStartTimestamp: BigInt, + timestamp: BigInt, + address: Address +): UniqueReceiver { + const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); + let uniqueReceiver = UniqueReceiver.load(id); + + if (!uniqueReceiver) { + uniqueReceiver = new UniqueReceiver(id); + uniqueReceiver.address = address; + uniqueReceiver.receiveCount = ZERO_BI; + uniqueReceiver.timestamp = timestamp; + uniqueReceiver.save(); + } + + return uniqueReceiver; +} + +function updateHolders( + holderAddress: Address, + value: BigInt, + increase: boolean +): BigInt { + if (holderAddress.toHexString() == ZERO_ADDRESS) { + return ZERO_BI; + } + + let count = ZERO_BI; + let holder = Holder.load(holderAddress); + + if (holder == null) { + holder = new Holder(holderAddress); + holder.address = holderAddress; + holder.balance = ZERO_BI; + } + + const balanceBeforeTransfer = holder.balance; + holder.balance = increase + ? holder.balance.plus(value) + : holder.balance.minus(value); + + if (balanceBeforeTransfer.isZero() && !holder.balance.isZero()) { + count = ONE_BI; + } else if (!balanceBeforeTransfer.isZero() && holder.balance.isZero()) { + count = ONE_BI.neg(); + } + + holder.save(); + + return count; +} + +function createOrLoadHMTStatistics(): HMTokenStatistics { + let statsEntity = HMTokenStatistics.load(HMT_STATISTICS_ENTITY_ID); + + if (!statsEntity) { + statsEntity = constructStatsEntity(); + } + + return statsEntity; +} + +export function handleTransfer(event: Transfer): void { + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalTransferEventCount = + statsEntity.totalTransferEventCount.plus(ONE_BI); + statsEntity.totalValueTransfered = statsEntity.totalValueTransfered.plus( + event.params._value + ); + + const diffHolders = updateHolders( + event.params._from, + event.params._value, + false + ).plus(updateHolders(event.params._to, event.params._value, true)); + statsEntity.holders = statsEntity.holders.plus(diffHolders); + + const eventDayData = getEventDayData(event); + eventDayData.dailyHMTTransferCount = + eventDayData.dailyHMTTransferCount.plus(ONE_BI); + eventDayData.dailyHMTTransferAmount = + eventDayData.dailyHMTTransferAmount.plus(event.params._value); + + const timestamp = event.block.timestamp.toI32(); + const dayID = timestamp / ONE_DAY; + const dayStartTimestamp = BigInt.fromI32(dayID * ONE_DAY); + + if (event.params._from.toHexString() != ZERO_ADDRESS) { + const uniqueSender = createOrLoadUniqueSender( + dayStartTimestamp, + event.block.timestamp, + event.params._from + ); + + if (uniqueSender.transferCount.equals(ZERO_BI)) { + eventDayData.dailyUniqueSenders = + eventDayData.dailyUniqueSenders.plus(ONE_BI); + } + + uniqueSender.transferCount = uniqueSender.transferCount.plus( + event.params._value + ); + uniqueSender.save(); + } + + if (event.params._to.toHexString() != ZERO_ADDRESS) { + const uniqueReceiver = createOrLoadUniqueReceiver( + dayStartTimestamp, + event.block.timestamp, + event.params._to + ); + + if (uniqueReceiver.receiveCount.equals(ZERO_BI)) { + eventDayData.dailyUniqueReceivers = + eventDayData.dailyUniqueReceivers.plus(ONE_BI); + } + + uniqueReceiver.receiveCount = uniqueReceiver.receiveCount.plus( + event.params._value + ); + uniqueReceiver.save(); + } + + eventDayData.save(); + statsEntity.save(); +} + +export function handleBulkTransfer(event: BulkTransfer): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalBulkTransferEventCount = + statsEntity.totalBulkTransferEventCount.plus(ONE_BI); + statsEntity.save(); +} + +export function handleApproval(event: Approval): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalApprovalEventCount = + statsEntity.totalApprovalEventCount.plus(ONE_BI); + statsEntity.save(); +} + +export function handleBulkApproval(event: BulkApproval): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalBulkApprovalEventCount = + statsEntity.totalBulkApprovalEventCount.plus(ONE_BI); + statsEntity.save(); +} diff --git a/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts b/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts new file mode 100644 index 0000000000..2706271424 --- /dev/null +++ b/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts @@ -0,0 +1,24 @@ +import { Bytes, ethereum } from '@graphprotocol/graph-ts'; +import { EventDayData } from '../../../generated/schema'; +import { ONE_DAY, ZERO_BI } from './number'; + +export function getEventDayData(event: ethereum.Event): EventDayData { + const timestamp = event.block.timestamp.toI32(); + const dayID = timestamp / ONE_DAY; + const dayStartTimestamp = dayID * ONE_DAY; + + const id = Bytes.fromI32(dayID); + let eventDayData = EventDayData.load(id); + + if (eventDayData == null) { + eventDayData = new EventDayData(id); + eventDayData.timestamp = dayStartTimestamp; + eventDayData.dailyHMTTransferCount = ZERO_BI; + eventDayData.dailyHMTTransferAmount = ZERO_BI; + eventDayData.dailyUniqueSenders = ZERO_BI; + eventDayData.dailyUniqueReceivers = ZERO_BI; + eventDayData.save(); + } + + return eventDayData; +} diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/number.ts b/packages/subgraph/hmt/src/mapping/utils/number.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/number.ts rename to packages/subgraph/hmt/src/mapping/utils/number.ts diff --git a/packages/subgraph/hmt/template.yaml b/packages/subgraph/hmt/template.yaml new file mode 100644 index 0000000000..ae4cb544ab --- /dev/null +++ b/packages/subgraph/hmt/template.yaml @@ -0,0 +1,37 @@ +specVersion: 1.0.0 +description: '{{ description }}' +schema: + file: ./schema.graphql +indexerHints: + prune: auto +dataSources: + - kind: ethereum + name: HMToken + network: '{{ network }}' + source: + abi: HMToken + address: '{{ HMToken.address }}' + startBlock: {{ HMToken.startBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - HMTokenStatistics + - Holder + - EventDayData + - UniqueSender + - UniqueReceiver + abis: + - name: HMToken + file: '{{{ HMToken.abi }}}' + eventHandlers: + - event: Approval(indexed address,indexed address,uint256) + handler: handleApproval + - event: BulkApproval(indexed uint256,uint256) + handler: handleBulkApproval + - event: BulkTransfer(indexed uint256,uint256) + handler: handleBulkTransfer + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTransfer + file: ./src/mapping/HMToken.ts diff --git a/packages/subgraph/hmt/tests/hmt/fixtures.ts b/packages/subgraph/hmt/tests/hmt/fixtures.ts new file mode 100644 index 0000000000..11f8ce43dd --- /dev/null +++ b/packages/subgraph/hmt/tests/hmt/fixtures.ts @@ -0,0 +1,138 @@ +import { newMockEvent } from 'matchstick-as/assembly/index'; +import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'; + +import { + Transfer, + Approval, + BulkTransfer, + BulkApproval, +} from '../../generated/HMToken/HMToken'; +import { generateUniqueHash } from '../../tests/utils'; + +const TOKEN_ADDRESS = '0xa16081f360e3847006db660bae1c6d1b2e17ffaa'; + +export function createTransferEvent( + from: string, + to: string, + value: i32, + timestamp: BigInt +): Transfer { + const transferEvent = changetype(newMockEvent()); + transferEvent.transaction.hash = generateUniqueHash( + to, + timestamp, + transferEvent.transaction.nonce + ); + transferEvent.transaction.from = Address.fromString(from); + transferEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + transferEvent.block.timestamp = timestamp; + transferEvent.block.number = timestamp; + + transferEvent.parameters = []; + transferEvent.parameters.push( + new ethereum.EventParam( + '_from', + ethereum.Value.fromAddress(Address.fromString(from)) + ) + ); + transferEvent.parameters.push( + new ethereum.EventParam( + '_to', + ethereum.Value.fromAddress(Address.fromString(to)) + ) + ); + transferEvent.parameters.push( + new ethereum.EventParam('_value', ethereum.Value.fromI32(value)) + ); + + return transferEvent; +} + +export function createApprovalEvent( + owner: string, + spender: string, + value: i32, + timestamp: BigInt +): Approval { + const approvalEvent = changetype(newMockEvent()); + approvalEvent.transaction.hash = generateUniqueHash( + owner, + timestamp, + approvalEvent.transaction.nonce + ); + approvalEvent.transaction.from = Address.fromString(owner); + approvalEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + approvalEvent.block.timestamp = timestamp; + approvalEvent.block.number = timestamp; + + approvalEvent.parameters = []; + approvalEvent.parameters.push( + new ethereum.EventParam( + '_owner', + ethereum.Value.fromAddress(Address.fromString(owner)) + ) + ); + approvalEvent.parameters.push( + new ethereum.EventParam( + '_spender', + ethereum.Value.fromAddress(Address.fromString(spender)) + ) + ); + approvalEvent.parameters.push( + new ethereum.EventParam('_value', ethereum.Value.fromI32(value)) + ); + + return approvalEvent; +} + +export function createBulkTransferEvent( + txId: i32, + bulkCount: i32, + timestamp: BigInt +): BulkTransfer { + const bulkTransferEvent = changetype(newMockEvent()); + bulkTransferEvent.transaction.hash = generateUniqueHash( + txId.toString(), + timestamp, + bulkTransferEvent.transaction.nonce + ); + bulkTransferEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + bulkTransferEvent.block.timestamp = timestamp; + bulkTransferEvent.block.number = timestamp; + + bulkTransferEvent.parameters = []; + bulkTransferEvent.parameters.push( + new ethereum.EventParam('_txId', ethereum.Value.fromI32(txId)) + ); + bulkTransferEvent.parameters.push( + new ethereum.EventParam('_bulkCount', ethereum.Value.fromI32(bulkCount)) + ); + + return bulkTransferEvent; +} + +export function createBulkApprovalEvent( + txId: i32, + bulkCount: i32, + timestamp: BigInt +): BulkApproval { + const bulkApprovalEvent = changetype(newMockEvent()); + bulkApprovalEvent.transaction.hash = generateUniqueHash( + txId.toString(), + timestamp, + bulkApprovalEvent.transaction.nonce + ); + bulkApprovalEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + bulkApprovalEvent.block.timestamp = timestamp; + bulkApprovalEvent.block.number = timestamp; + + bulkApprovalEvent.parameters = []; + bulkApprovalEvent.parameters.push( + new ethereum.EventParam('_txId', ethereum.Value.fromI32(txId)) + ); + bulkApprovalEvent.parameters.push( + new ethereum.EventParam('_bulkCount', ethereum.Value.fromI32(bulkCount)) + ); + + return bulkApprovalEvent; +} diff --git a/packages/subgraph/hmt/tests/hmt/hmt.test.ts b/packages/subgraph/hmt/tests/hmt/hmt.test.ts new file mode 100644 index 0000000000..92ab93fb4d --- /dev/null +++ b/packages/subgraph/hmt/tests/hmt/hmt.test.ts @@ -0,0 +1,302 @@ +import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'; +import { + afterAll, + assert, + beforeEach, + clearStore, + describe, + test, +} from 'matchstick-as/assembly'; + +import { + HMT_STATISTICS_ENTITY_ID, + handleApproval, + handleBulkApproval, + handleBulkTransfer, + handleTransfer, +} from '../../src/mapping/HMToken'; +import { ONE_DAY } from '../../src/mapping/utils/number'; +import { + createApprovalEvent, + createBulkApprovalEvent, + createBulkTransferEvent, + createTransferEvent, +} from './fixtures'; + +const zeroAddressString = '0x0000000000000000000000000000000000000000'; +const operatorAddressString = '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'; +const holderAddressString = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'; +const holderAddress = Address.fromString(holderAddressString); +const holder1AddressString = '0x92a2eEF7Ff696BCef98957a0189872680600a959'; +const holder1Address = Address.fromString(holder1AddressString); + +describe('HMToken', () => { + beforeEach(() => { + clearStore(); + }); + + afterAll(() => { + clearStore(); + }); + + describe('Statistics', () => { + test('Should properly calculate Transfer event in statistics', () => { + const transfer1 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 1, + BigInt.fromI32(10) + ); + const transfer2 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 2, + BigInt.fromI32(10) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalTransferEventCount', + '2' + ); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalValueTransfered', + '3' + ); + }); + + test('Should properly calculate holders in statistics', () => { + const transfer1 = createTransferEvent( + zeroAddressString, + holderAddressString, + 10, + BigInt.fromI32(10) + ); + + const transfer2 = createTransferEvent( + holderAddressString, + holder1AddressString, + 0, + BigInt.fromI32(10) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '0'); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '1' + ); + + const transfer3 = createTransferEvent( + zeroAddressString, + holder1AddressString, + 10, + BigInt.fromI32(10) + ); + + handleTransfer(transfer3); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '10'); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '2' + ); + + const transfer4 = createTransferEvent( + holderAddressString, + holder1AddressString, + 10, + BigInt.fromI32(10) + ); + + handleTransfer(transfer4); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '0'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '20'); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '1' + ); + }); + + test('Should properly calculate Transfer daily metrics in statistics', () => { + const transfer1 = createTransferEvent( + operatorAddressString, + holderAddressString, + 1, + BigInt.fromI32(100) + ); + const transfer2 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 2, + BigInt.fromI32(100) + ); + const transfer3 = createTransferEvent( + holder1AddressString, + holderAddressString, + 3, + BigInt.fromI32(100) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + handleTransfer(transfer3); + + const dayZeroId = Bytes.fromI32(0).toHex(); + assert.fieldEquals('EventDayData', dayZeroId, 'timestamp', '0'); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyHMTTransferCount', + '3' + ); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyHMTTransferAmount', + '6' + ); + assert.fieldEquals('EventDayData', dayZeroId, 'dailyUniqueSenders', '2'); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyUniqueReceivers', + '2' + ); + + const operatorSenderId = Bytes.fromI32(0) + .concat(Address.fromString(operatorAddressString)) + .toHex(); + const holder1SenderId = Bytes.fromI32(0).concat(holder1Address).toHex(); + const holderReceiverId = Bytes.fromI32(0).concat(holderAddress).toHex(); + const holder1ReceiverId = Bytes.fromI32(0).concat(holder1Address).toHex(); + + assert.fieldEquals( + 'UniqueSender', + operatorSenderId, + 'transferCount', + '3' + ); + assert.fieldEquals('UniqueSender', holder1SenderId, 'transferCount', '3'); + assert.fieldEquals( + 'UniqueReceiver', + holderReceiverId, + 'receiveCount', + '4' + ); + assert.fieldEquals( + 'UniqueReceiver', + holder1ReceiverId, + 'receiveCount', + '2' + ); + }); + + test('Should properly create separate daily stats per day', () => { + const day0Transfer = createTransferEvent( + zeroAddressString, + holderAddressString, + 5, + BigInt.fromI32(100) + ); + const day1Transfer = createTransferEvent( + zeroAddressString, + holderAddressString, + 7, + BigInt.fromI32(ONE_DAY + 100) + ); + + handleTransfer(day0Transfer); + handleTransfer(day1Transfer); + + assert.entityCount('EventDayData', 2); + + const day0Id = Bytes.fromI32(0).toHex(); + const day1Id = Bytes.fromI32(1).toHex(); + + assert.fieldEquals('EventDayData', day0Id, 'dailyHMTTransferCount', '1'); + assert.fieldEquals('EventDayData', day0Id, 'dailyHMTTransferAmount', '5'); + assert.fieldEquals('EventDayData', day0Id, 'dailyUniqueSenders', '0'); + assert.fieldEquals('EventDayData', day0Id, 'dailyUniqueReceivers', '1'); + + assert.fieldEquals('EventDayData', day1Id, 'dailyHMTTransferCount', '1'); + assert.fieldEquals('EventDayData', day1Id, 'dailyHMTTransferAmount', '7'); + assert.fieldEquals('EventDayData', day1Id, 'dailyUniqueSenders', '0'); + assert.fieldEquals('EventDayData', day1Id, 'dailyUniqueReceivers', '1'); + }); + + test('Should properly calculate BulkTransfer event in statistics', () => { + const bulkTransfer1 = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); + const bulkTransfer2 = createBulkTransferEvent(2, 3, BigInt.fromI32(10)); + + handleBulkTransfer(bulkTransfer1); + handleBulkTransfer(bulkTransfer2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalBulkTransferEventCount', + '2' + ); + }); + + test('Should properly calculate Approval event in statistics', () => { + const approval1 = createApprovalEvent( + holder1AddressString, + operatorAddressString, + 10, + BigInt.fromI32(10) + ); + + const approval2 = createApprovalEvent( + holder1AddressString, + operatorAddressString, + 10, + BigInt.fromI32(10) + ); + + handleApproval(approval1); + handleApproval(approval2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalApprovalEventCount', + '2' + ); + }); + + test('Should properly calculate BulkApproval event in statistics', () => { + const bulkApproval1 = createBulkApprovalEvent(1, 3, BigInt.fromI32(10)); + const bulkApproval2 = createBulkApprovalEvent(2, 3, BigInt.fromI32(10)); + + handleBulkApproval(bulkApproval1); + handleBulkApproval(bulkApproval2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalBulkApprovalEventCount', + '2' + ); + }); + }); +}); diff --git a/packages/subgraph/hmt/tests/utils.ts b/packages/subgraph/hmt/tests/utils.ts new file mode 100644 index 0000000000..95dd417cce --- /dev/null +++ b/packages/subgraph/hmt/tests/utils.ts @@ -0,0 +1,11 @@ +import { BigInt, Bytes } from '@graphprotocol/graph-ts'; + +export function generateUniqueHash( + value: string, + timestamp: BigInt, + nonce: BigInt +): Bytes { + const uniqueString = + value + '-' + timestamp.toString() + '-' + nonce.toString(); + return Bytes.fromUTF8(uniqueString); +} diff --git a/packages/sdk/typescript/subgraph/tsconfig.json b/packages/subgraph/hmt/tsconfig.json similarity index 73% rename from packages/sdk/typescript/subgraph/tsconfig.json rename to packages/subgraph/hmt/tsconfig.json index f9e6cb7174..3c11a89fec 100644 --- a/packages/sdk/typescript/subgraph/tsconfig.json +++ b/packages/subgraph/hmt/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "baseUrl": ".", "types": ["@graphprotocol/graph-ts", "node"] diff --git a/packages/sdk/typescript/subgraph/.gitignore b/packages/subgraph/human-protocol/.gitignore similarity index 100% rename from packages/sdk/typescript/subgraph/.gitignore rename to packages/subgraph/human-protocol/.gitignore diff --git a/packages/subgraph/human-protocol/.prettierignore b/packages/subgraph/human-protocol/.prettierignore new file mode 100644 index 0000000000..55be744ac5 --- /dev/null +++ b/packages/subgraph/human-protocol/.prettierignore @@ -0,0 +1,2 @@ +# Mustache template; not valid YAML for Prettier's YAML parser +template.yaml diff --git a/packages/subgraph/human-protocol/.prettierrc b/packages/subgraph/human-protocol/.prettierrc new file mode 100644 index 0000000000..4a2b9855ac --- /dev/null +++ b/packages/subgraph/human-protocol/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 80, + "arrowParens": "always" +} diff --git a/packages/sdk/typescript/subgraph/README.md b/packages/subgraph/human-protocol/README.md similarity index 100% rename from packages/sdk/typescript/subgraph/README.md rename to packages/subgraph/human-protocol/README.md diff --git a/packages/subgraph/human-protocol/config/amoy.json b/packages/subgraph/human-protocol/config/amoy.json new file mode 100644 index 0000000000..81bb09b1f4 --- /dev/null +++ b/packages/subgraph/human-protocol/config/amoy.json @@ -0,0 +1,25 @@ +{ + "network": "polygon-amoy", + "description": "HUMAN subgraph on Amoy Testnet", + "EscrowFactory": { + "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", + "startBlock": 5773000, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", + "startBlock": 5773002, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0xffE496683F842a923110415b7278ded3F265f2C5", + "startBlock": 14983952, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + } +} diff --git a/packages/subgraph/human-protocol/config/bsc-testnet.json b/packages/subgraph/human-protocol/config/bsc-testnet.json new file mode 100644 index 0000000000..636e82c61d --- /dev/null +++ b/packages/subgraph/human-protocol/config/bsc-testnet.json @@ -0,0 +1,33 @@ +{ + "network": "chapel", + "description": "Human subgraph on Binance Smart Chain testnet", + "EscrowFactory": { + "address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18", + "startBlock": 26716359, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xD6D347ba6987519B4e42EcED43dF98eFf5465a23", + "startBlock": 45938762, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0x32e27177BA6Ea91cf28dfd91a0Da9822A4b74EcF", + "startBlock": 34883905, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f", + "startBlock": 23632686, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/bsc.json b/packages/subgraph/human-protocol/config/bsc.json new file mode 100644 index 0000000000..e7144d3720 --- /dev/null +++ b/packages/subgraph/human-protocol/config/bsc.json @@ -0,0 +1,33 @@ +{ + "network": "bsc", + "description": "Human subgraph on BSC mainnet", + "EscrowFactory": { + "address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714", + "startBlock": 28745625, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98", + "startBlock": 45120420, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1", + "startBlock": 33941535, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0xc88bC422cAAb2ac8812de03176402dbcA09533f4", + "startBlock": 20689161, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/ethereum.json b/packages/subgraph/human-protocol/config/ethereum.json new file mode 100644 index 0000000000..5c03c57f35 --- /dev/null +++ b/packages/subgraph/human-protocol/config/ethereum.json @@ -0,0 +1,33 @@ +{ + "network": "mainnet", + "description": "Human subgraph on Ethereum Mainnet", + "EscrowFactory": { + "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", + "startBlock": 16924057, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D", + "startBlock": 18683644, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0xEf6Da3aB52c33925Be3F84038193a7e1331F51E6", + "startBlock": 21464165, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "LegacyEscrowFactory": { + "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", + "startBlock": 16924057, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/localhost.json b/packages/subgraph/human-protocol/config/localhost.json new file mode 100644 index 0000000000..9998836d75 --- /dev/null +++ b/packages/subgraph/human-protocol/config/localhost.json @@ -0,0 +1,25 @@ +{ + "network": "localhost", + "description": "Human subgraph on localhost", + "EscrowFactory": { + "address": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", + "startBlock": 5, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", + "startBlock": 3, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", + "startBlock": 6, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + } +} diff --git a/packages/subgraph/human-protocol/config/polygon.json b/packages/subgraph/human-protocol/config/polygon.json new file mode 100644 index 0000000000..c1a584b909 --- /dev/null +++ b/packages/subgraph/human-protocol/config/polygon.json @@ -0,0 +1,33 @@ +{ + "network": "matic", + "description": "Human subgraph on Polygon mainnet", + "EscrowFactory": { + "address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB", + "startBlock": 38858552, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0x01D115E9E8bF0C58318793624CC662a030D07F1D", + "startBlock": 65832028, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0xbcB28672F826a50B03EE91B28145EAbddA73B2eD", + "startBlock": 50567977, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794", + "startBlock": 25426566, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/sepolia.json b/packages/subgraph/human-protocol/config/sepolia.json new file mode 100644 index 0000000000..eca908cad1 --- /dev/null +++ b/packages/subgraph/human-protocol/config/sepolia.json @@ -0,0 +1,28 @@ +{ + "network": "sepolia", + "description": "HUMAN subgraph on Sepolia Ethereum Testnet", + "EscrowFactory": { + "address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf", + "startBlock": 7067993, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", + "startBlock": 5716238, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0x2163e3A40032Af1C359ac731deaB48258b317890", + "startBlock": 7062708, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/eslint.config.mjs b/packages/subgraph/human-protocol/eslint.config.mjs new file mode 100644 index 0000000000..666059c3d0 --- /dev/null +++ b/packages/subgraph/human-protocol/eslint.config.mjs @@ -0,0 +1,59 @@ +import eslint from '@eslint/js'; +import globals from 'globals'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import tseslint from 'typescript-eslint'; +import { flatConfigs as graphqlFlatConfigs } from '@graphql-eslint/eslint-plugin'; +const graphqlOperations = graphqlFlatConfigs['operations-recommended']; + +export default tseslint.config( + { + ignores: ['build', 'generated', 'schema.graphql'], + }, + eslint.configs.recommended, + tseslint.configs.recommended, + eslintPluginPrettierRecommended, + { + files: ['**/*.ts', '**/*.js'], + languageOptions: { + globals: { + ...globals.node, + ...globals.es2022, + }, + ecmaVersion: 2022, + sourceType: 'module', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', + 'no-console': 'warn', + '@/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: true }, + ], + }, + }, + { + files: ['**/*.graphql'], + languageOptions: { + ...(graphqlOperations.languageOptions ?? {}), + parserOptions: { + ...(graphqlOperations.languageOptions?.parserOptions ?? {}), + schema: './schema.graphql', + }, + }, + rules: graphqlOperations.rules, + }, + { + files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.test.ts', '**/*.test.tsx'], + languageOptions: { + globals: { + ...globals.jest, + }, + }, + } +); diff --git a/packages/sdk/typescript/subgraph/local-graph-status.sh b/packages/subgraph/human-protocol/local-graph-status.sh similarity index 100% rename from packages/sdk/typescript/subgraph/local-graph-status.sh rename to packages/subgraph/human-protocol/local-graph-status.sh diff --git a/packages/subgraph/human-protocol/matchstick.yaml b/packages/subgraph/human-protocol/matchstick.yaml new file mode 100644 index 0000000000..be49651fb8 --- /dev/null +++ b/packages/subgraph/human-protocol/matchstick.yaml @@ -0,0 +1 @@ +libsFolder: ../../../node_modules diff --git a/packages/sdk/typescript/subgraph/package.json b/packages/subgraph/human-protocol/package.json similarity index 85% rename from packages/sdk/typescript/subgraph/package.json rename to packages/subgraph/human-protocol/package.json index 50e13ffc6b..b52c140176 100644 --- a/packages/sdk/typescript/subgraph/package.json +++ b/packages/subgraph/human-protocol/package.json @@ -1,5 +1,5 @@ { - "name": "@tools/subgraph", + "name": "@tools/subgraph-human-protocol", "private": true, "description": "Human Protocol Subgraph", "version": "1.0.0", @@ -8,9 +8,8 @@ ], "scripts": { "clean": "rm -rf build generated subgraph.yaml", - "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && yarn generate-escrow && yarn generate-hmt && yarn generate-staking && graph codegen", + "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && yarn generate-escrow && yarn generate-staking && graph codegen", "generate-escrow": "mustache ./config/$NETWORK.json src/mapping/EscrowTemplate.ts > src/mapping/Escrow.ts", - "generate-hmt": "mustache ./config/$NETWORK.json src/mapping/HMTokenTemplate.ts > src/mapping/HMToken.ts", "generate-staking": "mustache ./config/$NETWORK.json src/mapping/StakingTemplate.ts > src/mapping/Staking.ts", "codegen": "graph codegen", "build": "graph build", @@ -27,7 +26,7 @@ "repository": { "type": "git", "url": "https://github.com/humanprotocol/human-protocol.git", - "directory": "packages/sdk/typescript/subgraph" + "directory": "packages/sdk/typescript/subgraph/human-protocol" }, "keywords": [ "human-protocol", @@ -46,7 +45,7 @@ "graphql": "^16.6.0", "matchstick-as": "^0.6.0", "mustache": "^4.2.0", - "prettier": "^3.7.4" + "prettier": "^3.8.1" }, "lint-staged": { "*.{ts,graphql}": [ diff --git a/packages/sdk/typescript/subgraph/schema.graphql b/packages/subgraph/human-protocol/schema.graphql similarity index 57% rename from packages/sdk/typescript/subgraph/schema.graphql rename to packages/subgraph/human-protocol/schema.graphql index e4d122605c..6204cc3577 100644 --- a/packages/sdk/typescript/subgraph/schema.graphql +++ b/packages/subgraph/human-protocol/schema.graphql @@ -2,33 +2,12 @@ # Entities # ################################################## -type Holder @entity(immutable: false) { - id: Bytes! - address: Bytes! # address - balance: BigInt! -} - type Worker @entity(immutable: false) { id: Bytes! address: Bytes! - totalHMTAmountReceived: BigInt! payoutCount: BigInt! } -type UniqueSender @entity(immutable: false) { - id: Bytes! - address: Bytes! - transferCount: BigInt! - timestamp: BigInt! -} - -type UniqueReceiver @entity(immutable: false) { - id: Bytes! - address: Bytes! - receiveCount: BigInt! - timestamp: BigInt! -} - type Operator @entity(immutable: false) { id: Bytes! address: Bytes! @@ -111,94 +90,6 @@ type DailyWorker @entity(immutable: true) { # Events # ################################################## -# HMToken -type HMTTransferEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - from: Bytes! # address - to: Bytes! # address - amount: BigInt! -} - -type HMTBulkTransferEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - txId: BigInt! - bulkCount: BigInt! -} - -type HMTApprovalEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - owner: Bytes! # address - spender: Bytes! # address - amount: BigInt! -} - -type HMTBulkApprovalEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - txId: BigInt! - bulkCount: BigInt! -} - -# Escrow -type FundEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - escrowAddress: Bytes! # address - sender: Bytes! # address - amount: BigInt! -} - -type PendingEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - escrowAddress: Bytes! # address - sender: Bytes! # address -} - -type StoreResultsEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - escrowAddress: Bytes! # address - sender: Bytes! # address - intermediateResultsUrl: String! # string - intermediateResultsHash: String! # string -} - -type BulkPayoutEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - escrowAddress: Bytes! - sender: Bytes! - payoutId: String! - bulkCount: BigInt! -} - type EscrowStatusEvent @entity(immutable: true) { id: Bytes! block: BigInt! @@ -211,19 +102,6 @@ type EscrowStatusEvent @entity(immutable: true) { status: String! } -type WithdrawEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - escrowAddress: Bytes! # address - sender: Bytes! # address - receiver: Bytes! # address - token: Bytes! # address - amount: BigInt! -} - type CancellationRefundEvent @entity(immutable: true) { id: Bytes! block: BigInt! @@ -235,17 +113,6 @@ type CancellationRefundEvent @entity(immutable: true) { } # KVStore -type KVStoreSetEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - operatorAddress: Bytes! # address - key: String! - value: String! -} - type KVStore @entity(immutable: false) { id: Bytes! block: BigInt! @@ -269,63 +136,10 @@ type Staker @entity(immutable: false) { operator: Operator } -type StakeDepositedEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - staker: Bytes! # address - amount: BigInt! -} - -type StakeLockedEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - staker: Bytes! # address - amount: BigInt! - lockedUntilTimestamp: BigInt! -} - -type StakeWithdrawnEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - staker: Bytes! # address - amount: BigInt! -} - -type StakeSlashedEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - escrowAddress: Bytes! # address - staker: Bytes! # address - slashRequester: Bytes! # address - amount: BigInt! -} - ################################################## # Statistics # ################################################## -type HMTokenStatistics @entity(immutable: false) { - id: Bytes! - totalTransferEventCount: BigInt! - totalBulkTransferEventCount: BigInt! - totalApprovalEventCount: BigInt! - totalBulkApprovalEventCount: BigInt! - totalValueTransfered: BigInt! - holders: BigInt! -} - type EscrowStatistics @entity(immutable: false) { id: Bytes! fundEventCount: BigInt! @@ -357,11 +171,6 @@ type EventDayData @entity(immutable: false) { dailyEscrowCount: BigInt! dailyWorkerCount: BigInt! dailyPayoutCount: BigInt! - dailyHMTPayoutAmount: BigInt! - dailyHMTTransferCount: BigInt! - dailyHMTTransferAmount: BigInt! - dailyUniqueSenders: BigInt! - dailyUniqueReceivers: BigInt! } ################################################## diff --git a/packages/sdk/typescript/subgraph/src/mapping/EscrowFactory.ts b/packages/subgraph/human-protocol/src/mapping/EscrowFactory.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/EscrowFactory.ts rename to packages/subgraph/human-protocol/src/mapping/EscrowFactory.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts b/packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts similarity index 81% rename from packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts rename to packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts index 83d89d7f0a..88685f7332 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts +++ b/packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts @@ -9,24 +9,20 @@ import { Pending, Fund, PendingV2, + PendingV3, Withdraw, CancellationRequested, CancellationRefund, } from '../../generated/templates/Escrow/Escrow'; import { - BulkPayoutEvent, CancellationRefundEvent, Escrow, EscrowStatistics, EscrowStatusEvent, - FundEvent, - PendingEvent, - StoreResultsEvent, Worker, Payout, DailyWorker, InternalTransaction, - WithdrawEvent, Operator, } from '../../generated/schema'; import { @@ -43,7 +39,6 @@ import { createTransaction } from './utils/transaction'; import { toBytes } from './utils/string'; import { createOrLoadOperator } from './KVStore'; -export const HMT_ADDRESS = Address.fromString('{{ HMToken.address }}'); export const STATISTICS_ENTITY_ID = toBytes('escrow-statistics-id'); function constructStatsEntity(): EscrowStatistics { @@ -80,7 +75,6 @@ export function createOrLoadWorker(address: Address): Worker { if (!worker) { worker = new Worker(address); worker.address = address; - worker.totalHMTAmountReceived = ZERO_BI; worker.payoutCount = ZERO_BI; } @@ -114,15 +108,6 @@ function createCommonEntitiesForPending( event: ethereum.Event, status: string ): EscrowStatusEvent { - // Create pendingEvent entity - const pendingEventEntity = new PendingEvent(toEventId(event)); - pendingEventEntity.block = event.block.number; - pendingEventEntity.timestamp = event.block.timestamp; - pendingEventEntity.txHash = event.transaction.hash; - pendingEventEntity.escrowAddress = event.address; - pendingEventEntity.sender = event.transaction.from; - pendingEventEntity.save(); - // Create EscrowStatusEvent entity const statusEventEntity = new EscrowStatusEvent(toEventId(event)); statusEventEntity.block = event.block.number; @@ -144,7 +129,10 @@ function updateEscrowEntityForPending( manifestHash: string, reputationOracle: Address | null = null, recordingOracle: Address | null = null, - exchangeOracle: Address | null = null + exchangeOracle: Address | null = null, + reputationOracleFee: BigInt | null = null, + recordingOracleFee: BigInt | null = null, + exchangeOracleFee: BigInt | null = null ): void { escrowEntity.manifest = manifest; escrowEntity.manifestHash = manifestHash; @@ -153,25 +141,37 @@ function updateEscrowEntityForPending( // Update oracles if provided if (reputationOracle) { escrowEntity.reputationOracle = reputationOracle; - const reputationOracleEntity = Operator.load(reputationOracle); - if (reputationOracleEntity) { - escrowEntity.reputationOracleFee = reputationOracleEntity.fee; + if (reputationOracleFee) { + escrowEntity.reputationOracleFee = reputationOracleFee; + } else { + const reputationOracleEntity = Operator.load(reputationOracle); + if (reputationOracleEntity) { + escrowEntity.reputationOracleFee = reputationOracleEntity.fee; + } } } if (recordingOracle) { escrowEntity.recordingOracle = recordingOracle; - const recordingOracleEntity = Operator.load(recordingOracle); - if (recordingOracleEntity) { - escrowEntity.recordingOracleFee = recordingOracleEntity.fee; + if (recordingOracleFee) { + escrowEntity.recordingOracleFee = recordingOracleFee; + } else { + const recordingOracleEntity = Operator.load(recordingOracle); + if (recordingOracleEntity) { + escrowEntity.recordingOracleFee = recordingOracleEntity.fee; + } } } if (exchangeOracle) { escrowEntity.exchangeOracle = exchangeOracle; - const exchangeOracleEntity = Operator.load(exchangeOracle); - if (exchangeOracleEntity) { - escrowEntity.exchangeOracleFee = exchangeOracleEntity.fee; + if (exchangeOracleFee) { + escrowEntity.exchangeOracleFee = exchangeOracleFee; + } else { + const exchangeOracleEntity = Operator.load(exchangeOracle); + if (exchangeOracleEntity) { + escrowEntity.exchangeOracleFee = exchangeOracleEntity.fee; + } } } @@ -283,18 +283,44 @@ export function handlePendingV2(event: PendingV2): void { } } -export function handleIntermediateStorage(event: IntermediateStorage): void { - // Create StoreResultsEvent entity - const eventEntity = new StoreResultsEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.escrowAddress = event.address; - eventEntity.sender = event.transaction.from; - eventEntity.intermediateResultsUrl = event.params.url; - eventEntity.intermediateResultsHash = event.params.hash; - eventEntity.save(); +export function handlePendingV3(event: PendingV3): void { + // Create common entities for setup and status + const escrowStatusEvent = createCommonEntitiesForPending(event, 'Pending'); + + // Update statistics + updateStatisticsForPending(); + + // Update event day data + updateEventDayDataForPending(event); + + // Update escrow entity + const escrowEntity = Escrow.load(dataSource.address()); + if (escrowEntity) { + updateEscrowEntityForPending( + escrowEntity, + escrowStatusEvent, + event.params.manifest, + event.params.hash, + event.params.reputationOracle, + event.params.recordingOracle, + event.params.exchangeOracle, + BigInt.fromI32(event.params.reputationOracleFeePercentage), + BigInt.fromI32(event.params.recordingOracleFeePercentage), + BigInt.fromI32(event.params.exchangeOracleFeePercentage) + ); + + createTransaction( + event, + 'setup', + event.transaction.from, + Address.fromBytes(escrowEntity.address), + null, + Address.fromBytes(escrowEntity.address) + ); + } +} +export function handleIntermediateStorage(event: IntermediateStorage): void { // Updates escrow statistics const statsEntity = createOrLoadEscrowStatistics(); statsEntity.storeResultsEventCount = @@ -327,23 +353,6 @@ export function handleIntermediateStorage(event: IntermediateStorage): void { } } -// Create BulkPayoutEvent entity -function createBulkPayoutEvent( - event: ethereum.Event, - payoutId: string, - recipientsLength: number -): void { - const eventEntity = new BulkPayoutEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.escrowAddress = event.address; - eventEntity.sender = event.transaction.from; - eventEntity.payoutId = payoutId; - eventEntity.bulkCount = BigInt.fromI32(recipientsLength); - eventEntity.save(); -} - // Update escrow statistics function updateEscrowStatisticsForBulkTransfer(isPartial: boolean): void { const statsEntity = createOrLoadEscrowStatistics(); @@ -429,13 +438,6 @@ function updateEscrowEntityForBulkTransfer( } export function handleBulkTransfer(event: BulkTransfer): void { - // Create BulkPayoutEvent entity - createBulkPayoutEvent( - event, - event.params.txId.toString(), - event.params.recipients.length - ); - // Update escrow statistics updateEscrowStatisticsForBulkTransfer(event.params.isPartial); @@ -476,16 +478,12 @@ export function handleBulkTransfer(event: BulkTransfer): void { function handleBulkTransferCommon( event: ethereum.Event, - payoutId: string, recipients: Address[], amounts: BigInt[], isPartial: boolean, finalResultsUrl: string, finalResultsHash: string | null ): void { - // Create BulkPayoutEvent entity - createBulkPayoutEvent(event, payoutId, recipients.length); - // Update escrow statistics updateEscrowStatisticsForBulkTransfer(isPartial); @@ -547,21 +545,20 @@ function handleBulkTransferCommon( } eventDayData.save(); - // If the escrow is non-HMT, create the internal transaction - if (Address.fromBytes(escrowEntity.token) != HMT_ADDRESS) { - const internalTransaction = new InternalTransaction( - event.transaction.hash - .concatI32(i) - .concatI32(event.block.timestamp.toI32()) - ); - internalTransaction.from = Address.fromBytes(escrowEntity.address); - internalTransaction.to = recipient; - internalTransaction.value = amount; - internalTransaction.transaction = transaction.id; - internalTransaction.method = 'transfer'; - internalTransaction.escrow = Address.fromBytes(escrowEntity.address); - internalTransaction.save(); - } + const internalTransaction = new InternalTransaction( + event.transaction.hash + .concatI32(i) + .concatI32(event.block.timestamp.toI32()) + ); + internalTransaction.from = Address.fromBytes(escrowEntity.address); + internalTransaction.to = recipient; + internalTransaction.value = amount; + internalTransaction.transaction = transaction.id; + internalTransaction.method = 'transfer'; + internalTransaction.escrow = Address.fromBytes(escrowEntity.address); + internalTransaction.token = Address.fromBytes(escrowEntity.token); + internalTransaction.receiver = recipient; + internalTransaction.save(); } // Assign finalResultsUrl directly from the event @@ -584,7 +581,6 @@ function handleBulkTransferCommon( export function handleBulkTransferV2(event: BulkTransferV2): void { handleBulkTransferCommon( event, - event.params.txId.toString(), event.params.recipients, event.params.amounts, event.params.isPartial, @@ -596,7 +592,6 @@ export function handleBulkTransferV2(event: BulkTransferV2): void { export function handleBulkTransferV3(event: BulkTransferV3): void { handleBulkTransferCommon( event, - event.params.payoutId.toHex(), event.params.recipients, event.params.amounts, event.params.isPartial, @@ -685,11 +680,7 @@ export function handleCompleted(event: Completed): void { null, Address.fromBytes(escrowEntity.address) ); - if ( - escrowEntity.balance && - escrowEntity.balance.gt(ZERO_BI) && - escrowEntity.token != HMT_ADDRESS - ) { + if (escrowEntity.balance && escrowEntity.balance.gt(ZERO_BI)) { const internalTransaction = new InternalTransaction(toEventId(event)); internalTransaction.from = escrowEntity.address; internalTransaction.to = escrowEntity.launcher; @@ -698,6 +689,7 @@ export function handleCompleted(event: Completed): void { internalTransaction.method = 'transfer'; internalTransaction.escrow = escrowEntity.address; internalTransaction.token = escrowEntity.token; + internalTransaction.receiver = escrowEntity.launcher; internalTransaction.save(); escrowEntity.balance = ZERO_BI; @@ -716,15 +708,16 @@ export function handleFund(event: Fund): void { return; } - // Create FundEvent entity - const fundEventEntity = new FundEvent(toEventId(event)); - fundEventEntity.block = event.block.number; - fundEventEntity.timestamp = event.block.timestamp; - fundEventEntity.txHash = event.transaction.hash; - fundEventEntity.escrowAddress = event.address; - fundEventEntity.sender = event.transaction.from; - fundEventEntity.amount = event.params.amount; - fundEventEntity.save(); + createTransaction( + event, + 'fund', + event.transaction.from, + Address.fromBytes(escrowEntity.address), + null, + Address.fromBytes(escrowEntity.address), + event.params.amount, + Address.fromBytes(escrowEntity.token) + ); // Update escrow statistics const statsEntity = createOrLoadEscrowStatistics(); @@ -743,9 +736,7 @@ export function handleFund(event: Fund): void { // Update escrow entity escrowEntity.totalFundedAmount = event.params.amount; - if (escrowEntity.token != HMT_ADDRESS) { - escrowEntity.balance = event.params.amount; - } + escrowEntity.balance = event.params.amount; escrowEntity.save(); } @@ -767,18 +758,6 @@ export function handleWithdraw(event: Withdraw): void { event.params.amount, event.params.token ); - - // Create WithdrawEvent entity - const withdrawEventEntity = new WithdrawEvent(toEventId(event)); - withdrawEventEntity.block = event.block.number; - withdrawEventEntity.timestamp = event.block.timestamp; - withdrawEventEntity.txHash = event.transaction.hash; - withdrawEventEntity.escrowAddress = event.address; - withdrawEventEntity.sender = event.transaction.from; - withdrawEventEntity.receiver = escrowEntity.canceler; - withdrawEventEntity.amount = event.params.amount; - withdrawEventEntity.token = event.params.token; - withdrawEventEntity.save(); } export function handleCancellationRequested( @@ -841,18 +820,15 @@ export function handleCancellationRefund(event: CancellationRefund): void { event.params.amount, Address.fromBytes(escrowEntity.token) ); - if (Address.fromBytes(escrowEntity.token) != HMT_ADDRESS) { - // If escrow is funded with HMT, balance is already tracked by HMT transfer - const internalTransaction = new InternalTransaction(toEventId(event)); - internalTransaction.from = escrowEntity.address; - internalTransaction.to = Address.fromBytes(escrowEntity.token); - internalTransaction.receiver = escrowEntity.canceler; - internalTransaction.value = escrowEntity.balance; - internalTransaction.transaction = transaction.id; - internalTransaction.method = 'transfer'; - internalTransaction.token = Address.fromBytes(escrowEntity.token); - internalTransaction.save(); - } + const internalTransaction = new InternalTransaction(toEventId(event)); + internalTransaction.from = escrowEntity.address; + internalTransaction.to = Address.fromBytes(escrowEntity.token); + internalTransaction.receiver = escrowEntity.canceler; + internalTransaction.value = escrowEntity.balance; + internalTransaction.transaction = transaction.id; + internalTransaction.method = 'transfer'; + internalTransaction.token = Address.fromBytes(escrowEntity.token); + internalTransaction.save(); escrowEntity.balance = escrowEntity.balance.minus(event.params.amount); escrowEntity.save(); diff --git a/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts b/packages/subgraph/human-protocol/src/mapping/KVStore.ts similarity index 91% rename from packages/sdk/typescript/subgraph/src/mapping/KVStore.ts rename to packages/subgraph/human-protocol/src/mapping/KVStore.ts index 7cf03a445d..7d63f404d7 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts +++ b/packages/subgraph/human-protocol/src/mapping/KVStore.ts @@ -8,14 +8,12 @@ import { import { DataSaved } from '../../generated/KVStore/KVStore'; import { KVStore, - KVStoreSetEvent, Operator, OperatorURL, ReputationNetwork, Staker, } from '../../generated/schema'; import { isValidEthAddress } from './utils/ethAdrress'; -import { toEventId } from './utils/event'; import { toBytes } from './utils/string'; import { createTransaction } from './utils/transaction'; import { store } from '@graphprotocol/graph-ts'; @@ -93,15 +91,6 @@ export function createOrUpdateKVStore(event: DataSaved): void { export function handleDataSaved(event: DataSaved): void { createTransaction(event, 'set', event.transaction.from, dataSource.address()); - // Create KVStoreSetEvent entity - const eventEntity = new KVStoreSetEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.operatorAddress = event.params.sender; - eventEntity.key = event.params.key; - eventEntity.value = event.params.value; - eventEntity.save(); // Update KVStore entity createOrUpdateKVStore(event); diff --git a/packages/sdk/typescript/subgraph/src/mapping/StakingTemplate.ts b/packages/subgraph/human-protocol/src/mapping/StakingTemplate.ts similarity index 53% rename from packages/sdk/typescript/subgraph/src/mapping/StakingTemplate.ts rename to packages/subgraph/human-protocol/src/mapping/StakingTemplate.ts index 30cc8a395f..9253698a77 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/StakingTemplate.ts +++ b/packages/subgraph/human-protocol/src/mapping/StakingTemplate.ts @@ -5,17 +5,9 @@ import { StakeSlashed, StakeWithdrawn, } from '../../generated/Staking/Staking'; -import { - Operator, - StakeDepositedEvent, - StakeLockedEvent, - Staker, - StakeSlashedEvent, - StakeWithdrawnEvent, -} from '../../generated/schema'; +import { Operator, Staker } from '../../generated/schema'; import { Address, dataSource } from '@graphprotocol/graph-ts'; import { ZERO_BI } from './utils/number'; -import { toEventId } from './utils/event'; import { createTransaction } from './utils/transaction'; export const TOKEN_ADDRESS = Address.fromString('{{ HMToken.address }}'); @@ -55,19 +47,10 @@ export function handleStakeDeposited(event: StakeDeposited): void { event.params.tokens, TOKEN_ADDRESS ); - // Create StakeDepostiedEvent entity - const eventEntity = new StakeDepositedEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.staker = event.params.staker; - eventEntity.amount = event.params.tokens; - eventEntity.save(); - // Update staker const staker = createOrLoadStaker(event.params.staker); - staker.stakedAmount = staker.stakedAmount.plus(eventEntity.amount); + staker.stakedAmount = staker.stakedAmount.plus(event.params.tokens); staker.lastDepositTimestamp = event.block.timestamp; staker.save(); @@ -84,20 +67,10 @@ export function handleStakeLocked(event: StakeLocked): void { event.params.tokens, TOKEN_ADDRESS ); - // Create StakeLockedEvent entity - const eventEntity = new StakeLockedEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.staker = event.params.staker; - eventEntity.amount = event.params.tokens; - eventEntity.lockedUntilTimestamp = event.params.until; - eventEntity.save(); - // Update staker const staker = createOrLoadStaker(event.params.staker); - staker.lockedAmount = eventEntity.amount; - staker.lockedUntilTimestamp = eventEntity.lockedUntilTimestamp; + staker.lockedAmount = event.params.tokens; + staker.lockedUntilTimestamp = event.params.until; staker.save(); } @@ -112,23 +85,14 @@ export function handleStakeWithdrawn(event: StakeWithdrawn): void { event.params.tokens, TOKEN_ADDRESS ); - // Create StakeWithdrawnEvent entity - const eventEntity = new StakeWithdrawnEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.staker = event.params.staker; - eventEntity.amount = event.params.tokens; - eventEntity.save(); - // Update staker const staker = createOrLoadStaker(event.params.staker); - staker.lockedAmount = staker.lockedAmount.minus(eventEntity.amount); + staker.lockedAmount = staker.lockedAmount.minus(event.params.tokens); if (staker.lockedAmount.equals(ZERO_BI)) { staker.lockedUntilTimestamp = ZERO_BI; } - staker.stakedAmount = staker.stakedAmount.minus(eventEntity.amount); - staker.withdrawnAmount = staker.withdrawnAmount.plus(eventEntity.amount); + staker.stakedAmount = staker.stakedAmount.minus(event.params.tokens); + staker.withdrawnAmount = staker.withdrawnAmount.plus(event.params.tokens); staker.save(); } @@ -143,21 +107,10 @@ export function handleStakeSlashed(event: StakeSlashed): void { event.params.tokens, TOKEN_ADDRESS ); - // Create StakeSlashedEvent entity - const eventEntity = new StakeSlashedEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.staker = event.params.staker; - eventEntity.amount = event.params.tokens; - eventEntity.escrowAddress = event.params.escrowAddress; - eventEntity.slashRequester = event.params.slashRequester; - eventEntity.save(); - // Update staker const staker = createOrLoadStaker(event.params.staker); - staker.slashedAmount = staker.slashedAmount.plus(eventEntity.amount); - staker.stakedAmount = staker.stakedAmount.minus(eventEntity.amount); + staker.slashedAmount = staker.slashedAmount.plus(event.params.tokens); + staker.stakedAmount = staker.stakedAmount.minus(event.params.tokens); staker.save(); } diff --git a/packages/sdk/typescript/subgraph/src/mapping/legacy/Escrow.ts b/packages/subgraph/human-protocol/src/mapping/legacy/Escrow.ts similarity index 79% rename from packages/sdk/typescript/subgraph/src/mapping/legacy/Escrow.ts rename to packages/subgraph/human-protocol/src/mapping/legacy/Escrow.ts index 7e39c4ae6a..11ab81ec67 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/legacy/Escrow.ts +++ b/packages/subgraph/human-protocol/src/mapping/legacy/Escrow.ts @@ -6,15 +6,9 @@ import { IntermediateStorage, Pending, } from '../../../generated/templates/LegacyEscrow/Escrow'; -import { - BulkPayoutEvent, - Escrow, - PendingEvent, - StoreResultsEvent, -} from '../../../generated/schema'; +import { Escrow } from '../../../generated/schema'; import { createOrLoadEscrowStatistics } from '../Escrow'; import { ONE_BI } from '../utils/number'; -import { toEventId } from '../utils/event'; import { getEventDayData } from '../utils/dayUpdates'; import { createTransaction } from '../utils/transaction'; @@ -28,15 +22,6 @@ enum EscrowStatuses { } export function handlePending(event: Pending): void { - // Create PendingEvent entity - const pendingEventEntity = new PendingEvent(toEventId(event)); - pendingEventEntity.block = event.block.number; - pendingEventEntity.timestamp = event.block.timestamp; - pendingEventEntity.txHash = event.transaction.hash; - pendingEventEntity.escrowAddress = dataSource.address(); - pendingEventEntity.sender = event.transaction.from; - pendingEventEntity.save(); - // Updates escrow statistics const statsEntity = createOrLoadEscrowStatistics(); statsEntity.pendingStatusEventCount = @@ -88,17 +73,6 @@ export function handlePending(event: Pending): void { } export function handleIntermediateStorage(event: IntermediateStorage): void { - // Create StoreResultsEvent entity - const eventEntity = new StoreResultsEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.escrowAddress = event.address; - eventEntity.sender = event.transaction.from; - eventEntity.intermediateResultsUrl = event.params._url; - eventEntity.intermediateResultsHash = event.params._hash; - eventEntity.save(); - // Updates escrow statistics const statsEntity = createOrLoadEscrowStatistics(); statsEntity.storeResultsEventCount = @@ -133,17 +107,6 @@ export function handleIntermediateStorage(event: IntermediateStorage): void { } export function handleBulkTransfer(event: BulkTransfer): void { - // Create BulkPayoutEvent entity - const eventEntity = new BulkPayoutEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.escrowAddress = event.address; - eventEntity.sender = event.transaction.from; - eventEntity.payoutId = event.params._txId.toString(); - eventEntity.bulkCount = event.params._bulkCount; - eventEntity.save(); - // Update escrow statistics const statsEntity = createOrLoadEscrowStatistics(); statsEntity.bulkPayoutEventCount = diff --git a/packages/sdk/typescript/subgraph/src/mapping/legacy/EscrowFactory.ts b/packages/subgraph/human-protocol/src/mapping/legacy/EscrowFactory.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/legacy/EscrowFactory.ts rename to packages/subgraph/human-protocol/src/mapping/legacy/EscrowFactory.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts b/packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts similarity index 84% rename from packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts rename to packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts index 6d615b15a5..463f0db316 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts +++ b/packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts @@ -26,11 +26,6 @@ export function getEventDayData(event: ethereum.Event): EventDayData { eventDayData.dailyEscrowCount = ZERO_BI; eventDayData.dailyWorkerCount = ZERO_BI; eventDayData.dailyPayoutCount = ZERO_BI; - eventDayData.dailyHMTPayoutAmount = ZERO_BI; - eventDayData.dailyHMTTransferCount = ZERO_BI; - eventDayData.dailyHMTTransferAmount = ZERO_BI; - eventDayData.dailyUniqueSenders = ZERO_BI; - eventDayData.dailyUniqueReceivers = ZERO_BI; } return eventDayData; } diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/ethAdrress.ts b/packages/subgraph/human-protocol/src/mapping/utils/ethAdrress.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/ethAdrress.ts rename to packages/subgraph/human-protocol/src/mapping/utils/ethAdrress.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/event.ts b/packages/subgraph/human-protocol/src/mapping/utils/event.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/event.ts rename to packages/subgraph/human-protocol/src/mapping/utils/event.ts diff --git a/packages/subgraph/human-protocol/src/mapping/utils/number.ts b/packages/subgraph/human-protocol/src/mapping/utils/number.ts new file mode 100644 index 0000000000..476fba0237 --- /dev/null +++ b/packages/subgraph/human-protocol/src/mapping/utils/number.ts @@ -0,0 +1,6 @@ +import { BigInt } from '@graphprotocol/graph-ts'; + +export const ZERO_BI = BigInt.fromI32(0); +export const ONE_BI = BigInt.fromI32(1); + +export const ONE_DAY = 86400; diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/string.ts b/packages/subgraph/human-protocol/src/mapping/utils/string.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/string.ts rename to packages/subgraph/human-protocol/src/mapping/utils/string.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/transaction.ts b/packages/subgraph/human-protocol/src/mapping/utils/transaction.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/transaction.ts rename to packages/subgraph/human-protocol/src/mapping/utils/transaction.ts diff --git a/packages/sdk/typescript/subgraph/template.yaml b/packages/subgraph/human-protocol/template.yaml similarity index 79% rename from packages/sdk/typescript/subgraph/template.yaml rename to packages/subgraph/human-protocol/template.yaml index fa3b45fd4f..3217e18a10 100644 --- a/packages/sdk/typescript/subgraph/template.yaml +++ b/packages/subgraph/human-protocol/template.yaml @@ -33,46 +33,6 @@ dataSources: handler: handleLaunched - event: LaunchedV2(address,address,string) handler: handleLaunchedV2 - - kind: ethereum - name: HMToken - network: '{{ network }}' - source: - abi: HMToken - address: '{{ HMToken.address }}' - startBlock: {{ HMToken.startBlock }} - mapping: - kind: ethereum/events - apiVersion: 0.0.7 - language: wasm/assemblyscript - entities: - - DailyWorker - - Escrow - - EventDayData - - HMTApprovalEvent - - HMTBulkApprovalEvent - - HMTBulkTransferEvent - - HMTTransferEvent - - HMTokenStatistics - - Holder - - Payout - - UniqueReceiver - - UniqueSender - - Worker - - Transaction - - InternalTransaction - abis: - - name: HMToken - file: '{{{ HMToken.abi }}}' - eventHandlers: - - event: Approval(indexed address,indexed address,uint256) - handler: handleApproval - - event: BulkApproval(indexed uint256,uint256) - handler: handleBulkApproval - - event: BulkTransfer(indexed uint256,uint256) - handler: handleBulkTransfer - - event: Transfer(indexed address,indexed address,uint256) - handler: handleTransfer - file: ./src/mapping/HMToken.ts - kind: ethereum name: KVStore network: '{{ network }}' @@ -86,7 +46,6 @@ dataSources: language: wasm/assemblyscript entities: - KVStore - - KVStoreSetEvent - Operator - OperatorURL - ReputationNetwork @@ -109,10 +68,6 @@ dataSources: apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - StakeDepositedEvent - - StakeLockedEvent - - StakeWithdrawnEvent - - StakeSlashedEvent - FeeWithdrawn - Operator - OperatorStatistics @@ -176,14 +131,9 @@ templates: - Worker - EscrowStatistics - EventDayData - - PendingEvent - EscrowStatusEvent - Operator - - StoreResultsEvent - - BulkPayoutEvent - DailyWorker - - FundEvent - - WithdrawEvent - Transaction - InternalTransaction abis: @@ -196,6 +146,8 @@ templates: handler: handlePending - event: PendingV2(string,string,address,address,address) handler: handlePendingV2 + - event: PendingV3(string,string,address,address,address,uint8,uint8,uint8) + handler: handlePendingV3 - event: BulkTransfer(indexed uint256,address[],uint256[],bool) handler: handleBulkTransfer - event: BulkTransferV2(indexed uint256,address[],uint256[],bool,string) @@ -232,8 +184,6 @@ templates: - Escrow - Transaction - InternalTransaction - - StoreResultsEvent - - BulkPayoutEvent abis: - name: Escrow file: '{{{ LegacyEscrow.abi }}}' @@ -244,4 +194,4 @@ templates: handler: handlePending - event: BulkTransfer(indexed uint256,uint256) handler: handleBulkTransfer -{{ /LegacyEscrow }} \ No newline at end of file +{{ /LegacyEscrow }} diff --git a/packages/sdk/typescript/subgraph/tests/escrow-factory/escrow-factory.test.ts b/packages/subgraph/human-protocol/tests/escrow-factory/escrow-factory.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/escrow-factory/escrow-factory.test.ts rename to packages/subgraph/human-protocol/tests/escrow-factory/escrow-factory.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/escrow-factory/fixtures.ts b/packages/subgraph/human-protocol/tests/escrow-factory/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/escrow-factory/fixtures.ts rename to packages/subgraph/human-protocol/tests/escrow-factory/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts b/packages/subgraph/human-protocol/tests/escrow/escrow.test.ts similarity index 86% rename from packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts rename to packages/subgraph/human-protocol/tests/escrow/escrow.test.ts index 9fabb437ba..26a8d442f1 100644 --- a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts +++ b/packages/subgraph/human-protocol/tests/escrow/escrow.test.ts @@ -31,6 +31,7 @@ import { handleIntermediateStorage, handlePending, handlePendingV2, + handlePendingV3, handleWithdraw, } from '../../src/mapping/Escrow'; import { toEventId } from '../../src/mapping/utils/event'; @@ -47,6 +48,7 @@ import { createISEvent, createPendingEvent, createPendingV2Event, + createPendingV3Event, createWithdrawEvent, } from './fixtures'; @@ -158,31 +160,6 @@ describe('Escrow', () => { const id = toEventId(newPending1).toHex(); // PendingEvent - assert.fieldEquals( - 'PendingEvent', - id, - 'block', - newPending1.block.number.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'timestamp', - newPending1.block.timestamp.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'txHash', - newPending1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); // EscrowStatusEvent assert.fieldEquals( @@ -309,31 +286,6 @@ describe('Escrow', () => { const id = toEventId(newPending1).toHex(); // PendingEvent - assert.fieldEquals( - 'PendingEvent', - id, - 'block', - newPending1.block.number.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'timestamp', - newPending1.block.timestamp.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'txHash', - newPending1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); // EscrowStatusEvent assert.fieldEquals( @@ -475,31 +427,6 @@ describe('Escrow', () => { const id = toEventId(newPending1).toHex(); // PendingEvent - assert.fieldEquals( - 'PendingEvent', - id, - 'block', - newPending1.block.number.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'timestamp', - newPending1.block.timestamp.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'txHash', - newPending1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); // EscrowStatusEvent assert.fieldEquals( @@ -640,25 +567,25 @@ describe('Escrow', () => { handleFund(fund); - const id = toEventId(fund).toHex(); - - // FundEvent - assert.fieldEquals('FundEvent', id, 'block', fund.block.number.toString()); + // Transaction assert.fieldEquals( - 'FundEvent', - id, - 'timestamp', - fund.block.timestamp.toString() + 'Transaction', + fund.transaction.hash.toHex(), + 'method', + 'fund' ); assert.fieldEquals( - 'FundEvent', - id, - 'txHash', - fund.transaction.hash.toHex() + 'Transaction', + fund.transaction.hash.toHex(), + 'to', + escrowAddressString + ); + assert.fieldEquals( + 'Transaction', + fund.transaction.hash.toHex(), + 'value', + '100' ); - assert.fieldEquals('FundEvent', id, 'escrowAddress', escrowAddressString); - assert.fieldEquals('FundEvent', id, 'sender', operatorAddressString); - assert.fieldEquals('FundEvent', id, 'amount', '100'); // Escrow assert.fieldEquals('Escrow', escrowAddressString, 'balance', '100'); @@ -704,31 +631,6 @@ describe('Escrow', () => { const id = toEventId(newPending1).toHex(); // PendingEvent - assert.fieldEquals( - 'PendingEvent', - id, - 'block', - newPending1.block.number.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'timestamp', - newPending1.block.timestamp.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'txHash', - newPending1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); // EscrowStatusEvent assert.fieldEquals( @@ -858,47 +760,62 @@ describe('Escrow', () => { ); }); - test('should properly handle IntermediateStorage event', () => { - const URL = 'test.com'; - const HASH = 'is_hash_1'; - const newIS = createISEvent(workerAddress, URL, HASH, BigInt.fromI32(6)); - handleIntermediateStorage(newIS); - - const id = toEventId(newIS).toHex(); + test('Should properly handle PendingV3 event with fee percentages from event', () => { + const URL = 'test-v3.com'; + const HASH = 'is_hash_v3_1'; - // StoreResultsEvent - assert.fieldEquals( - 'StoreResultsEvent', - id, - 'block', - newIS.block.number.toString() + const newPending1 = createPendingV3Event( + operatorAddress, + URL, + HASH, + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 7, + 8, + 9, + BigInt.fromI32(51) ); + + handlePendingV3(newPending1); + + const id = toEventId(newPending1).toHex(); + + assert.fieldEquals('EscrowStatusEvent', id, 'status', 'Pending'); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'status', 'Pending'); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'manifest', URL); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'manifestHash', HASH); assert.fieldEquals( - 'StoreResultsEvent', - id, - 'timestamp', - newIS.block.timestamp.toString() + 'Escrow', + escrowAddress.toHex(), + 'reputationOracleFee', + '7' ); assert.fieldEquals( - 'StoreResultsEvent', - id, - 'txHash', - newIS.transaction.hash.toHex() + 'Escrow', + escrowAddress.toHex(), + 'recordingOracleFee', + '8' ); assert.fieldEquals( - 'StoreResultsEvent', - id, - 'escrowAddress', - escrowAddressString + 'Escrow', + escrowAddress.toHex(), + 'exchangeOracleFee', + '9' ); - assert.fieldEquals('StoreResultsEvent', id, 'sender', workerAddressString); - assert.fieldEquals('StoreResultsEvent', id, 'intermediateResultsUrl', URL); assert.fieldEquals( - 'StoreResultsEvent', - id, - 'intermediateResultsHash', - HASH + 'Transaction', + newPending1.transaction.hash.toHex(), + 'method', + 'setup' ); + }); + + test('should properly handle IntermediateStorage event', () => { + const URL = 'test.com'; + const HASH = 'is_hash_1'; + const newIS = createISEvent(workerAddress, URL, HASH, BigInt.fromI32(6)); + handleIntermediateStorage(newIS); assert.fieldEquals( 'Transaction', @@ -948,33 +865,6 @@ describe('Escrow', () => { const id1 = toEventId(bulk1).toHex(); // BulkPayoutEvent; - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'block', - bulk1.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'timestamp', - bulk1.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'txHash', - bulk1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'sender', operatorAddressString); - assert.fieldEquals('BulkPayoutEvent', id1, 'payoutId', '1'); - assert.fieldEquals('BulkPayoutEvent', id1, 'bulkCount', '2'); // EscrowStatusEvent assert.fieldEquals( @@ -1032,34 +922,6 @@ describe('Escrow', () => { const id2 = toEventId(bulk2).toHex(); - assert.fieldEquals( - 'BulkPayoutEvent', - id2, - 'block', - bulk2.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id2, - 'timestamp', - bulk2.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id2, - 'txHash', - bulk2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id2, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id2, 'sender', operatorAddressString); - assert.fieldEquals('BulkPayoutEvent', id2, 'payoutId', '3'); - assert.fieldEquals('BulkPayoutEvent', id2, 'bulkCount', '4'); - // EscrowStatusEvent assert.fieldEquals( 'EscrowStatusEvent', @@ -1157,33 +1019,6 @@ describe('Escrow', () => { const id1 = toEventId(bulk1).toHex(); // BulkPayoutEvent - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'block', - bulk1.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'timestamp', - bulk1.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'txHash', - bulk1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'sender', operatorAddressString); - assert.fieldEquals('BulkPayoutEvent', id1, 'payoutId', '1'); - assert.fieldEquals('BulkPayoutEvent', id1, 'bulkCount', '2'); // EscrowStatusEvent assert.fieldEquals( @@ -1326,38 +1161,6 @@ describe('Escrow', () => { const id1 = toEventId(bulk1).toHex(); // BulkPayoutEvent - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'block', - bulk1.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'timestamp', - bulk1.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'txHash', - bulk1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'sender', operatorAddressString); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'payoutId', - Bytes.fromHexString('test-1').toHex() - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'bulkCount', '2'); // EscrowStatusEvent assert.fieldEquals( @@ -1736,38 +1539,6 @@ describe('Escrow', () => { handleWithdraw(withdraw); - const id = toEventId(withdraw).toHex(); - - // WithdrawEvent - assert.fieldEquals( - 'WithdrawEvent', - id, - 'block', - withdraw.block.number.toString() - ); - assert.fieldEquals( - 'WithdrawEvent', - id, - 'timestamp', - withdraw.block.timestamp.toString() - ); - assert.fieldEquals( - 'WithdrawEvent', - id, - 'txHash', - withdraw.transaction.hash.toHex() - ); - assert.fieldEquals( - 'WithdrawEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('WithdrawEvent', id, 'sender', operatorAddressString); - assert.fieldEquals('WithdrawEvent', id, 'receiver', launcherAddressString); - assert.fieldEquals('WithdrawEvent', id, 'amount', '100'); - assert.fieldEquals('WithdrawEvent', id, 'token', tokenAddressString); - assert.fieldEquals( 'Transaction', withdraw.transaction.hash.toHex(), @@ -1861,6 +1632,49 @@ describe('Escrow', () => { ); }); + test('Should properly calculate setup & pending in statistics for PendingV3', () => { + const newPending1 = createPendingV3Event( + operatorAddress, + 'test.com', + 'is_hash_1', + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 5, + 5, + 5, + BigInt.fromI32(21) + ); + const newPending2 = createPendingV3Event( + operatorAddress, + 'test.com', + 'is_hash_2', + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 10, + 10, + 10, + BigInt.fromI32(22) + ); + + handlePendingV3(newPending1); + handlePendingV3(newPending2); + + assert.fieldEquals( + 'EscrowStatistics', + STATISTICS_ENTITY_ID.toHex(), + 'pendingStatusEventCount', + '2' + ); + assert.fieldEquals( + 'EscrowStatistics', + STATISTICS_ENTITY_ID.toHex(), + 'totalEventCount', + '4' + ); + }); + test('Should properly calculate StoreResults event in statistics', () => { const newIS = createISEvent( workerAddress, diff --git a/packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts b/packages/subgraph/human-protocol/tests/escrow/fixtures.ts similarity index 81% rename from packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts rename to packages/subgraph/human-protocol/tests/escrow/fixtures.ts index 77d0b2020f..816f9b4412 100644 --- a/packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts +++ b/packages/subgraph/human-protocol/tests/escrow/fixtures.ts @@ -12,6 +12,7 @@ import { IntermediateStorage, Pending, PendingV2, + PendingV3, Withdraw, } from '../../generated/templates/Escrow/Escrow'; import { generateUniqueHash } from '../../tests/utils'; @@ -98,6 +99,74 @@ export function createPendingV2Event( return newPendingEvent; } +export function createPendingV3Event( + sender: Address, + manifest: string, + hash: string, + reputationOracleAddress: Address, + recordingOracleAddress: Address, + exchangeOracleAddress: Address, + reputationOracleFeePercentage: i32, + recordingOracleFeePercentage: i32, + exchangeOracleFeePercentage: i32, + timestamp: BigInt +): PendingV3 { + const newPendingEvent = changetype(newMockEvent()); + newPendingEvent.transaction.hash = generateUniqueHash( + sender.toString(), + timestamp, + newPendingEvent.transaction.nonce + ); + + newPendingEvent.transaction.from = sender; + + newPendingEvent.parameters = []; + + const manifestParam = new ethereum.EventParam( + 'manifest', + ethereum.Value.fromString(manifest) + ); + const hashParam = new ethereum.EventParam( + 'hash', + ethereum.Value.fromString(hash) + ); + const reputationOracleAddressParam = new ethereum.EventParam( + 'reputationOracleAddress', + ethereum.Value.fromAddress(reputationOracleAddress) + ); + const recordingOracleAddressParam = new ethereum.EventParam( + 'recordingOracleAddress', + ethereum.Value.fromAddress(recordingOracleAddress) + ); + const exchangeOracleAddressParam = new ethereum.EventParam( + 'exchangeOracleAddress', + ethereum.Value.fromAddress(exchangeOracleAddress) + ); + const reputationOracleFeePercentageParam = new ethereum.EventParam( + 'reputationOracleFeePercentage', + ethereum.Value.fromI32(reputationOracleFeePercentage) + ); + const recordingOracleFeePercentageParam = new ethereum.EventParam( + 'recordingOracleFeePercentage', + ethereum.Value.fromI32(recordingOracleFeePercentage) + ); + const exchangeOracleFeePercentageParam = new ethereum.EventParam( + 'exchangeOracleFeePercentage', + ethereum.Value.fromI32(exchangeOracleFeePercentage) + ); + + newPendingEvent.parameters.push(manifestParam); + newPendingEvent.parameters.push(hashParam); + newPendingEvent.parameters.push(reputationOracleAddressParam); + newPendingEvent.parameters.push(recordingOracleAddressParam); + newPendingEvent.parameters.push(exchangeOracleAddressParam); + newPendingEvent.parameters.push(reputationOracleFeePercentageParam); + newPendingEvent.parameters.push(recordingOracleFeePercentageParam); + newPendingEvent.parameters.push(exchangeOracleFeePercentageParam); + + return newPendingEvent; +} + export function createISEvent( sender: Address, url: string, @@ -324,7 +393,7 @@ export function createFundEvent( ): Fund { const newFundEvent = changetype(newMockEvent()); newFundEvent.transaction.hash = generateUniqueHash( - sender.toString(), + sender.toString() + '-fund', timestamp, newFundEvent.transaction.nonce ); @@ -386,7 +455,7 @@ export function createCancellationRequestedEvent( ): CancellationRequested { const event = changetype(newMockEvent()); event.transaction.hash = generateUniqueHash( - sender.toString(), + sender.toString() + '-cancellation-requested', timestamp, event.transaction.nonce ); diff --git a/packages/sdk/typescript/subgraph/tests/kvstore/fixtures.ts b/packages/subgraph/human-protocol/tests/kvstore/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/kvstore/fixtures.ts rename to packages/subgraph/human-protocol/tests/kvstore/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/kvstore/kvstore.test.ts b/packages/subgraph/human-protocol/tests/kvstore/kvstore.test.ts similarity index 93% rename from packages/sdk/typescript/subgraph/tests/kvstore/kvstore.test.ts rename to packages/subgraph/human-protocol/tests/kvstore/kvstore.test.ts index 5a2df8a98d..b94bb82ed8 100644 --- a/packages/sdk/typescript/subgraph/tests/kvstore/kvstore.test.ts +++ b/packages/subgraph/human-protocol/tests/kvstore/kvstore.test.ts @@ -46,65 +46,6 @@ describe('KVStore', () => { handleDataSaved(data1); handleDataSaved(data2); - - const id1 = toEventId(data1).toHex(); - const id2 = toEventId(data2).toHex(); - - // Data 1 - assert.fieldEquals( - 'KVStoreSetEvent', - id1, - 'block', - data1.block.number.toString() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id1, - 'timestamp', - data1.block.timestamp.toString() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id1, - 'txHash', - data1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id1, - 'operatorAddress', - data1.params.sender.toHex() - ); - assert.fieldEquals('KVStoreSetEvent', id1, 'key', 'role'); - assert.fieldEquals('KVStoreSetEvent', id1, 'value', 'Operator'); - - // Data 2 - assert.fieldEquals( - 'KVStoreSetEvent', - id2, - 'block', - data2.block.number.toString() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id2, - 'timestamp', - data2.block.timestamp.toString() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id2, - 'txHash', - data2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'KVStoreSetEvent', - id2, - 'operatorAddress', - data2.params.sender.toHex() - ); - assert.fieldEquals('KVStoreSetEvent', id2, 'key', 'role'); - assert.fieldEquals('KVStoreSetEvent', id2, 'value', 'job_launcher'); }); test('Should properly create a transaction with set method', () => { diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/escrow-factory.test.ts b/packages/subgraph/human-protocol/tests/legacy/escrow-factory/escrow-factory.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/escrow-factory.test.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow-factory/escrow-factory.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/fixtures.ts b/packages/subgraph/human-protocol/tests/legacy/escrow-factory/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/fixtures.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow-factory/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow/escrow.test.ts b/packages/subgraph/human-protocol/tests/legacy/escrow/escrow.test.ts similarity index 80% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow/escrow.test.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow/escrow.test.ts index e1f50864fa..59edf8b75e 100644 --- a/packages/sdk/typescript/subgraph/tests/legacy/escrow/escrow.test.ts +++ b/packages/subgraph/human-protocol/tests/legacy/escrow/escrow.test.ts @@ -23,7 +23,6 @@ import { handlePending, handleBulkTransfer, } from '../../../src/mapping/legacy/Escrow'; -import { toEventId } from '../../../src/mapping/utils/event'; import { ZERO_BI } from '../../../src/mapping/utils/number'; import { createISEvent, @@ -112,35 +111,6 @@ describe('Escrow', () => { handlePending(newPending1); - const id = toEventId(newPending1).toHex(); - - // PendingEvent - assert.fieldEquals( - 'PendingEvent', - id, - 'block', - newPending1.block.number.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'timestamp', - newPending1.block.timestamp.toString() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'txHash', - newPending1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'PendingEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); - // Escrow assert.fieldEquals('Escrow', escrowAddress.toHex(), 'status', 'Pending'); assert.fieldEquals('Escrow', escrowAddress.toHex(), 'manifest', URL); @@ -194,35 +164,6 @@ describe('Escrow', () => { const newIS = createISEvent(workerAddress, URL, 'is_hash_1'); handleIntermediateStorage(newIS); - const id = toEventId(newIS).toHex(); - - // StoreResultsEvent - assert.fieldEquals( - 'StoreResultsEvent', - id, - 'block', - newIS.block.number.toString() - ); - assert.fieldEquals( - 'StoreResultsEvent', - id, - 'timestamp', - newIS.block.timestamp.toString() - ); - assert.fieldEquals( - 'StoreResultsEvent', - id, - 'txHash', - newIS.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StoreResultsEvent', - id, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('StoreResultsEvent', id, 'sender', workerAddressString); - assert.fieldEquals('StoreResultsEvent', id, 'intermediateResultsUrl', URL); assert.fieldEquals( 'Transaction', newIS.transaction.hash.toHex(), @@ -270,37 +211,6 @@ describe('Escrow', () => { handleBulkTransfer(bulk1); - const id1 = toEventId(bulk1).toHex(); - - // BulkPayoutEvent - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'block', - bulk1.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'timestamp', - bulk1.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'txHash', - bulk1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'sender', operatorAddressString); - assert.fieldEquals('BulkPayoutEvent', id1, 'payoutId', '1'); - assert.fieldEquals('BulkPayoutEvent', id1, 'bulkCount', '2'); - // Escrow assert.fieldEquals( 'Escrow', @@ -355,37 +265,6 @@ describe('Escrow', () => { handleBulkTransfer(bulk1); - const id1 = toEventId(bulk1).toHex(); - - // BulkPayoutEvent - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'block', - bulk1.block.number.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'timestamp', - bulk1.block.timestamp.toString() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'txHash', - bulk1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'BulkPayoutEvent', - id1, - 'escrowAddress', - escrowAddressString - ); - assert.fieldEquals('BulkPayoutEvent', id1, 'sender', operatorAddressString); - assert.fieldEquals('BulkPayoutEvent', id1, 'payoutId', '1'); - assert.fieldEquals('BulkPayoutEvent', id1, 'bulkCount', '2'); - // Escrow assert.fieldEquals('Escrow', escrowAddress.toHex(), 'status', 'Paid'); assert.fieldEquals( diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow/fixtures.ts b/packages/subgraph/human-protocol/tests/legacy/escrow/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow/fixtures.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/staking/fixtures.ts b/packages/subgraph/human-protocol/tests/staking/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/staking/fixtures.ts rename to packages/subgraph/human-protocol/tests/staking/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/staking/staking.test.ts b/packages/subgraph/human-protocol/tests/staking/staking.test.ts similarity index 68% rename from packages/sdk/typescript/subgraph/tests/staking/staking.test.ts rename to packages/subgraph/human-protocol/tests/staking/staking.test.ts index a935e329b6..57af50d203 100644 --- a/packages/sdk/typescript/subgraph/tests/staking/staking.test.ts +++ b/packages/subgraph/human-protocol/tests/staking/staking.test.ts @@ -18,7 +18,6 @@ import { handleFeeWithdrawn, TOKEN_ADDRESS, } from '../../src/mapping/Staking'; -import { toEventId } from '../../src/mapping/utils/event'; import { ZERO_BI } from '../../src/mapping/utils/number'; import { createFeeWithdrawnEvent, @@ -90,63 +89,6 @@ describe('Staking', () => { handleStakeDeposited(data1); handleStakeDeposited(data2); - const id1 = toEventId(data1).toHex(); - const id2 = toEventId(data2).toHex(); - - // Data 1 - assert.fieldEquals( - 'StakeDepositedEvent', - id1, - 'block', - data1.block.number.toString() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id1, - 'timestamp', - data1.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id1, - 'txHash', - data1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id1, - 'staker', - data1.params.staker.toHex() - ); - assert.fieldEquals('StakeDepositedEvent', id1, 'amount', '100'); - - // Data 2 - assert.fieldEquals( - 'StakeDepositedEvent', - id2, - 'block', - data2.block.number.toString() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id2, - 'timestamp', - data2.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id2, - 'txHash', - data2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeDepositedEvent', - id2, - 'staker', - data2.params.staker.toHex() - ); - assert.fieldEquals('StakeDepositedEvent', id2, 'amount', '200'); - // Operator assert.fieldEquals( 'Staker', @@ -225,65 +167,6 @@ describe('Staking', () => { handleStakeLocked(data1); handleStakeLocked(data2); - const id1 = toEventId(data1).toHex(); - const id2 = toEventId(data2).toHex(); - - // Data 1 - assert.fieldEquals( - 'StakeLockedEvent', - id1, - 'block', - data1.block.number.toString() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id1, - 'timestamp', - data1.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id1, - 'txHash', - data1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id1, - 'staker', - data1.params.staker.toHex() - ); - assert.fieldEquals('StakeLockedEvent', id1, 'amount', '50'); - assert.fieldEquals('StakeLockedEvent', id1, 'lockedUntilTimestamp', '30'); - - // Data 2 - assert.fieldEquals( - 'StakeLockedEvent', - id2, - 'block', - data2.block.number.toString() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id2, - 'timestamp', - data2.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id2, - 'txHash', - data2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeLockedEvent', - id2, - 'staker', - data2.params.staker.toHex() - ); - assert.fieldEquals('StakeLockedEvent', id2, 'amount', '100'); - assert.fieldEquals('StakeLockedEvent', id2, 'lockedUntilTimestamp', '31'); - // Operator assert.fieldEquals( 'Staker', @@ -377,63 +260,6 @@ describe('Staking', () => { handleStakeWithdrawn(data1); handleStakeWithdrawn(data2); - const id1 = toEventId(data1).toHex(); - const id2 = toEventId(data2).toHex(); - - // Data 1 - assert.fieldEquals( - 'StakeWithdrawnEvent', - id1, - 'block', - data1.block.number.toString() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id1, - 'timestamp', - data1.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id1, - 'txHash', - data1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id1, - 'staker', - data1.params.staker.toHex() - ); - assert.fieldEquals('StakeWithdrawnEvent', id1, 'amount', '30'); - - // Data 2 - assert.fieldEquals( - 'StakeWithdrawnEvent', - id2, - 'block', - data2.block.number.toString() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id2, - 'timestamp', - data2.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id2, - 'txHash', - data2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeWithdrawnEvent', - id2, - 'staker', - data2.params.staker.toHex() - ); - assert.fieldEquals('StakeWithdrawnEvent', id2, 'amount', '100'); - // Operator assert.fieldEquals( 'Staker', @@ -543,87 +369,6 @@ describe('Staking', () => { handleStakeSlashed(data1); handleStakeSlashed(data2); - const id1 = toEventId(data1).toHex(); - const id2 = toEventId(data2).toHex(); - - // Data 1 - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'block', - data1.block.number.toString() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'timestamp', - data1.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'txHash', - data1.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'staker', - data1.params.staker.toHex() - ); - assert.fieldEquals('StakeSlashedEvent', id1, 'amount', '10'); - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'escrowAddress', - data1.params.escrowAddress.toHex() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id1, - 'slashRequester', - data1.params.slashRequester.toHex() - ); - - // Data 2 - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'block', - data2.block.number.toString() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'timestamp', - data2.block.timestamp.toString() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'txHash', - data2.transaction.hash.toHex() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'staker', - data2.params.staker.toHex() - ); - assert.fieldEquals('StakeSlashedEvent', id2, 'amount', '10'); - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'escrowAddress', - data2.params.escrowAddress.toHex() - ); - assert.fieldEquals( - 'StakeSlashedEvent', - id2, - 'slashRequester', - data2.params.slashRequester.toHex() - ); - // Operator assert.fieldEquals( 'Staker', diff --git a/packages/sdk/typescript/subgraph/tests/utils.ts b/packages/subgraph/human-protocol/tests/utils.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/utils.ts rename to packages/subgraph/human-protocol/tests/utils.ts diff --git a/packages/subgraph/human-protocol/tsconfig.json b/packages/subgraph/human-protocol/tsconfig.json new file mode 100644 index 0000000000..3c11a89fec --- /dev/null +++ b/packages/subgraph/human-protocol/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "types": ["@graphprotocol/graph-ts", "node"] + }, + "include": ["src", "tests"] +} diff --git a/yarn.lock b/yarn.lock index 0db0c9afc7..f305864d96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -105,11 +105,11 @@ __metadata: dependencies: "@emotion/react": "npm:^11.11.4" "@emotion/styled": "npm:^11.11.5" - "@eslint/js": "npm:^9.27.0" + "@eslint/js": "npm:^10.0.1" "@human-protocol/sdk": "workspace:*" - "@mui/icons-material": "npm:^7.2.0" + "@mui/icons-material": "npm:^7.3.8" "@mui/material": "npm:^7.2.0" - "@mui/styled-engine-sc": "npm:7.2.0" + "@mui/styled-engine-sc": "npm:7.3.8" "@mui/system": "npm:^7.2.0" "@mui/x-data-grid": "npm:^8.7.0" "@mui/x-date-pickers": "npm:^8.26.0" @@ -131,15 +131,15 @@ __metadata: eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-react-refresh: "npm:^0.4.11" globals: "npm:^16.2.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-number-format: "npm:^5.4.3" - react-router-dom: "npm:^6.23.1" + react-router-dom: "npm:^7.13.0" recharts: "npm:^2.13.0-alpha.4" sass: "npm:^1.89.2" simplebar-react: "npm:^3.3.2" - styled-components: "npm:^6.1.11" + styled-components: "npm:^6.3.11" swiper: "npm:^11.1.3" terser: "npm:^5.36.0" typescript: "npm:^5.6.3" @@ -169,10 +169,10 @@ __metadata: "@nestjs/core": "npm:^11.1.12" "@nestjs/mapped-types": "npm:^2.1.0" "@nestjs/platform-express": "npm:^11.1.12" - "@nestjs/schedule": "npm:^6.1.0" + "@nestjs/schedule": "npm:^6.1.1" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@types/express": "npm:^5.0.6" "@types/jest": "npm:30.0.0" "@types/node": "npm:22.10.5" @@ -193,7 +193,7 @@ __metadata: keyv: "npm:^5.5.5" lodash: "npm:^4.17.21" minio: "npm:8.0.6" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" source-map-support: "npm:^0.5.20" @@ -211,7 +211,7 @@ __metadata: "@emotion/react": "npm:^11.14.0" "@emotion/styled": "npm:^11.14.1" "@human-protocol/sdk": "workspace:*" - "@mui/icons-material": "npm:^7.0.1" + "@mui/icons-material": "npm:^7.3.8" "@mui/material": "npm:^5.16.7" "@types/react": "npm:^18.3.12" "@types/react-dom": "npm:^18.3.1" @@ -223,11 +223,11 @@ __metadata: eslint-plugin-import: "npm:^2.29.0" eslint-plugin-react: "npm:^7.34.3" eslint-plugin-react-hooks: "npm:^5.1.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-loading-skeleton: "npm:^3.3.1" - react-router-dom: "npm:^6.4.3" + react-router-dom: "npm:^7.13.0" serve: "npm:^14.2.4" typescript: "npm:^5.8.3" viem: "npm:2.x" @@ -269,7 +269,7 @@ __metadata: "@emotion/react": "npm:^11.11.3" "@emotion/styled": "npm:^11.11.0" "@human-protocol/sdk": "workspace:^" - "@mui/icons-material": "npm:^7.0.1" + "@mui/icons-material": "npm:^7.3.8" "@mui/material": "npm:^5.16.7" "@tanstack/query-sync-storage-persister": "npm:^5.68.0" "@tanstack/react-query": "npm:^5.67.2" @@ -285,10 +285,10 @@ __metadata: eslint-plugin-react-hooks: "npm:^5.1.0" eslint-plugin-react-refresh: "npm:^0.4.11" ethers: "npm:^6.15.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" - react-router-dom: "npm:^6.24.1" + react-router-dom: "npm:^7.13.0" serve: "npm:^14.2.4" typescript: "npm:^5.6.3" viem: "npm:2.x" @@ -312,11 +312,11 @@ __metadata: "@nestjs/core": "npm:^11.1.12" "@nestjs/passport": "npm:^11.0.5" "@nestjs/platform-express": "npm:^11.1.12" - "@nestjs/schedule": "npm:^6.1.0" + "@nestjs/schedule": "npm:^6.1.1" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" "@nestjs/terminus": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@nestjs/typeorm": "npm:^11.0.0" "@types/body-parser": "npm:^1" "@types/express": "npm:^5.0.6" @@ -345,7 +345,7 @@ __metadata: passport: "npm:^0.7.0" passport-jwt: "npm:^4.0.1" pg: "npm:8.13.1" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" source-map-support: "npm:^0.5.20" @@ -372,7 +372,7 @@ __metadata: "@nestjs/platform-express": "npm:^11.1.12" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@types/express": "npm:^5.0.6" "@types/jest": "npm:^29.5.14" "@types/node": "npm:^22.15.16" @@ -388,7 +388,7 @@ __metadata: jest: "npm:^29.7.0" joi: "npm:^17.13.3" minio: "npm:8.0.6" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" ts-jest: "npm:29.2.5" @@ -426,7 +426,7 @@ __metadata: "@hcaptcha/react-hcaptcha": "npm:^1.14.0" "@hookform/resolvers": "npm:^5.1.0" "@human-protocol/sdk": "workspace:*" - "@mui/icons-material": "npm:^7.0.1" + "@mui/icons-material": "npm:^7.3.8" "@mui/material": "npm:^5.16.7" "@mui/system": "npm:^5.15.14" "@mui/x-date-pickers": "npm:^8.26.0" @@ -446,7 +446,7 @@ __metadata: "@typescript-eslint/parser": "npm:^8.46.3" "@vercel/style-guide": "npm:^6.0.0" "@vitejs/plugin-react": "npm:^4.2.1" - "@wagmi/core": "npm:^2.17.1" + "@wagmi/core": "npm:^3.4.0" date-fns: "npm:^4.1.0" eslint: "npm:^9.39.1" eslint-config-prettier: "npm:^9.1.0" @@ -461,7 +461,7 @@ __metadata: material-react-table: "npm:3.0.1" mui-image: "npm:^1.0.7" notistack: "npm:^3.0.1" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" prop-types: "npm:^15.8.1" query-string: "npm:^9.0.0" react: "npm:^18.3.1" @@ -470,13 +470,13 @@ __metadata: react-i18next: "npm:^15.1.0" react-imask: "npm:^7.4.0" react-number-format: "npm:^5.4.3" - react-router-dom: "npm:^6.22.0" + react-router-dom: "npm:^7.13.0" serve: "npm:^14.2.4" typescript: "npm:^5.6.3" viem: "npm:^2.31.4" vite: "npm:^6.2.4" vite-plugin-svgr: "npm:^4.2.0" - vitest: "npm:^3.1.1" + vitest: "npm:^4.0.18" wagmi: "npm:^2.15.6" zod: "npm:^4.0.17" zustand: "npm:^5.0.10" @@ -502,11 +502,11 @@ __metadata: "@nestjs/core": "npm:^11.1.12" "@nestjs/passport": "npm:^11.0.5" "@nestjs/platform-express": "npm:^11.1.12" - "@nestjs/schedule": "npm:^6.1.0" + "@nestjs/schedule": "npm:^6.1.1" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" "@nestjs/terminus": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@types/express": "npm:^5.0.6" "@types/jest": "npm:30.0.0" "@types/jsonwebtoken": "npm:^9.0.7" @@ -531,10 +531,10 @@ __metadata: jwt-decode: "npm:^4.0.0" keyv: "npm:^5.5.5" lodash: "npm:^4.17.21" - nock: "npm:^13.5.1" + nock: "npm:^14.0.11" passport: "npm:^0.7.0" passport-jwt: "npm:^4.0.1" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" source-map-support: "npm:^0.5.20" @@ -553,7 +553,7 @@ __metadata: "@emotion/styled": "npm:^11.10.5" "@hcaptcha/react-hcaptcha": "npm:^1.14.0" "@human-protocol/sdk": "workspace:*" - "@mui/icons-material": "npm:^7.0.1" + "@mui/icons-material": "npm:^7.3.8" "@mui/lab": "npm:^6.0.0-dev.240424162023-9968b4889d" "@mui/material": "npm:^5.16.7" "@mui/system": "npm:^5.15.14" @@ -583,11 +583,11 @@ __metadata: file-saver: "npm:^2.0.5" formik: "npm:^2.4.2" jwt-decode: "npm:^4.0.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" react-redux: "npm:^9.1.0" - react-router-dom: "npm:^6.14.1" + react-router-dom: "npm:^7.13.0" recharts: "npm:^2.7.2" resize-observer-polyfill: "npm:^1.5.1" serve: "npm:^14.2.4" @@ -608,7 +608,7 @@ __metadata: dependencies: "@faker-js/faker": "npm:^9.8.0" "@golevelup/ts-jest": "npm:^0.6.1" - "@google-cloud/storage": "npm:^7.15.0" + "@google-cloud/storage": "npm:^7.19.0" "@google-cloud/vision": "npm:^4.3.2" "@human-protocol/logger": "workspace:*" "@human-protocol/sdk": "workspace:*" @@ -620,15 +620,15 @@ __metadata: "@nestjs/jwt": "npm:^11.0.2" "@nestjs/passport": "npm:^11.0.5" "@nestjs/platform-express": "npm:^11.1.12" - "@nestjs/schedule": "npm:^6.1.0" + "@nestjs/schedule": "npm:^6.1.1" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" "@nestjs/terminus": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@nestjs/throttler": "npm:^6.5.0" "@nestjs/typeorm": "npm:^11.0.0" "@sendgrid/mail": "npm:^8.1.3" - "@types/bcrypt": "npm:^5.0.2" + "@types/bcrypt": "npm:^6.0.0" "@types/express": "npm:^5.0.6" "@types/jest": "npm:30.0.0" "@types/node": "npm:22.10.5" @@ -640,7 +640,7 @@ __metadata: "@typescript-eslint/parser": "npm:^8.46.3" async-mutex: "npm:^0.5.0" axios: "npm:^1.7.2" - bcrypt: "npm:^5.1.1" + bcrypt: "npm:^6.0.0" body-parser: "npm:^1.20.3" class-transformer: "npm:^0.5.1" class-validator: "npm:0.14.1" @@ -660,7 +660,7 @@ __metadata: passport: "npm:^0.7.0" passport-jwt: "npm:^4.0.1" pg: "npm:8.13.1" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" source-map-support: "npm:^0.5.20" @@ -680,7 +680,7 @@ __metadata: version: 0.0.0-use.local resolution: "@apps/reputation-oracle@workspace:packages/apps/reputation-oracle/server" dependencies: - "@eslint/js": "npm:^9.33.0" + "@eslint/js": "npm:^10.0.1" "@faker-js/faker": "npm:^9.8.0" "@golevelup/ts-jest": "npm:^0.6.1" "@human-protocol/core": "workspace:*" @@ -694,17 +694,17 @@ __metadata: "@nestjs/jwt": "npm:^11.0.2" "@nestjs/passport": "npm:^11.0.5" "@nestjs/platform-express": "npm:^11.1.12" - "@nestjs/schedule": "npm:^6.1.0" + "@nestjs/schedule": "npm:^6.1.1" "@nestjs/schematics": "npm:^11.0.9" "@nestjs/swagger": "npm:^11.2.5" "@nestjs/terminus": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.12" + "@nestjs/testing": "npm:^11.1.14" "@nestjs/typeorm": "npm:^11.0.0" "@sendgrid/mail": "npm:^8.1.3" "@slack/bolt": "npm:^4.2.1" "@slack/web-api": "npm:^7.9.1" "@slack/webhook": "npm:^7.0.5" - "@types/bcrypt": "npm:^5.0.2" + "@types/bcrypt": "npm:^6.0.0" "@types/express": "npm:^5.0.6" "@types/jest": "npm:30.0.0" "@types/lodash": "npm:^4.17.14" @@ -713,7 +713,7 @@ __metadata: "@types/uuid": "npm:^10.0.0" "@types/zxcvbn": "npm:4.4.5" axios: "npm:^1.8.1" - bcrypt: "npm:^5.1.1" + bcrypt: "npm:^6.0.0" body-parser: "npm:^1.20.3" class-transformer: "npm:^0.5.1" class-validator: "npm:^0.14.1" @@ -731,11 +731,11 @@ __metadata: json-stable-stringify: "npm:^1.2.1" lodash: "npm:^4.17.21" minio: "npm:8.0.6" - nock: "npm:^14.0.3" + nock: "npm:^14.0.11" passport: "npm:^0.7.0" passport-jwt: "npm:^4.0.1" pg: "npm:8.13.1" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" ts-jest: "npm:29.2.5" @@ -759,7 +759,7 @@ __metadata: "@emotion/react": "npm:^11.14.0" "@emotion/styled": "npm:^11.14.1" "@human-protocol/sdk": "npm:*" - "@mui/icons-material": "npm:^7.0.1" + "@mui/icons-material": "npm:^7.3.8" "@mui/material": "npm:^5.16.7" "@mui/x-data-grid": "npm:^8.7.0" "@tanstack/query-sync-storage-persister": "npm:^5.68.0" @@ -776,10 +776,10 @@ __metadata: eslint-plugin-react-hooks: "npm:^5.1.0" eslint-plugin-react-refresh: "npm:^0.4.11" ethers: "npm:^6.15.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" - react-router-dom: "npm:^6.24.1" + react-router-dom: "npm:^7.13.0" sass: "npm:^1.89.2" serve: "npm:^14.2.4" simplebar-react: "npm:^3.3.2" @@ -2893,7 +2893,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.24.4": +"@babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.28.6": version: 7.28.6 resolution: "@babel/runtime@npm:7.28.6" checksum: 10c0/358cf2429992ac1c466df1a21c1601d595c46930a13c1d4662fde908d44ee78ec3c183aaff513ecb01ef8c55c3624afe0309eeeb34715672dbfadb7feedb2c0d @@ -3408,16 +3408,7 @@ __metadata: languageName: node linkType: hard -"@emotion/is-prop-valid@npm:1.2.2": - version: 1.2.2 - resolution: "@emotion/is-prop-valid@npm:1.2.2" - dependencies: - "@emotion/memoize": "npm:^0.8.1" - checksum: 10c0/bb1530dcb4e0e5a4fabb219279f2d0bc35796baf66f6241f98b0d03db1985c890a8cafbea268e0edefd5eeda143dbd5c09a54b5fba74cee8c69b98b13194af50 - languageName: node - linkType: hard - -"@emotion/is-prop-valid@npm:^1.3.0": +"@emotion/is-prop-valid@npm:1.4.0, @emotion/is-prop-valid@npm:^1.3.0": version: 1.4.0 resolution: "@emotion/is-prop-valid@npm:1.4.0" dependencies: @@ -3426,13 +3417,6 @@ __metadata: languageName: node linkType: hard -"@emotion/memoize@npm:^0.8.1": - version: 0.8.1 - resolution: "@emotion/memoize@npm:0.8.1" - checksum: 10c0/dffed372fc3b9fa2ba411e76af22b6bb686fb0cb07694fdfaa6dd2baeb0d5e4968c1a7caa472bfcf06a5997d5e7c7d16b90e993f9a6ffae79a2c3dbdc76dfe78 - languageName: node - linkType: hard - "@emotion/memoize@npm:^0.9.0": version: 0.9.0 resolution: "@emotion/memoize@npm:0.9.0" @@ -3501,14 +3485,7 @@ __metadata: languageName: node linkType: hard -"@emotion/unitless@npm:0.8.1": - version: 0.8.1 - resolution: "@emotion/unitless@npm:0.8.1" - checksum: 10c0/a1ed508628288f40bfe6dd17d431ed899c067a899fa293a13afe3aed1d70fac0412b8a215fafab0b42829360db687fecd763e5f01a64ddc4a4b58ec3112ff548 - languageName: node - linkType: hard - -"@emotion/unitless@npm:^0.10.0": +"@emotion/unitless@npm:0.10.0, @emotion/unitless@npm:^0.10.0": version: 0.10.0 resolution: "@emotion/unitless@npm:0.10.0" checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 @@ -3545,6 +3522,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-arm64@npm:0.25.12" @@ -3552,6 +3536,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-arm@npm:0.25.12" @@ -3559,6 +3550,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-x64@npm:0.25.12" @@ -3566,6 +3564,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/darwin-arm64@npm:0.25.12" @@ -3573,6 +3578,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/darwin-x64@npm:0.25.12" @@ -3580,6 +3592,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/freebsd-arm64@npm:0.25.12" @@ -3587,6 +3606,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/freebsd-x64@npm:0.25.12" @@ -3594,6 +3620,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-arm64@npm:0.25.12" @@ -3601,6 +3634,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-arm@npm:0.25.12" @@ -3608,6 +3648,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-ia32@npm:0.25.12" @@ -3615,6 +3662,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-loong64@npm:0.25.12" @@ -3622,6 +3676,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-mips64el@npm:0.25.12" @@ -3629,6 +3690,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-ppc64@npm:0.25.12" @@ -3636,6 +3704,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-riscv64@npm:0.25.12" @@ -3643,6 +3718,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-s390x@npm:0.25.12" @@ -3650,6 +3732,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-x64@npm:0.25.12" @@ -3657,6 +3746,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/netbsd-arm64@npm:0.25.12" @@ -3664,6 +3760,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/netbsd-x64@npm:0.25.12" @@ -3671,6 +3774,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openbsd-arm64@npm:0.25.12" @@ -3678,6 +3788,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openbsd-x64@npm:0.25.12" @@ -3685,6 +3802,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openharmony-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openharmony-arm64@npm:0.25.12" @@ -3692,6 +3816,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/sunos-x64@npm:0.25.12" @@ -3699,6 +3830,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-arm64@npm:0.25.12" @@ -3706,6 +3844,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-ia32@npm:0.25.12" @@ -3713,6 +3858,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-x64@npm:0.25.12" @@ -3720,6 +3872,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -3808,10 +3967,15 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:^9.27.0, @eslint/js@npm:^9.30.1, @eslint/js@npm:^9.33.0": - version: 9.39.0 - resolution: "@eslint/js@npm:9.39.0" - checksum: 10c0/f0ac65784932f1a5d3b9c0db12eb1ff9dcb480dbd03da1045e5da820bd97a35875fb7790f1fbe652763270b1327b770c79a9ba0396e2ad91fbd97822493e67eb +"@eslint/js@npm:^10.0.1": + version: 10.0.1 + resolution: "@eslint/js@npm:10.0.1" + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/9f3fcaf71ba7fdf65d82e8faad6ecfe97e11801cc3c362b306a88ea1ed1344ae0d35330dddb0e8ad18f010f6687a70b75491b9e01c8af57acd7987cee6b3ec6c languageName: node linkType: hard @@ -4465,9 +4629,9 @@ __metadata: languageName: node linkType: hard -"@google-cloud/storage@npm:^7.15.0": - version: 7.17.2 - resolution: "@google-cloud/storage@npm:7.17.2" +"@google-cloud/storage@npm:^7.19.0": + version: 7.19.0 + resolution: "@google-cloud/storage@npm:7.19.0" dependencies: "@google-cloud/paginator": "npm:^5.0.0" "@google-cloud/projectify": "npm:^4.0.0" @@ -4475,7 +4639,7 @@ __metadata: abort-controller: "npm:^3.0.0" async-retry: "npm:^1.3.3" duplexify: "npm:^4.1.3" - fast-xml-parser: "npm:^4.4.1" + fast-xml-parser: "npm:^5.3.4" gaxios: "npm:^6.0.2" google-auth-library: "npm:^9.6.3" html-entities: "npm:^2.5.2" @@ -4484,7 +4648,7 @@ __metadata: retry-request: "npm:^7.0.0" teeny-request: "npm:^9.0.0" uuid: "npm:^8.0.0" - checksum: 10c0/0f178d5936818e6e1d90cfdad87a5ca955cbef5f28a206b3722f3c5b5b8b63b89278e7590f7468a0d7057c64d36883fa5372c91cb254d124a32a16127104645e + checksum: 10c0/2951e4a0b3c2f90c28917a9b313a981722a9e5648ca2b6d04f384f816e9107e1637b00c32c5e71ed8d25c7e1840898b616f015b29c2cc1d8d8a22c8630778d8c languageName: node linkType: hard @@ -4957,13 +5121,14 @@ __metadata: hardhat-dependency-compiler: "npm:^1.2.1" hardhat-gas-reporter: "npm:^2.0.2" openpgp: "npm:6.2.2" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" prettier-plugin-solidity: "npm:^1.3.1" solidity-coverage: "npm:^0.8.17" tenderly: "npm:^0.9.1" ts-node: "npm:^10.9.2" typechain: "npm:^8.3.2" typescript: "npm:^5.8.3" + typescript-eslint: "npm:^8.39.1" xdeployer: "npm:3.1.6" peerDependencies: ethers: ~6.15.0 @@ -4974,7 +5139,7 @@ __metadata: version: 0.0.0-use.local resolution: "@human-protocol/logger@workspace:packages/libs/logger" dependencies: - "@eslint/js": "npm:^9.30.1" + "@eslint/js": "npm:^10.0.1" "@types/node": "npm:^22.10.5" eslint: "npm:^9.39.1" eslint-config-prettier: "npm:^10.1.5" @@ -4984,7 +5149,7 @@ __metadata: globals: "npm:^16.3.0" pino: "npm:^10.1.0" pino-pretty: "npm:^13.1.3" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" ts-node: "npm:^10.9.2" typescript: "npm:^5.8.3" typescript-eslint: "npm:^8.35.1" @@ -5036,14 +5201,14 @@ __metadata: graphql-request: "npm:^7.3.4" graphql-tag: "npm:^2.12.6" openpgp: "npm:^6.2.2" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" secp256k1: "npm:^5.0.1" ts-node: "npm:^10.9.2" typedoc: "npm:^0.28.15" typedoc-plugin-markdown: "npm:^4.9.0" typescript: "npm:^5.8.3" validator: "npm:^13.12.0" - vitest: "npm:^3.0.9" + vitest: "npm:^4.0.18" languageName: unknown linkType: soft @@ -6196,25 +6361,6 @@ __metadata: languageName: node linkType: hard -"@mapbox/node-pre-gyp@npm:^1.0.11": - version: 1.0.11 - resolution: "@mapbox/node-pre-gyp@npm:1.0.11" - dependencies: - detect-libc: "npm:^2.0.0" - https-proxy-agent: "npm:^5.0.0" - make-dir: "npm:^3.1.0" - node-fetch: "npm:^2.6.7" - nopt: "npm:^5.0.0" - npmlog: "npm:^5.0.1" - rimraf: "npm:^3.0.2" - semver: "npm:^7.3.5" - tar: "npm:^6.1.11" - bin: - node-pre-gyp: bin/node-pre-gyp - checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc - languageName: node - linkType: hard - "@metamask/eth-json-rpc-provider@npm:^1.0.0": version: 1.0.1 resolution: "@metamask/eth-json-rpc-provider@npm:1.0.1" @@ -6505,9 +6651,9 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.39.5": - version: 0.39.8 - resolution: "@mswjs/interceptors@npm:0.39.8" +"@mswjs/interceptors@npm:^0.41.0": + version: 0.41.3 + resolution: "@mswjs/interceptors@npm:0.41.3" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -6515,7 +6661,7 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10c0/0d07625ff1bbbf4b5ea702e164914490e26cc3754323f544429f843f5ad8b157194f6a650259cfd1bbdeb6003e1354f63be1d9bf483816aaa17d93ef4b321aaf + checksum: 10c0/a259bbfc3bb4caada7a9a3529cc830159818e838c152df89ac890e7203df615a5e070ca63aa1e70a39868322ff5c1441ab74bbadb4081ca55b0c3a462e2903c0 languageName: node linkType: hard @@ -6555,19 +6701,19 @@ __metadata: languageName: node linkType: hard -"@mui/icons-material@npm:^7.0.1, @mui/icons-material@npm:^7.2.0": - version: 7.3.4 - resolution: "@mui/icons-material@npm:7.3.4" +"@mui/icons-material@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/icons-material@npm:7.3.8" dependencies: - "@babel/runtime": "npm:^7.28.4" + "@babel/runtime": "npm:^7.28.6" peerDependencies: - "@mui/material": ^7.3.4 + "@mui/material": ^7.3.8 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/09c5708f0a96979dafeefdfbaef4950463e987bdc283874831d67ae0ce32cbc946bf408ba5084bd7a8f57af0cb87c3fdfddcf4c21e0946bb5e17c34abfd49d80 + checksum: 10c0/3c972ef066ddd0fbfc9ed3c26afa7ad769126ba19add9e89d50007671865238581d23d3c45fec901527642e086f8887404de38444d9b0bf1524d8fc5ffce2f6e languageName: node linkType: hard @@ -6720,18 +6866,18 @@ __metadata: languageName: node linkType: hard -"@mui/styled-engine-sc@npm:7.2.0": - version: 7.2.0 - resolution: "@mui/styled-engine-sc@npm:7.2.0" +"@mui/styled-engine-sc@npm:7.3.8": + version: 7.3.8 + resolution: "@mui/styled-engine-sc@npm:7.3.8" dependencies: - "@babel/runtime": "npm:^7.27.6" - "@types/hoist-non-react-statics": "npm:^3.3.6" - csstype: "npm:^3.1.3" + "@babel/runtime": "npm:^7.28.6" + "@types/hoist-non-react-statics": "npm:^3.3.7" + csstype: "npm:^3.2.3" hoist-non-react-statics: "npm:^3.3.2" prop-types: "npm:^15.8.1" peerDependencies: styled-components: ^6.0.0 - checksum: 10c0/3baa8e92160215a11d9e86b1cc9f1a898dc24179bfd106bc0976c3893682793f9d5a0db3d4903c4690c3c564cba0c3ee78b4e67d26bd34a817b66916799ca150 + checksum: 10c0/6a9473a0e325eef2a00f3ed6fa54cdc28962e742aefa8f529d87790e407aa9b75beb7acaf06f249180d359c76d65590637d361b1cd6bd3c5ece5997f35592239 languageName: node linkType: hard @@ -7365,15 +7511,15 @@ __metadata: languageName: node linkType: hard -"@nestjs/schedule@npm:^6.1.0": - version: 6.1.0 - resolution: "@nestjs/schedule@npm:6.1.0" +"@nestjs/schedule@npm:^6.1.1": + version: 6.1.1 + resolution: "@nestjs/schedule@npm:6.1.1" dependencies: - cron: "npm:4.3.5" + cron: "npm:4.4.0" peerDependencies: "@nestjs/common": ^10.0.0 || ^11.0.0 "@nestjs/core": ^10.0.0 || ^11.0.0 - checksum: 10c0/05465d7e42caad248e7550c3ff9c4cc5d8a29f87d30738bc4df593344576f83e99cac4b3f0694f4db3062ae22827b3cb252bf7c8370a824ead38a3c04401171c + checksum: 10c0/0185efe4824bd353fea792954ea04d2d4cb99836c93c6d42d76fe9d57a6fd97eb7ba9d874be6f7f94b6f8bbca11badab16993f30e9b1f94b4c341639407578de languageName: node linkType: hard @@ -7475,9 +7621,9 @@ __metadata: languageName: node linkType: hard -"@nestjs/testing@npm:^11.1.12": - version: 11.1.12 - resolution: "@nestjs/testing@npm:11.1.12" +"@nestjs/testing@npm:^11.1.14": + version: 11.1.14 + resolution: "@nestjs/testing@npm:11.1.14" dependencies: tslib: "npm:2.8.1" peerDependencies: @@ -7490,7 +7636,7 @@ __metadata: optional: true "@nestjs/platform-express": optional: true - checksum: 10c0/7f65cc8d0fb7a654ecadf1d8f5a4c0d2112f43cc2a2b59454dcc34b6a25b6280af3ee5a189439e354212d5ba8afd29df67e099bc8387389eb2f93da5c783ad8a + checksum: 10c0/bde7b90fead08ca3a5a7f44331e5c4ccf2904bcba5b95bbfa4fe22fc278f3e22beadbe4c4408ec4ea2fcd6dea267faeef502ccc0d3276bd838f3723f0eba7148 languageName: node linkType: hard @@ -8626,13 +8772,6 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.23.0": - version: 1.23.0 - resolution: "@remix-run/router@npm:1.23.0" - checksum: 10c0/eaef5cb46a1e413f7d1019a75990808307e08e53a39d4cf69c339432ddc03143d725decef3d6b9b5071b898da07f72a4a57c4e73f787005fcf10162973d8d7d7 - languageName: node - linkType: hard - "@reown/appkit-adapter-wagmi@npm:^1.7.11": version: 1.8.12 resolution: "@reown/appkit-adapter-wagmi@npm:1.8.12" @@ -11164,9 +11303,26 @@ __metadata: languageName: node linkType: hard -"@tools/subgraph@workspace:packages/sdk/typescript/subgraph": +"@tools/subgraph-hmt@workspace:packages/subgraph/hmt": + version: 0.0.0-use.local + resolution: "@tools/subgraph-hmt@workspace:packages/subgraph/hmt" + dependencies: + "@graphprotocol/graph-cli": "npm:^0.97.1" + "@graphprotocol/graph-ts": "npm:^0.38.0" + "@graphql-eslint/eslint-plugin": "npm:^3.19.1" + "@human-protocol/core": "workspace:*" + eslint: "npm:^9.39.1" + ethers: "npm:~6.15.0" + graphql: "npm:^16.6.0" + matchstick-as: "npm:^0.6.0" + mustache: "npm:^4.2.0" + prettier: "npm:^3.8.1" + languageName: unknown + linkType: soft + +"@tools/subgraph-human-protocol@workspace:packages/subgraph/human-protocol": version: 0.0.0-use.local - resolution: "@tools/subgraph@workspace:packages/sdk/typescript/subgraph" + resolution: "@tools/subgraph-human-protocol@workspace:packages/subgraph/human-protocol" dependencies: "@graphprotocol/graph-cli": "npm:^0.97.1" "@graphprotocol/graph-ts": "npm:^0.38.0" @@ -11177,7 +11333,7 @@ __metadata: graphql: "npm:^16.6.0" matchstick-as: "npm:^0.6.0" mustache: "npm:^4.2.0" - prettier: "npm:^3.7.4" + prettier: "npm:^3.8.1" languageName: unknown linkType: soft @@ -11294,12 +11450,12 @@ __metadata: languageName: node linkType: hard -"@types/bcrypt@npm:^5.0.2": - version: 5.0.2 - resolution: "@types/bcrypt@npm:5.0.2" +"@types/bcrypt@npm:^6.0.0": + version: 6.0.0 + resolution: "@types/bcrypt@npm:6.0.0" dependencies: "@types/node": "npm:*" - checksum: 10c0/dd7f05e183b9b1fc08ec499069febf197ab8e9c720766b5bbb5628395082e248f9a444c60882fe7788361fcadc302e21e055ab9c26a300f100e08791c353e6aa + checksum: 10c0/48b66408838f5eb59791da5a77260cef59a5291ce0945f2caa3b5a6158c44b321b684b4aa7dce771852c63271c766cadd587374273451b300377a17015c1d51d languageName: node linkType: hard @@ -11577,7 +11733,7 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.1, @types/hoist-non-react-statics@npm:^3.3.6": +"@types/hoist-non-react-statics@npm:^3.3.1, @types/hoist-non-react-statics@npm:^3.3.7": version: 3.3.7 resolution: "@types/hoist-non-react-statics@npm:3.3.7" dependencies: @@ -12006,10 +12162,10 @@ __metadata: languageName: node linkType: hard -"@types/stylis@npm:4.2.5": - version: 4.2.5 - resolution: "@types/stylis@npm:4.2.5" - checksum: 10c0/23f5b35a3a04f6bb31a29d404fa1bc8e0035fcaff2356b4047743a057e0c37b2eba7efe14d57dd2b95b398cea3bac294d9c6cd93ed307d8c0b7f5d282224b469 +"@types/stylis@npm:4.2.7": + version: 4.2.7 + resolution: "@types/stylis@npm:4.2.7" + checksum: 10c0/01a9679addb3f63951a9c09729564e2205581f2db40875a28b25cc461efc52ba17a711cc50cdb5e7d3a67c5f2cd60580e078c8a69b8df7b67699d89060d2a977 languageName: node linkType: hard @@ -12997,6 +13153,20 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/expect@npm:4.0.18" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + chai: "npm:^6.2.1" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc + languageName: node + linkType: hard + "@vitest/mocker@npm:3.2.4": version: 3.2.4 resolution: "@vitest/mocker@npm:3.2.4" @@ -13016,6 +13186,25 @@ __metadata: languageName: node linkType: hard +"@vitest/mocker@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/mocker@npm:4.0.18" + dependencies: + "@vitest/spy": "npm:4.0.18" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b + languageName: node + linkType: hard + "@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": version: 3.2.4 resolution: "@vitest/pretty-format@npm:3.2.4" @@ -13025,6 +13214,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/pretty-format@npm:4.0.18" + dependencies: + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94 + languageName: node + linkType: hard + "@vitest/runner@npm:3.2.4": version: 3.2.4 resolution: "@vitest/runner@npm:3.2.4" @@ -13036,6 +13234,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/runner@npm:4.0.18" + dependencies: + "@vitest/utils": "npm:4.0.18" + pathe: "npm:^2.0.3" + checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01 + languageName: node + linkType: hard + "@vitest/snapshot@npm:3.2.4": version: 3.2.4 resolution: "@vitest/snapshot@npm:3.2.4" @@ -13047,6 +13255,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/snapshot@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43 + languageName: node + linkType: hard + "@vitest/spy@npm:3.2.4": version: 3.2.4 resolution: "@vitest/spy@npm:3.2.4" @@ -13056,6 +13275,13 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/spy@npm:4.0.18" + checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e + languageName: node + linkType: hard + "@vitest/utils@npm:3.2.4": version: 3.2.4 resolution: "@vitest/utils@npm:3.2.4" @@ -13067,6 +13293,16 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/utils@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb + languageName: node + linkType: hard + "@wagmi/connectors@npm:6.1.3, @wagmi/connectors@npm:>=5.9.9": version: 6.1.3 resolution: "@wagmi/connectors@npm:6.1.3" @@ -13091,7 +13327,7 @@ __metadata: languageName: node linkType: hard -"@wagmi/core@npm:2.22.1, @wagmi/core@npm:^2.17.1": +"@wagmi/core@npm:2.22.1": version: 2.22.1 resolution: "@wagmi/core@npm:2.22.1" dependencies: @@ -13111,6 +13347,29 @@ __metadata: languageName: node linkType: hard +"@wagmi/core@npm:^3.4.0": + version: 3.4.0 + resolution: "@wagmi/core@npm:3.4.0" + dependencies: + eventemitter3: "npm:5.0.1" + mipd: "npm:0.0.7" + zustand: "npm:5.0.0" + peerDependencies: + "@tanstack/query-core": ">=5.0.0" + ox: ">=0.11.1" + typescript: ">=5.7.3" + viem: 2.x + peerDependenciesMeta: + "@tanstack/query-core": + optional: true + ox: + optional: true + typescript: + optional: true + checksum: 10c0/27b1751d4e7e3c75b7448e95f2cf908339a04efaa9aeb5a4d364b41c88996194b8593aead25dd4bbb02e9703bd2af02e8790747bbbdff26ebebb6c6db21d7deb + languageName: node + linkType: hard + "@wallet-standard/base@npm:^1.1.0": version: 1.1.0 resolution: "@wallet-standard/base@npm:1.1.0" @@ -14350,13 +14609,6 @@ __metadata: languageName: node linkType: hard -"aproba@npm:^1.0.3 || ^2.0.0": - version: 2.1.0 - resolution: "aproba@npm:2.1.0" - checksum: 10c0/ec8c1d351bac0717420c737eb062766fb63bde1552900e0f4fdad9eb064c3824fef23d1c416aa5f7a80f21ca682808e902d79b7c9ae756d342b5f1884f36932f - languageName: node - linkType: hard - "arch@npm:^2.2.0": version: 2.2.0 resolution: "arch@npm:2.2.0" @@ -14364,16 +14616,6 @@ __metadata: languageName: node linkType: hard -"are-we-there-yet@npm:^2.0.0": - version: 2.0.0 - resolution: "are-we-there-yet@npm:2.0.0" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c - languageName: node - linkType: hard - "arg@npm:5.0.2": version: 5.0.2 resolution: "arg@npm:5.0.2" @@ -15008,13 +15250,14 @@ __metadata: languageName: node linkType: hard -"bcrypt@npm:^5.1.1": - version: 5.1.1 - resolution: "bcrypt@npm:5.1.1" +"bcrypt@npm:^6.0.0": + version: 6.0.0 + resolution: "bcrypt@npm:6.0.0" dependencies: - "@mapbox/node-pre-gyp": "npm:^1.0.11" - node-addon-api: "npm:^5.0.0" - checksum: 10c0/743231158c866bddc46f25eb8e9617fe38bc1a6f5f3052aba35e361d349b7f8fb80e96b45c48a4c23c45c29967ccd11c81cf31166454fc0ab019801c336cab40 + node-addon-api: "npm:^8.3.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.8.4" + checksum: 10c0/006e69d4b3f0f021051fa9d998a768721a51dde28e7848e3e775ab608ba3476b25fa9262a7082ececbb0d493b0b6ca83673a6f3810b114fe90ec1bc6ae4b35b2 languageName: node linkType: hard @@ -15810,6 +16053,13 @@ __metadata: languageName: node linkType: hard +"chai@npm:^6.2.1": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + "chalk-template@npm:0.4.0": version: 0.4.0 resolution: "chalk-template@npm:0.4.0" @@ -16248,15 +16498,6 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.2": - version: 1.1.3 - resolution: "color-support@npm:1.1.3" - bin: - color-support: bin.js - checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 - languageName: node - linkType: hard - "colorette@npm:^2.0.20, colorette@npm:^2.0.7": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -16462,13 +16703,6 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": - version: 1.1.0 - resolution: "console-control-strings@npm:1.1.0" - checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 - languageName: node - linkType: hard - "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -16541,6 +16775,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c + languageName: node + linkType: hard + "copy-to-clipboard@npm:^3.3.3": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -16708,13 +16949,13 @@ __metadata: languageName: node linkType: hard -"cron@npm:4.3.5": - version: 4.3.5 - resolution: "cron@npm:4.3.5" +"cron@npm:4.4.0": + version: 4.4.0 + resolution: "cron@npm:4.4.0" dependencies: "@types/luxon": "npm:~3.7.0" luxon: "npm:~3.7.0" - checksum: 10c0/5d05afe6dd2e045709ecbc22a6ea2d5a7913cec32d4bdfff1649b32a2b0e11dafc75b1cc021aedfd1c1a8e4a813acde886ececc078207f19c90521e97a224c55 + checksum: 10c0/e3762fda4a9a2404ac2e54e1104a3c6c695747e14b2321bbc5531aa1192c75a319075695fcaf68b6927e0ffd877b5454f314c680f6586db1a78b2c25b76b8a32 languageName: node linkType: hard @@ -16822,7 +17063,14 @@ __metadata: languageName: node linkType: hard -"csstype@npm:3.1.3, csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.1.3": +"csstype@npm:3.2.3, csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.1.3": version: 3.1.3 resolution: "csstype@npm:3.1.3" checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 @@ -17292,13 +17540,6 @@ __metadata: languageName: node linkType: hard -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 - languageName: node - linkType: hard - "delete-empty@npm:^3.0.0": version: 3.0.0 resolution: "delete-empty@npm:3.0.0" @@ -17390,13 +17631,6 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0": - version: 2.1.2 - resolution: "detect-libc@npm:2.1.2" - checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 - languageName: node - linkType: hard - "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -18120,6 +18354,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -19068,6 +19391,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.2.2": + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd + languageName: node + linkType: hard + "expect@npm:^29.0.0, expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -19351,6 +19681,13 @@ __metadata: languageName: node linkType: hard +"fast-xml-builder@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-xml-builder@npm:1.0.0" + checksum: 10c0/2631fda265c81e8008884d08944eeed4e284430116faa5b8b7a43a3602af367223b7bf01c933215c9ad2358b8666e45041bc038d64877156a2f88821841b3014 + languageName: node + linkType: hard + "fast-xml-parser@npm:5.2.5": version: 5.2.5 resolution: "fast-xml-parser@npm:5.2.5" @@ -19373,6 +19710,18 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:^5.3.4": + version: 5.4.2 + resolution: "fast-xml-parser@npm:5.4.2" + dependencies: + fast-xml-builder: "npm:^1.0.0" + strnum: "npm:^2.1.2" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/83ea57fda336f3fdcc8938ecc8730236a3e084843cbe6c2fb009c3f2fe2811570316735c1c7e76a4d3dbce2b0387312b106444d5d603dc6135b4bcf0e07251bb + languageName: node + linkType: hard + "fastest-levenshtein@npm:^1.0.7": version: 1.0.16 resolution: "fastest-levenshtein@npm:1.0.16" @@ -19825,23 +20174,6 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^3.0.0": - version: 3.0.2 - resolution: "gauge@npm:3.0.2" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.2" - console-control-strings: "npm:^1.0.0" - has-unicode: "npm:^2.0.1" - object-assign: "npm:^4.1.1" - signal-exit: "npm:^3.0.0" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.2" - checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 - languageName: node - linkType: hard - "gaxios@npm:^6.0.0, gaxios@npm:^6.0.2, gaxios@npm:^6.1.1": version: 6.7.1 resolution: "gaxios@npm:6.7.1" @@ -20688,13 +21020,6 @@ __metadata: languageName: node linkType: hard -"has-unicode@npm:^2.0.1": - version: 2.0.1 - resolution: "has-unicode@npm:2.0.1" - checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c - languageName: node - linkType: hard - "hash-base@npm:^3.0.0, hash-base@npm:^3.1.2": version: 3.1.2 resolution: "hash-base@npm:3.1.2" @@ -23658,7 +23983,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.17, magic-string@npm:^0.30.3": +"magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.3": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -23674,15 +23999,6 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.1.0": - version: 3.1.0 - resolution: "make-dir@npm:3.1.0" - dependencies: - semver: "npm:^6.0.0" - checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa - languageName: node - linkType: hard - "make-dir@npm:^4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -24570,25 +24886,14 @@ __metadata: languageName: node linkType: hard -"nock@npm:^13.5.1": - version: 13.5.6 - resolution: "nock@npm:13.5.6" - dependencies: - debug: "npm:^4.1.0" - json-stringify-safe: "npm:^5.0.1" - propagate: "npm:^2.0.0" - checksum: 10c0/94249a294176a6e521bbb763c214de4aa6b6ab63dff1e299aaaf455886a465d38906891d7f24570d94a43b1e376c239c54d89ff7697124bc57ef188006acc25e - languageName: node - linkType: hard - -"nock@npm:^14.0.3": - version: 14.0.10 - resolution: "nock@npm:14.0.10" +"nock@npm:^14.0.11": + version: 14.0.11 + resolution: "nock@npm:14.0.11" dependencies: - "@mswjs/interceptors": "npm:^0.39.5" + "@mswjs/interceptors": "npm:^0.41.0" json-stringify-safe: "npm:^5.0.1" propagate: "npm:^2.0.0" - checksum: 10c0/4868ce7c3e6a51ee83b496a1305eb821ad89427eb9e09c3c431344d91fd49974717e214fe97548be7d5f9a8039fefc3602ffbaad036f3508dd2c143726e3cfb8 + checksum: 10c0/154fde5d582ad8078b328dba850cd08d4d2084ebc387e032b3f24ae56498a8a2309f5718b4a6b81eda9c4683a4de7f545fe653f263a6e84a70317f30f55b175b languageName: node linkType: hard @@ -24626,6 +24931,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^8.3.0": + version: 8.6.0 + resolution: "node-addon-api@npm:8.6.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/869fe4fd13aef4feed3e4ca042136fd677675c061b13cde3b720dcd8e60439efe2538fbab841ed273ab8d5b077e1a0af66011141796589a5db0b5e6b183e2191 + languageName: node + linkType: hard + "node-cache@npm:^5.1.2": version: 5.1.2 resolution: "node-cache@npm:5.1.2" @@ -24651,7 +24965,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -24665,7 +24979,7 @@ __metadata: languageName: node linkType: hard -"node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": +"node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0, node-gyp-build@npm:^4.8.4": version: 4.8.4 resolution: "node-gyp-build@npm:4.8.4" bin: @@ -24770,17 +25084,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" - dependencies: - abbrev: "npm:1" - bin: - nopt: bin/nopt.js - checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 - languageName: node - linkType: hard - "nopt@npm:^8.0.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -24870,18 +25173,6 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^5.0.1": - version: 5.0.1 - resolution: "npmlog@npm:5.0.1" - dependencies: - are-we-there-yet: "npm:^2.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^3.0.0" - set-blocking: "npm:^2.0.0" - checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa - languageName: node - linkType: hard - "number-to-bn@npm:1.7.0": version: 1.7.0 resolution: "number-to-bn@npm:1.7.0" @@ -25023,6 +25314,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + "ofetch@npm:^1.5.0": version: 1.5.1 resolution: "ofetch@npm:1.5.1" @@ -26321,12 +26619,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.7.4": - version: 3.7.4 - resolution: "prettier@npm:3.7.4" +"prettier@npm:^3.8.1": + version: 3.8.1 + resolution: "prettier@npm:3.8.1" bin: prettier: bin/prettier.cjs - checksum: 10c0/9675d2cd08eacb1faf1d1a2dbfe24bfab6a912b059fc9defdb380a408893d88213e794a40a2700bd29b140eb3172e0b07c852853f6e22f16f3374659a1a13389 + checksum: 10c0/33169b594009e48f570471271be7eac7cdcf88a209eed39ac3b8d6d78984039bfa9132f82b7e6ba3b06711f3bfe0222a62a1bfb87c43f50c25a83df1b78a2c42 languageName: node linkType: hard @@ -26928,27 +27226,31 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.14.1, react-router-dom@npm:^6.22.0, react-router-dom@npm:^6.23.1, react-router-dom@npm:^6.24.1, react-router-dom@npm:^6.4.3": - version: 6.30.1 - resolution: "react-router-dom@npm:6.30.1" +"react-router-dom@npm:^7.13.0": + version: 7.13.0 + resolution: "react-router-dom@npm:7.13.0" dependencies: - "@remix-run/router": "npm:1.23.0" - react-router: "npm:6.30.1" + react-router: "npm:7.13.0" peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: 10c0/e9e1297236b0faa864424ad7d51c392fc6e118595d4dad4cd542fd217c479a81601a81c6266d5801f04f9e154de02d3b094fc22ccb544e755c2eb448fab4ec6b + react: ">=18" + react-dom: ">=18" + checksum: 10c0/759bd5e7fe7b5baba50a0264724188707682d217cad8eac702a55e0b1abebf295be014dd3bfaff8e3c2def9dfaa23e6ded3f908feab84df766e9b82cc3774e98 languageName: node linkType: hard -"react-router@npm:6.30.1": - version: 6.30.1 - resolution: "react-router@npm:6.30.1" +"react-router@npm:7.13.0": + version: 7.13.0 + resolution: "react-router@npm:7.13.0" dependencies: - "@remix-run/router": "npm:1.23.0" + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" peerDependencies: - react: ">=16.8" - checksum: 10c0/0414326f2d8e0c107fb4603cf4066dacba6a1f6f025c6e273f003e177b2f18888aca3de06d9b5522908f0e41de93be1754c37e82aa97b3a269c4742c08e82539 + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/397cb009bc83d071269c8f9323bbfe1f856721fde75e39b29fe0ddfe7564ebdc3b8bbb85768321cae92ec28b406e8fac7eab7e232d0738b3b1c092e2764e4307 languageName: node linkType: hard @@ -27570,17 +27872,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - "ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1, ripemd160@npm:^2.0.3": version: 2.0.3 resolution: "ripemd160@npm:2.0.3" @@ -27973,7 +28264,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": +"semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" bin: @@ -28074,6 +28365,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + "set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -28246,7 +28544,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -28696,7 +28994,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.9.0": +"std-env@npm:^3.10.0, std-env@npm:^3.9.0": version: 3.10.0 resolution: "std-env@npm:3.10.0" checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f @@ -28835,7 +29133,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -29096,6 +29394,13 @@ __metadata: languageName: node linkType: hard +"strnum@npm:^2.1.2": + version: 2.2.0 + resolution: "strnum@npm:2.2.0" + checksum: 10c0/9a656f5048047abff8d10d0bb57761a01916e368a71e95d4f5a962b57f64b738e20672e68ba10b7de3dc78e861c77bc0566bdeed7017abdda1caf0303c929a3f + languageName: node + linkType: hard + "strtok3@npm:^10.3.4": version: 10.3.4 resolution: "strtok3@npm:10.3.4" @@ -29112,23 +29417,26 @@ __metadata: languageName: node linkType: hard -"styled-components@npm:^6.1.11": - version: 6.1.19 - resolution: "styled-components@npm:6.1.19" +"styled-components@npm:^6.3.11": + version: 6.3.11 + resolution: "styled-components@npm:6.3.11" dependencies: - "@emotion/is-prop-valid": "npm:1.2.2" - "@emotion/unitless": "npm:0.8.1" - "@types/stylis": "npm:4.2.5" + "@emotion/is-prop-valid": "npm:1.4.0" + "@emotion/unitless": "npm:0.10.0" + "@types/stylis": "npm:4.2.7" css-to-react-native: "npm:3.2.0" - csstype: "npm:3.1.3" + csstype: "npm:3.2.3" postcss: "npm:8.4.49" shallowequal: "npm:1.1.0" - stylis: "npm:4.3.2" - tslib: "npm:2.6.2" + stylis: "npm:4.3.6" + tslib: "npm:2.8.1" peerDependencies: react: ">= 16.8.0" react-dom: ">= 16.8.0" - checksum: 10c0/8d20427a5debe54bfa3b55f79af2a3577551ed7f1d1cd34df986b73fd01ac519f9081b7737cc1f76e12fbc483fa50551e55be0bc984296e623cc6a2364697cd8 + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/26e9e6205b9cebb3db56cd1ffcd43d8b7cb0d2801cdd6cea7d361b11ac32a8de24a88279e6138816c6d922099dccd6a08a0a6aeacf08f2281095151169640595 languageName: node linkType: hard @@ -29139,10 +29447,10 @@ __metadata: languageName: node linkType: hard -"stylis@npm:4.3.2": - version: 4.3.2 - resolution: "stylis@npm:4.3.2" - checksum: 10c0/0410e1404cbeee3388a9e17587875211ce2f014c8379af0d1e24ca55878867c9f1ccc7b0ce9a156ca53f5d6e301391a82b0645522a604674a378b3189a4a1994 +"stylis@npm:4.3.6": + version: 4.3.6 + resolution: "stylis@npm:4.3.6" + checksum: 10c0/e736d484983a34f7c65d362c67dc79b7bce388054b261c2b7b23d02eaaf280617033f65d44b1ea341854f4331a5074b885668ac8741f98c13a6cfd6443ae85d0 languageName: node linkType: hard @@ -29518,6 +29826,13 @@ __metadata: languageName: node linkType: hard +"tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.6": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" @@ -29542,6 +29857,13 @@ __metadata: languageName: node linkType: hard +"tinyrainbow@npm:^3.0.3": + version: 3.0.3 + resolution: "tinyrainbow@npm:3.0.3" + checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c + languageName: node + linkType: hard + "tinyspy@npm:^4.0.3": version: 4.0.4 resolution: "tinyspy@npm:4.0.4" @@ -29843,13 +30165,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.2": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb - languageName: node - linkType: hard - "tslib@npm:2.7.0": version: 2.7.0 resolution: "tslib@npm:2.7.0" @@ -31087,6 +31402,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.0.0 || ^7.0.0": + version: 7.3.1 + resolution: "vite@npm:7.3.1" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 + languageName: node + linkType: hard + "vite@npm:^6.2.4": version: 6.4.1 resolution: "vite@npm:6.4.1" @@ -31142,7 +31512,7 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^3.0.9, vitest@npm:^3.1.1": +"vitest@npm:^3.0.9": version: 3.2.4 resolution: "vitest@npm:3.2.4" dependencies: @@ -31198,6 +31568,65 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^4.0.18": + version: 4.0.18 + resolution: "vitest@npm:4.0.18" + dependencies: + "@vitest/expect": "npm:4.0.18" + "@vitest/mocker": "npm:4.0.18" + "@vitest/pretty-format": "npm:4.0.18" + "@vitest/runner": "npm:4.0.18" + "@vitest/snapshot": "npm:4.0.18" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + es-module-lexer: "npm:^1.7.0" + expect-type: "npm:^1.2.2" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^3.10.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" + vite: "npm:^6.0.0 || ^7.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.0.18 + "@vitest/browser-preview": 4.0.18 + "@vitest/browser-webdriverio": 4.0.18 + "@vitest/ui": 4.0.18 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa + languageName: node + linkType: hard + "vm-browserify@npm:^1.0.1": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" @@ -31849,15 +32278,6 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.2": - version: 1.1.5 - resolution: "wide-align@npm:1.1.5" - dependencies: - string-width: "npm:^1.0.2 || 2 || 3 || 4" - checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 - languageName: node - linkType: hard - "widest-line@npm:^3.1.0": version: 3.1.0 resolution: "widest-line@npm:3.1.0"