diff --git a/.github/workflows/cd-subgraph-hmt.yaml b/.github/workflows/cd-subgraph-hmt.yaml
index c8a3306ba6..f9916b1c1d 100644
--- a/.github/workflows/cd-subgraph-hmt.yaml
+++ b/.github/workflows/cd-subgraph-hmt.yaml
@@ -53,9 +53,9 @@ jobs:
- name: Install dependencies
if: steps.filter_networks.outputs.continue == 'true'
run: yarn workspaces focus @tools/subgraph-hmt
- - name: Build packages (scoped)
+ - name: Build core dependency
if: steps.filter_networks.outputs.continue == 'true'
- run: yarn workspaces foreach -Rpt --from @tools/subgraph-hmt run build
+ run: yarn workspace @human-protocol/core build
- name: Generate and build Subgraph
if: steps.filter_networks.outputs.continue == 'true'
run: yarn generate && yarn build
diff --git a/.github/workflows/cd-subgraph-human.yaml b/.github/workflows/cd-subgraph-human.yaml
index 0b54337f57..0ce408555b 100644
--- a/.github/workflows/cd-subgraph-human.yaml
+++ b/.github/workflows/cd-subgraph-human.yaml
@@ -53,9 +53,9 @@ jobs:
- name: Install dependencies
if: steps.filter_networks.outputs.continue == 'true'
run: yarn workspaces focus @tools/subgraph-human-protocol
- - name: Build packages (scoped)
+ - name: Build core dependency
if: steps.filter_networks.outputs.continue == 'true'
- run: yarn workspaces foreach -Rpt --from @tools/subgraph-human-protocol run build
+ run: yarn workspace @human-protocol/core build
- name: Generate and build Subgraph
if: steps.filter_networks.outputs.continue == 'true'
run: yarn generate && yarn build
diff --git a/docker-setup/docker-compose.dev.yml b/docker-setup/docker-compose.dev.yml
index 285fcb690a..28253c35b4 100644
--- a/docker-setup/docker-compose.dev.yml
+++ b/docker-setup/docker-compose.dev.yml
@@ -102,7 +102,7 @@ services:
container_name: hp-dev-graph-node
# In case of issues on Mac M1 rebuild the image for it locally
# https://github.com/graphprotocol/graph-node/blob/master/docker/README.md#running-graph-node-on-an-macbook-m1
- image: graphprotocol/graph-node
+ image: graphprotocol/graph-node:v0.41.1
restart: *default-restart
logging:
<<: *default-logging
diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json
index 0089c0ef8e..7677ec9567 100644
--- a/packages/apps/dashboard/client/package.json
+++ b/packages/apps/dashboard/client/package.json
@@ -26,15 +26,15 @@
"@mui/system": "^7.3.9",
"@mui/x-data-grid": "^8.7.0",
"@mui/x-date-pickers": "^8.26.0",
- "@tanstack/react-query": "^5.67.2",
+ "@tanstack/react-query": "^5.91.3",
"@types/react-router-dom": "^5.3.3",
"@types/recharts": "^1.8.29",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-number-format": "^5.4.3",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "react-number-format": "^5.4.5",
"react-router-dom": "^7.13.0",
"recharts": "^2.13.0-alpha.4",
"simplebar-react": "^3.3.2",
@@ -47,10 +47,10 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.2.1",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-prettier": "^5.5.5",
diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/SearchResults.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/SearchResults.tsx
index ff88b6637e..a3f2706a2d 100644
--- a/packages/apps/dashboard/client/src/features/searchResults/ui/SearchResults.tsx
+++ b/packages/apps/dashboard/client/src/features/searchResults/ui/SearchResults.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { type ReactElement, useEffect, useState } from 'react';
import Stack from '@mui/material/Stack';
import { AxiosError } from 'axios';
@@ -38,7 +38,7 @@ const renderCurrentResultType = (
const renderType: Record<
keyof AddressDetails,
- { title: string; icon: JSX.Element }
+ { title: string; icon: ReactElement }
> = {
operator: {
title: 'Wallet Address',
diff --git a/packages/apps/dashboard/client/src/shared/ui/ShadowIcon/index.tsx b/packages/apps/dashboard/client/src/shared/ui/ShadowIcon/index.tsx
index 94efb88761..0031def70c 100644
--- a/packages/apps/dashboard/client/src/shared/ui/ShadowIcon/index.tsx
+++ b/packages/apps/dashboard/client/src/shared/ui/ShadowIcon/index.tsx
@@ -1,11 +1,11 @@
-import type { FC } from 'react';
+import type { FC, ReactElement } from 'react';
import clsx from 'clsx';
const ShadowIcon: FC<{
className?: string;
title?: string;
- img: string | JSX.Element;
+ img: string | ReactElement;
}> = ({ className, title, img }) => {
return (
diff --git a/packages/apps/dashboard/server/package.json b/packages/apps/dashboard/server/package.json
index 2b4a786c14..ec904cd72d 100644
--- a/packages/apps/dashboard/server/package.json
+++ b/packages/apps/dashboard/server/package.json
@@ -49,16 +49,16 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@golevelup/ts-jest": "^1.2.1",
+ "@golevelup/ts-jest": "^3.0.0",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.14",
"@types/express": "^5.0.6",
"@types/jest": "30.0.0",
"@types/node": "22.10.5",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-jest": "^29.15.0",
+ "eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
"jest": "^29.7.0",
diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json
index 6fb1508b7e..9c0606702e 100644
--- a/packages/apps/faucet/client/package.json
+++ b/packages/apps/faucet/client/package.json
@@ -23,8 +23,8 @@
"@human-protocol/sdk": "workspace:*",
"@mui/icons-material": "^7.3.8",
"@mui/material": "^5.16.7",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
"react-loading-skeleton": "^3.3.1",
"react-router-dom": "^7.13.0",
"serve": "^14.2.4",
@@ -32,11 +32,11 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"dotenv": "^17.2.2",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^5.5.5",
diff --git a/packages/apps/faucet/server/package.json b/packages/apps/faucet/server/package.json
index ffa9ddad54..8a96c203e1 100644
--- a/packages/apps/faucet/server/package.json
+++ b/packages/apps/faucet/server/package.json
@@ -18,7 +18,7 @@
"@human-protocol/sdk": "workspace:*",
"axios": "^1.3.4",
"body-parser": "^1.20.0",
- "cors": "^2.8.5",
+ "cors": "^2.8.6",
"express": "^5.2.1",
"express-rate-limit": "^7.3.0",
"node-cache": "^5.1.2",
@@ -31,7 +31,7 @@
"@types/jest": "^29.5.14",
"@types/node": "^22.15.16",
"concurrently": "^9.1.2",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json
index 89b7e86dba..2c7c38158e 100644
--- a/packages/apps/fortune/exchange-oracle/client/package.json
+++ b/packages/apps/fortune/exchange-oracle/client/package.json
@@ -33,12 +33,12 @@
"@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",
+ "@tanstack/react-query": "^5.91.3",
"@tanstack/react-query-persist-client": "^5.80.7",
"axios": "^1.7.2",
"ethers": "^6.16.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
"react-router-dom": "^7.13.0",
"serve": "^14.2.4",
"viem": "2.x",
@@ -46,11 +46,11 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.3",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.3.1",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^16.3.0",
diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json
index e006a225c1..a0687122ae 100644
--- a/packages/apps/fortune/exchange-oracle/server/package.json
+++ b/packages/apps/fortune/exchange-oracle/server/package.json
@@ -62,7 +62,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
- "@golevelup/ts-jest": "^0.6.1",
+ "@golevelup/ts-jest": "^3.0.0",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.14",
@@ -73,9 +73,9 @@
"@types/node": "22.10.5",
"@types/passport": "^1",
"@types/pg": "8.11.10",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-jest": "^29.15.0",
+ "eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
"jest": "^29.7.0",
diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts
index d39c5d9dd6..d95fbda020 100644
--- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts
+++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts
@@ -1,6 +1,11 @@
import { createMock } from '@golevelup/ts-jest';
import { HMToken__factory } from '@human-protocol/core/typechain-types';
-import { Encryption, EscrowClient, OperatorUtils } from '@human-protocol/sdk';
+import {
+ Encryption,
+ EncryptionUtils,
+ EscrowClient,
+ OperatorUtils,
+} from '@human-protocol/sdk';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { Test } from '@nestjs/testing';
@@ -69,7 +74,6 @@ jest.mock('minio', () => {
describe('JobService', () => {
let jobService: JobService;
- let web3Service: Web3Service;
let storageService: StorageService;
let jobRepository: JobRepository;
let assignmentRepository: AssignmentRepository;
@@ -136,7 +140,6 @@ describe('JobService', () => {
}).compile();
jobService = moduleRef.get
(JobService);
- web3Service = moduleRef.get(Web3Service);
storageService = moduleRef.get(StorageService);
jobRepository = moduleRef.get(JobRepository);
assignmentRepository =
@@ -144,6 +147,11 @@ describe('JobService', () => {
webhookRepository = moduleRef.get(WebhookRepository);
});
+ afterEach(() => {
+ (jobService as any).manifestCache.clear();
+ jest.clearAllMocks();
+ });
+
describe('createJob', () => {
beforeAll(async () => {
jest.spyOn(jobRepository, 'createUnique');
@@ -497,7 +505,6 @@ describe('JobService', () => {
.mockResolvedValue(solutionsUrl);
await jobService.solveJob(assignment.id, 'solution');
- expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
expect(webhookRepository.createUnique).toHaveBeenCalledWith({
escrowAddress,
chainId,
@@ -515,7 +522,6 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
new ConflictError(ErrorAssignment.InvalidStatus),
);
- expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
});
it('should fail if user is not assigned to the job', async () => {
@@ -556,7 +562,6 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
'This job has already been completed',
);
- expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
});
it('should fail if user has already submitted a solution', async () => {
@@ -589,7 +594,100 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
new ValidationError(ErrorJob.SolutionAlreadySubmitted),
);
- expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
+ });
+ });
+
+ describe('getManifest', () => {
+ const downloadFileFromUrlMock = jest.mocked(downloadFileFromUrl);
+
+ it('should fetch and parse a non encrypted manifest', async () => {
+ const manifest: ManifestDto = {
+ requesterTitle: 'Example Title',
+ requesterDescription: 'Example Description',
+ submissionsRequired: 5,
+ fundAmount: 100,
+ };
+
+ downloadFileFromUrlMock.mockResolvedValue(JSON.stringify(manifest));
+ EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false);
+
+ const result = await jobService.getManifest(
+ chainId,
+ escrowAddress,
+ MOCK_MANIFEST_URL,
+ );
+
+ expect(result).toEqual(manifest);
+ expect(Encryption.build).not.toHaveBeenCalled();
+ });
+
+ it('should fetch and decrypt an encrypted manifest', async () => {
+ const manifest: ManifestDto = {
+ requesterTitle: 'Example Title',
+ requesterDescription: 'Example Description',
+ submissionsRequired: 5,
+ fundAmount: 100,
+ };
+
+ downloadFileFromUrlMock.mockResolvedValue('encrypted-content');
+ EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true);
+ (Encryption.build as any).mockImplementation(() => ({
+ decrypt: jest.fn().mockResolvedValue(JSON.stringify(manifest)),
+ }));
+
+ const result = await jobService.getManifest(
+ chainId,
+ escrowAddress,
+ MOCK_MANIFEST_URL,
+ );
+
+ expect(result).toEqual(manifest);
+ expect(Encryption.build).toHaveBeenCalled();
+ });
+
+ it('should cache the manifest in memory for repeated requests', async () => {
+ const manifest: ManifestDto = {
+ requesterTitle: 'Example Title',
+ requesterDescription: 'Example Description',
+ submissionsRequired: 5,
+ fundAmount: 100,
+ };
+
+ downloadFileFromUrlMock.mockResolvedValue(manifest);
+
+ const firstManifest = await jobService.getManifest(
+ chainId,
+ escrowAddress,
+ MOCK_MANIFEST_URL,
+ );
+ const secondManifest = await jobService.getManifest(
+ chainId,
+ escrowAddress,
+ MOCK_MANIFEST_URL,
+ );
+
+ expect(firstManifest).toEqual(manifest);
+ expect(secondManifest).toEqual(manifest);
+ expect(downloadFileFromUrlMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('should retry downloading the manifest after a failed request', async () => {
+ downloadFileFromUrlMock.mockRejectedValue(
+ new Error('Storage file not found'),
+ );
+ jest
+ .spyOn(webhookRepository, 'createUnique')
+ .mockResolvedValue({} as any);
+
+ await expect(
+ jobService.getManifest(chainId, escrowAddress, MOCK_MANIFEST_URL),
+ ).rejects.toThrow(ErrorJob.ManifestNotFound);
+ await expect(
+ jobService.getManifest(chainId, escrowAddress, MOCK_MANIFEST_URL),
+ ).rejects.toThrow(ErrorJob.ManifestNotFound);
+
+ expect(downloadFileFromUrlMock).toHaveBeenCalledTimes(2);
+ expect(webhookRepository.createUnique).toHaveBeenCalledTimes(2);
});
});
diff --git a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts
index 27646027f3..1c60b0df81 100644
--- a/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts
+++ b/packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts
@@ -43,6 +43,8 @@ import { JobRepository } from './job.repository';
@Injectable()
export class JobService {
+ private readonly manifestCache = new Map>();
+
constructor(
private readonly pgpConfigService: PGPConfigService,
public readonly jobRepository: JobRepository,
@@ -344,6 +346,32 @@ export class JobService {
chainId: number,
escrowAddress: string,
manifestUrl: string,
+ ): Promise {
+ const cacheKey = `${chainId}:${escrowAddress}`;
+
+ const cachedManifest = this.manifestCache.get(cacheKey);
+ if (cachedManifest) {
+ return cachedManifest;
+ }
+
+ const manifestRequest = this.fetchManifest(
+ chainId,
+ escrowAddress,
+ manifestUrl,
+ ).catch((error) => {
+ this.manifestCache.delete(cacheKey);
+ throw error;
+ });
+
+ this.manifestCache.set(cacheKey, manifestRequest);
+
+ return manifestRequest;
+ }
+
+ private async fetchManifest(
+ chainId: number,
+ escrowAddress: string,
+ manifestUrl: string,
): Promise {
let manifest: ManifestDto | null = null;
diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json
index d3fb090936..7dbb9968e4 100644
--- a/packages/apps/fortune/recording-oracle/package.json
+++ b/packages/apps/fortune/recording-oracle/package.json
@@ -50,9 +50,9 @@
"@types/express": "^5.0.6",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.16",
- "eslint": "^10.0.3",
+ "eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
- "eslint-plugin-jest": "^29.15.0",
+ "eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
"jest": "^29.7.0",
diff --git a/packages/apps/human-app/frontend/index.html b/packages/apps/human-app/frontend/index.html
index 4e8f5f2046..1e80b56cd7 100644
--- a/packages/apps/human-app/frontend/index.html
+++ b/packages/apps/human-app/frontend/index.html
@@ -11,22 +11,12 @@
-
+