From 9752affea16adbdc19264be189bee11f77674d9f Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Wed, 28 May 2025 15:21:11 +0200 Subject: [PATCH 1/7] feat: add arweave upload service --- packages/sdk/src/config/config.ts | 2 + packages/sdk/src/services/arweave.ts | 58 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 packages/sdk/src/services/arweave.ts diff --git a/packages/sdk/src/config/config.ts b/packages/sdk/src/config/config.ts index 59f332395..c93019d1e 100644 --- a/packages/sdk/src/config/config.ts +++ b/packages/sdk/src/config/config.ts @@ -27,6 +27,8 @@ export const CHAIN_CONFIG: Record = { }; export const DEFAULT_CHAIN_ID = 134; +export const DEFAULT_ARWEAVE_UPLOAD_API = 'http://localhost:3000'; // TODO change this +export const DEFAULT_ARWEAVE_GATEWAY = 'https://arweave.net'; export const DEFAULT_DATA_NAME = ''; export const SCONE_TAG = ['tee', 'scone']; export const DEFAULT_MAX_PRICE = 0; diff --git a/packages/sdk/src/services/arweave.ts b/packages/sdk/src/services/arweave.ts new file mode 100644 index 000000000..bb2c5ef58 --- /dev/null +++ b/packages/sdk/src/services/arweave.ts @@ -0,0 +1,58 @@ +import { + DEFAULT_ARWEAVE_GATEWAY, + DEFAULT_ARWEAVE_UPLOAD_API, +} from '../config/config.js'; + +interface AddOptions { + arweaveUploadApi?: string; + arweaveGateway?: string; +} + +const add = async ( + content: Uint8Array, + { arweaveGateway, arweaveUploadApi }: AddOptions = {} +): Promise => { + let arweaveId: string; + + try { + const payload = new FormData(); + payload.append('file', new Blob([content])); + const res = await fetch( + `${arweaveUploadApi || DEFAULT_ARWEAVE_UPLOAD_API}/upload`, + { + method: 'POST', + body: payload, + } + ); + if (!res.ok) { + throw Error(`Arweave upload API answered with status ${res.status}`); + } + try { + const data = await res.json(); + arweaveId = data?.arweaveId; + if (!arweaveId) { + throw Error('missing arweaveId'); + } + } catch (e) { + throw Error('Arweave upload API answered with an invalid response', { + cause: e, + }); + } + } catch (e) { + throw Error('Failed to add file on Arweave', { cause: e }); + } + const publicUrl = `${arweaveGateway || DEFAULT_ARWEAVE_GATEWAY}/${arweaveId}`; + await fetch(publicUrl) + .then((res) => { + if (!res.ok) { + throw Error(`Arweave gateway answered with status ${res.status}`); + } + }) + .catch((e) => { + throw Error(`Failed to load uploaded file at ${publicUrl}`, { cause: e }); + }); + + return arweaveId; +}; + +export { add }; From 89aa296572bbe3dfb323e9568ca6f56251a77333 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Wed, 28 May 2025 17:06:16 +0200 Subject: [PATCH 2/7] feat(sdk): add uploadMode option for protectData --- .../src/lib/dataProtectorCore/protectData.ts | 72 ++++++++++++++----- packages/sdk/src/lib/types/coreTypes.ts | 9 +++ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/sdk/src/lib/dataProtectorCore/protectData.ts b/packages/sdk/src/lib/dataProtectorCore/protectData.ts index 1117181c9..b841e731f 100644 --- a/packages/sdk/src/lib/dataProtectorCore/protectData.ts +++ b/packages/sdk/src/lib/dataProtectorCore/protectData.ts @@ -1,6 +1,10 @@ import { multiaddr as Multiaddr } from '@multiformats/multiaddr'; -import { DEFAULT_DATA_NAME } from '../../config/config.js'; -import { add } from '../../services/ipfs.js'; +import { + DEFAULT_ARWEAVE_GATEWAY, + DEFAULT_DATA_NAME, +} from '../../config/config.js'; +import * as arweave from '../../services/arweave.js'; +import * as ipfs from '../../services/ipfs.js'; import { createZipFromObject, ensureDataObjectIsValid, @@ -43,6 +47,7 @@ export const protectData = async ({ iexecDebug = throwIfMissing(), dataprotectorContractAddress, name = DEFAULT_DATA_NAME, + uploadMode = 'ipfs', ipfsNode, ipfsGateway, allowDebug = false, @@ -54,6 +59,10 @@ export const protectData = async ({ IpfsNodeAndGateway & ProtectDataParams): Promise => { const vName = stringSchema().label('name').validateSync(name); + const vUploadMode = stringSchema() + .oneOf(['ipfs', 'arweave']) + .label('uploadMode') + .validateSync(uploadMode); const vIpfsNodeUrl = urlSchema().label('ipfsNode').validateSync(ipfsNode); const vIpfsGateway = urlSchema() .label('ipfsGateway') @@ -149,28 +158,53 @@ export const protectData = async ({ title: 'UPLOAD_ENCRYPTED_FILE', isDone: false, }); - const cid = await add(encryptedFile, { - ipfsNode: vIpfsNodeUrl, - ipfsGateway: vIpfsGateway, - }).catch((e: Error) => { - throw new WorkflowError({ - message: 'Failed to upload encrypted data', - errorCause: e, + + let multiaddr: string; + let multiaddrBytes: Uint8Array; + + if (vUploadMode === 'arweave') { + const arweaveId = await arweave.add(encryptedFile).catch((e: Error) => { + throw new WorkflowError({ + message: 'Failed to upload encrypted data', + errorCause: e, + }); }); - }); - const multiaddr = `/p2p/${cid}`; - vOnStatusUpdate({ - title: 'UPLOAD_ENCRYPTED_FILE', - isDone: true, - payload: { - cid, - }, - }); + multiaddr = `${DEFAULT_ARWEAVE_GATEWAY}/${arweaveId}`; + multiaddrBytes = new TextEncoder().encode(multiaddr); + vOnStatusUpdate({ + title: 'UPLOAD_ENCRYPTED_FILE', + isDone: true, + payload: { + arweaveId, + }, + }); + } else { + // ipfs fallback + const cid = await ipfs + .add(encryptedFile, { + ipfsNode: vIpfsNodeUrl, + ipfsGateway: vIpfsGateway, + }) + .catch((e: Error) => { + throw new WorkflowError({ + message: 'Failed to upload encrypted data', + errorCause: e, + }); + }); + multiaddr = `/p2p/${cid}`; + multiaddrBytes = Multiaddr(multiaddr).bytes; + vOnStatusUpdate({ + title: 'UPLOAD_ENCRYPTED_FILE', + isDone: true, + payload: { + cid, + }, + }); + } const { provider, signer, txOptions } = await iexec.config.resolveContractsClient(); - const multiaddrBytes = Multiaddr(multiaddr).bytes; const ownerAddress = await signer.getAddress(); vOnStatusUpdate({ diff --git a/packages/sdk/src/lib/types/coreTypes.ts b/packages/sdk/src/lib/types/coreTypes.ts index c3d4035a9..126b15cf7 100644 --- a/packages/sdk/src/lib/types/coreTypes.ts +++ b/packages/sdk/src/lib/types/coreTypes.ts @@ -72,6 +72,14 @@ export type ProtectDataParams = { */ allowDebug?: boolean; + /** + * specify the platform used for storing the encrypted payload of the protected data + * + * - `"ipfs"` (default): https://ipfs.tech/ + * - `"arweave"`: https://arweave.org/ + */ + uploadMode?: 'ipfs' | 'arweave'; + /** * Callback function that will get called at each step of the process */ @@ -98,6 +106,7 @@ export type ProtectedDataCreationProps = { transactionHash: string; zipFile: Uint8Array; encryptionKey: string; + multiaddr: string; }; export type ProtectedDataWithSecretProps = ProtectedData & From b882f7e9429d1cb1926c5440c646365dffb1fa10 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Wed, 28 May 2025 17:07:13 +0200 Subject: [PATCH 3/7] test(sdk): test uploadMode arweave --- .../e2e/dataProtectorCore/protectData.test.ts | 93 +++++++ .../dataProtectorCore/protectData.test.ts | 229 ++++++++++++++++++ 2 files changed, 322 insertions(+) diff --git a/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts index 34d7925ca..86f4c29d6 100644 --- a/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts +++ b/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts @@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { HDNodeWallet, Wallet } from 'ethers'; import { IExec } from 'iexec'; import { SmsCallError } from 'iexec/errors'; +import { DEFAULT_ARWEAVE_GATEWAY } from '../../../src/config/config.js'; import { IExecDataProtectorCore, WorkflowError } from '../../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, @@ -89,6 +90,8 @@ describe('dataProtectorCore.protectData()', () => { expect(typeof result.transactionHash).toBe('string'); expect(result.zipFile).toBeInstanceOf(Uint8Array); expect(typeof result.encryptionKey).toBe('string'); + expect(typeof result.multiaddr).toBe('string'); + expect(result.multiaddr.startsWith('/p2p/')).toBe(true); const ethProvider = getTestRpcProvider(); const iexecOptions = getTestIExecOption(); @@ -218,4 +221,94 @@ describe('dataProtectorCore.protectData()', () => { 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME ); }); + + // TODO use a local service for Arweave upload API + describe('when `uploadMode: "arweave"`', () => { + it( + 'creates the protected data', + async () => { + // load some binary data + const pngImage = await fsPromises.readFile( + path.join(process.cwd(), 'tests', '_test_inputs_', 'image.png') + ); + const data = { + numberZero: 0, + numberOne: 1, + numberMinusOne: -1, + numberPointOne: 0.1, + bigintTen: BigInt(10), + booleanTrue: true, + booleanFalse: false, + string: 'hello world!', + nested: { + object: { + with: { + binary: { + data: { + pngImage, + }, + }, + }, + }, + }, + }; + + const DATA_NAME = 'test do not use'; + + const expectedSchema = { + numberZero: 'f64', + numberOne: 'f64', + numberMinusOne: 'f64', + numberPointOne: 'f64', + bigintTen: 'i128', + booleanTrue: 'bool', + booleanFalse: 'bool', + string: 'string', + nested: { + object: { + with: { + binary: { + data: { + pngImage: 'image/png', + }, + }, + }, + }, + }, + }; + + const result = await dataProtectorCore.protectData({ + data, + uploadMode: 'arweave', + name: DATA_NAME, + }); + expect(result.name).toBe(DATA_NAME); + expect(typeof result.address).toBe('string'); + expect(result.owner).toBe(wallet.address); + expect(result.schema).toStrictEqual(expectedSchema); + expect(typeof result.creationTimestamp).toBe('number'); + expect(typeof result.transactionHash).toBe('string'); + expect(result.zipFile).toBeInstanceOf(Uint8Array); + expect(typeof result.encryptionKey).toBe('string'); + expect(typeof result.multiaddr).toBe('string'); + expect(result.multiaddr.startsWith(DEFAULT_ARWEAVE_GATEWAY)).toBe(true); + console.log(result.multiaddr); + + const ethProvider = getTestRpcProvider(); + const iexecOptions = getTestIExecOption(); + const iexecProd = new IExec({ ethProvider }, iexecOptions); + const iexecDebug = new IExec( + { ethProvider }, + { ...iexecOptions, smsURL: iexecOptions.smsDebugURL } + ); + const prodSecretPushed = + await iexecProd.dataset.checkDatasetSecretExists(result.address); + const debugSecretPushed = + await iexecDebug.dataset.checkDatasetSecretExists(result.address); + expect(prodSecretPushed).toBe(true); + expect(debugSecretPushed).toBe(false); + }, + 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); + }); }); diff --git a/packages/sdk/tests/unit/dataProtectorCore/protectData.test.ts b/packages/sdk/tests/unit/dataProtectorCore/protectData.test.ts index c204ae45c..30b0251bc 100644 --- a/packages/sdk/tests/unit/dataProtectorCore/protectData.test.ts +++ b/packages/sdk/tests/unit/dataProtectorCore/protectData.test.ts @@ -12,6 +12,10 @@ jest.unstable_mockModule('../../../src/services/ipfs.js', () => ({ add: jest.fn(), })); +jest.unstable_mockModule('../../../src/services/arweave.js', () => ({ + add: jest.fn(), +})); + jest.unstable_mockModule( '../../../src/lib/dataProtectorCore/smartContract/getDataProtectorCoreContract.js', () => ({ @@ -65,6 +69,11 @@ describe('protectData()', () => { 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ' ); + const arweave: any = await import('../../../src/services/arweave.js'); + arweave.add.mockResolvedValue( + 'gxOFCHJVJ-L310Ml6vSlk1_RoE3E3IumsFBwDigR35E' + ); + const getContractModule: any = await import( '../../../src/lib/dataProtectorCore/smartContract/getDataProtectorCoreContract.js' ); @@ -145,6 +154,28 @@ describe('protectData()', () => { }); }); + describe('When uploadMode option is invalid', () => { + it('should throw a yup ValidationError with the correct message', async () => { + // --- GIVEN + const invalidUploadMode = 'foo'; + + await expect( + // --- WHEN + protectData({ + iexec, + iexecDebug, + // @ts-expect-error This is intended to actually test yup runtime validation + uploadMode: invalidUploadMode, + }) + // --- THEN + ).rejects.toThrow( + new ValidationError( + 'uploadMode must be one of the following values: ipfs, arweave' + ) + ); + }); + }); + // TODO That should be validated at IExec SDK instantiation describe('When given IPFS node URL is NOT a proper URL', () => { it('should throw a yup ValidationError with the correct message', async () => { @@ -667,5 +698,203 @@ describe('protectData()', () => { }); }); }); + + describe('when `uploadMode: "arweave"`', () => { + it('should go fine and create the protected data', async () => { + const pngImage = await fsPromises.readFile( + path.join(process.cwd(), 'tests', '_test_inputs_', 'image.png') + ); + const data = { + numberZero: 0, + numberOne: 1, + numberMinusOne: -1, + numberPointOne: 0.1, + bigintTen: BigInt(10), + booleanTrue: true, + booleanFalse: false, + string: 'hello world!', + nested: { + object: { + with: { + binary: { + data: { + pngImage, + }, + }, + }, + }, + }, + }; + const DATA_NAME = 'test do not use'; + const expectedSchema = { + numberZero: 'f64', + numberOne: 'f64', + numberMinusOne: 'f64', + numberPointOne: 'f64', + bigintTen: 'i128', + booleanTrue: 'bool', + booleanFalse: 'bool', + string: 'string', + nested: { + object: { + with: { + binary: { + data: { + pngImage: 'image/png', + }, + }, + }, + }, + }, + }; + const result = await protectData({ + iexec, + iexecDebug, + dataprotectorContractAddress: getRandomAddress(), + ...protectDataDefaultArgs, + data, + name: DATA_NAME, + uploadMode: 'arweave', + }); + + expect(result.name).toBe(DATA_NAME); + expect(result.address).toBe('mockedAddress'); + expect(result.owner).toBe(wallet.address); + expect(result.schema).toStrictEqual(expectedSchema); + expect(typeof result.creationTimestamp).toBe('number'); + expect(result.transactionHash).toBe('mockedTxHash'); + expect(result.zipFile).toBeInstanceOf(Uint8Array); + expect(typeof result.encryptionKey).toBe('string'); + + expect(iexec.dataset.pushDatasetSecret).toHaveBeenCalledTimes(1); + expect(iexecDebug.dataset.pushDatasetSecret).toHaveBeenCalledTimes(0); + }); + + it('should call the onStatusUpdate() callback function at each step', async () => { + // --- GIVEN + const onStatusUpdateMock = jest.fn(); + + // --- WHEN + await protectData({ + iexec, + iexecDebug, + dataprotectorContractAddress: getRandomAddress(), + data: { foo: 'bar' }, + uploadMode: 'arweave', + onStatusUpdate: onStatusUpdateMock, + }); + + // --- THEN + expect(onStatusUpdateMock).toHaveBeenCalledTimes(14); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(1, { + title: 'EXTRACT_DATA_SCHEMA', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(2, { + title: 'EXTRACT_DATA_SCHEMA', + isDone: true, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(3, { + title: 'CREATE_ZIP_FILE', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(4, { + title: 'CREATE_ZIP_FILE', + isDone: true, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(5, { + title: 'CREATE_ENCRYPTION_KEY', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(6, { + title: 'CREATE_ENCRYPTION_KEY', + isDone: true, + payload: { + encryptionKey: expect.any(String), + }, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(7, { + title: 'ENCRYPT_FILE', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(8, { + title: 'ENCRYPT_FILE', + isDone: true, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(9, { + title: 'UPLOAD_ENCRYPTED_FILE', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(10, { + title: 'UPLOAD_ENCRYPTED_FILE', + isDone: true, + payload: { + arweaveId: expect.any(String), + }, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(11, { + title: 'DEPLOY_PROTECTED_DATA', + isDone: false, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(12, { + title: 'DEPLOY_PROTECTED_DATA', + isDone: true, + payload: { + address: expect.any(String), + explorerUrl: expect.any(String), + owner: expect.any(String), + creationTimestamp: expect.any(String), + txHash: expect.any(String), + }, + }); + + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(13, { + title: 'PUSH_SECRET_TO_SMS', + isDone: false, + payload: { + teeFramework: expect.any(String), + }, + }); + expect(onStatusUpdateMock).toHaveBeenNthCalledWith(14, { + title: 'PUSH_SECRET_TO_SMS', + isDone: true, + payload: { + teeFramework: expect.any(String), + }, + }); + }); + + describe('When upload to Arweave fails', () => { + it('should throw a WorkflowError with the correct message', async () => { + // --- GIVEN + const arweave: any = await import('../../../src/services/arweave.js'); + arweave.add.mockRejectedValue(new Error('Boom Arweave')); + + await expect( + // --- WHEN + protectData({ + iexec, + iexecDebug, + dataprotectorContractAddress: getRandomAddress(), + ...protectDataDefaultArgs, + data: { foo: 'bar' }, + uploadMode: 'arweave', + }) + // --- THEN + ).rejects.toThrow( + new WorkflowError({ + message: 'Failed to upload encrypted data', + errorCause: Error('Boom Arweave'), + }) + ); + }); + }); + }); }); }); From 90a0c3af6a8b0273fa927420536363ce2579fb89 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:47:45 +0200 Subject: [PATCH 4/7] feat(sdk): add arweave upload size limit --- packages/sdk/src/config/config.ts | 1 + packages/sdk/src/services/arweave.ts | 10 +++++++++- packages/sdk/tests/unit/services/arweave.test.ts | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/tests/unit/services/arweave.test.ts diff --git a/packages/sdk/src/config/config.ts b/packages/sdk/src/config/config.ts index c93019d1e..911313c55 100644 --- a/packages/sdk/src/config/config.ts +++ b/packages/sdk/src/config/config.ts @@ -29,6 +29,7 @@ export const CHAIN_CONFIG: Record = { export const DEFAULT_CHAIN_ID = 134; export const DEFAULT_ARWEAVE_UPLOAD_API = 'http://localhost:3000'; // TODO change this export const DEFAULT_ARWEAVE_GATEWAY = 'https://arweave.net'; +export const ARWEAVE_FREE_UPLOAD_MAX_SIZE = 100 * 1024; // 100kb export const DEFAULT_DATA_NAME = ''; export const SCONE_TAG = ['tee', 'scone']; export const DEFAULT_MAX_PRICE = 0; diff --git a/packages/sdk/src/services/arweave.ts b/packages/sdk/src/services/arweave.ts index bb2c5ef58..ba6d89dc5 100644 --- a/packages/sdk/src/services/arweave.ts +++ b/packages/sdk/src/services/arweave.ts @@ -1,6 +1,7 @@ import { DEFAULT_ARWEAVE_GATEWAY, DEFAULT_ARWEAVE_UPLOAD_API, + ARWEAVE_FREE_UPLOAD_MAX_SIZE, } from '../config/config.js'; interface AddOptions { @@ -12,8 +13,15 @@ const add = async ( content: Uint8Array, { arweaveGateway, arweaveUploadApi }: AddOptions = {} ): Promise => { - let arweaveId: string; + if (content.length >= ARWEAVE_FREE_UPLOAD_MAX_SIZE) { + throw Error( + `Arweave upload ${(ARWEAVE_FREE_UPLOAD_MAX_SIZE / 1024).toFixed( + 0 + )}kb size limit reached` + ); + } + let arweaveId: string; try { const payload = new FormData(); payload.append('file', new Blob([content])); diff --git a/packages/sdk/tests/unit/services/arweave.test.ts b/packages/sdk/tests/unit/services/arweave.test.ts new file mode 100644 index 000000000..2abb94233 --- /dev/null +++ b/packages/sdk/tests/unit/services/arweave.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from '@jest/globals'; +import { ARWEAVE_FREE_UPLOAD_MAX_SIZE } from '../../../src/config/config.js'; +import * as arweave from '../../../src/services/arweave.js'; + +describe('arweave.add()', () => { + describe('when content to upload is too large', () => { + it('throws an error', async () => { + const content = Buffer.alloc(ARWEAVE_FREE_UPLOAD_MAX_SIZE, 0); + await expect(arweave.add(content)).rejects.toThrow( + Error('Arweave upload 100kb size limit reached') + ); + }); + }); +}); From c3d235567e2147a15129b4c6219646d6da46aea0 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:08:33 +0200 Subject: [PATCH 5/7] chore(sdk): update arweave upload API URL --- packages/sdk/src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/config/config.ts b/packages/sdk/src/config/config.ts index 911313c55..e34c1b518 100644 --- a/packages/sdk/src/config/config.ts +++ b/packages/sdk/src/config/config.ts @@ -27,7 +27,7 @@ export const CHAIN_CONFIG: Record = { }; export const DEFAULT_CHAIN_ID = 134; -export const DEFAULT_ARWEAVE_UPLOAD_API = 'http://localhost:3000'; // TODO change this +export const DEFAULT_ARWEAVE_UPLOAD_API = 'https://arweave-api.iex.ec'; export const DEFAULT_ARWEAVE_GATEWAY = 'https://arweave.net'; export const ARWEAVE_FREE_UPLOAD_MAX_SIZE = 100 * 1024; // 100kb export const DEFAULT_DATA_NAME = ''; From 29b663c5f1e65cc1d5d51183641ef6d21ac9d382 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:40:11 +0200 Subject: [PATCH 6/7] test(sdk): add arweave upload stub server --- packages/sdk/package-lock.json | 894 +++++++++++++++++- packages/sdk/package.json | 2 + .../sdk/src/lib/IExecDataProtectorModule.ts | 4 +- .../IExecDataProtectorCore.ts | 1 + .../src/lib/dataProtectorCore/protectData.ts | 15 +- packages/sdk/src/lib/types/internalTypes.ts | 4 + .../e2e/dataProtectorCore/protectData.test.ts | 18 +- packages/sdk/tests/utils/arweaveUploadApi.ts | 43 + 8 files changed, 946 insertions(+), 35 deletions(-) create mode 100644 packages/sdk/tests/utils/arweaveUploadApi.ts diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index a590c9f42..4275ad672 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@iexec/dataprotector", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@iexec/dataprotector", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.15", "license": "Apache-2.0", "dependencies": { "@ethersproject/bytes": "^5.7.0", @@ -30,6 +30,7 @@ "@swc/core": "^1.7.3", "@swc/jest": "^0.2.36", "@types/debug": "^4.1.7", + "@types/express": "^5.0.2", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", @@ -42,6 +43,7 @@ "eslint-plugin-jest": "^27.4.2", "eslint-plugin-sonarjs": "^0.21.0", "eslint-plugin-unicorn": "^51.0.1", + "express": "^5.1.0", "jest": "^29.7.0", "prettier": "^2.8.4", "rimraf": "^5.0.5", @@ -2411,6 +2413,27 @@ "@types/node": "*" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.11.tgz", @@ -2428,6 +2451,31 @@ "@types/node": "*" } }, + "node_modules/@types/express": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.2.tgz", + "integrity": "sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2437,6 +2485,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2483,6 +2538,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", @@ -2509,12 +2571,49 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", @@ -2762,6 +2861,20 @@ "node": ">=10" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -3170,6 +3283,40 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "license": "MIT" }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/borsh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/borsh/-/borsh-2.0.0.tgz", @@ -3283,6 +3430,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -3297,6 +3454,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3650,12 +3838,76 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-js-compat": { "version": "3.36.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", @@ -3825,6 +4077,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3878,12 +4140,34 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-fetch": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.9.1.tgz", @@ -3918,6 +4202,16 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -4017,6 +4311,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -4066,6 +4393,13 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4639,6 +4973,16 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ethers": { "version": "6.13.5", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", @@ -4720,6 +5064,49 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4839,6 +5226,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-replace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", @@ -4938,6 +5343,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -5038,15 +5463,25 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5066,6 +5501,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -5189,12 +5638,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5274,10 +5724,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5312,10 +5763,11 @@ "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -5335,6 +5787,23 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5573,6 +6042,16 @@ "node": ">= 0.4" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ipfs-unixfs": { "version": "11.1.4", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.1.4.tgz", @@ -5807,6 +6286,13 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -6998,6 +7484,39 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-options": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", @@ -7037,6 +7556,29 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -7161,6 +7703,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7243,10 +7795,14 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7338,6 +7894,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7605,6 +8174,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7662,6 +8241,16 @@ "node": "14 || >=16.14" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7883,6 +8472,20 @@ "multiformats": "^13.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7908,6 +8511,22 @@ } ] }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/query-string": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.1.tgz", @@ -7945,6 +8564,45 @@ } ] }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -8376,6 +9034,23 @@ "resolved": "https://registry.npmjs.org/rlc-faucet-contract/-/rlc-faucet-contract-1.0.10.tgz", "integrity": "sha512-pGQyIMcLIUQraCTNIcA4B9FY1g2vJHB8qWg3qbwjA1YUj1490LStKDlA4YlPwPgO/a0NWg0UHD7R9ki3DMGACQ==" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -8477,6 +9152,45 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -8511,6 +9225,13 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8533,14 +9254,76 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8663,6 +9446,16 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -8974,6 +9767,16 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", @@ -9135,6 +9938,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typechain": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", @@ -9368,6 +10186,16 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -9445,6 +10273,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 8364e0689..90b6c41e2 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -73,6 +73,7 @@ "@swc/core": "^1.7.3", "@swc/jest": "^0.2.36", "@types/debug": "^4.1.7", + "@types/express": "^5.0.2", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", @@ -85,6 +86,7 @@ "eslint-plugin-jest": "^27.4.2", "eslint-plugin-sonarjs": "^0.21.0", "eslint-plugin-unicorn": "^51.0.1", + "express": "^5.1.0", "jest": "^29.7.0", "prettier": "^2.8.4", "rimraf": "^5.0.5", diff --git a/packages/sdk/src/lib/IExecDataProtectorModule.ts b/packages/sdk/src/lib/IExecDataProtectorModule.ts index 802c8efd4..df7716c86 100644 --- a/packages/sdk/src/lib/IExecDataProtectorModule.ts +++ b/packages/sdk/src/lib/IExecDataProtectorModule.ts @@ -1,7 +1,7 @@ import { AbstractProvider, AbstractSigner, Eip1193Provider } from 'ethers'; import { GraphQLClient } from 'graphql-request'; import { IExec } from 'iexec'; -import { CHAIN_CONFIG } from '../config/config.js'; +import { CHAIN_CONFIG, DEFAULT_ARWEAVE_UPLOAD_API } from '../config/config.js'; import { getChainIdFromProvider } from '../utils/getChainId.js'; import { AddressOrENS, @@ -38,6 +38,8 @@ abstract class IExecDataProtectorModule { protected ipfsGateway!: string; + protected arweaveUploadApi: string = DEFAULT_ARWEAVE_UPLOAD_API; + protected defaultWorkerpool!: string; protected iexec!: IExec; diff --git a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts index 471ab4957..d65a1285a 100644 --- a/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts +++ b/packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts @@ -39,6 +39,7 @@ class IExecDataProtectorCore extends IExecDataProtectorModule { dataprotectorContractAddress: this.dataprotectorContractAddress, ipfsNode: this.ipfsNode, ipfsGateway: this.ipfsGateway, + arweaveUploadApi: this.arweaveUploadApi, iexec: this.iexec, iexecDebug: this.iexecDebug, }); diff --git a/packages/sdk/src/lib/dataProtectorCore/protectData.ts b/packages/sdk/src/lib/dataProtectorCore/protectData.ts index b841e731f..390e816ba 100644 --- a/packages/sdk/src/lib/dataProtectorCore/protectData.ts +++ b/packages/sdk/src/lib/dataProtectorCore/protectData.ts @@ -32,6 +32,7 @@ import { ProtectedDataWithSecretProps, } from '../types/index.js'; import { + ArweaveUploadConsumer, DataProtectorContractConsumer, IExecConsumer, IExecDebugConsumer, @@ -50,6 +51,7 @@ export const protectData = async ({ uploadMode = 'ipfs', ipfsNode, ipfsGateway, + arweaveUploadApi, allowDebug = false, data, onStatusUpdate = () => {}, @@ -57,6 +59,7 @@ export const protectData = async ({ IExecDebugConsumer & DataProtectorContractConsumer & IpfsNodeAndGateway & + ArweaveUploadConsumer & ProtectDataParams): Promise => { const vName = stringSchema().label('name').validateSync(name); const vUploadMode = stringSchema() @@ -163,12 +166,14 @@ export const protectData = async ({ let multiaddrBytes: Uint8Array; if (vUploadMode === 'arweave') { - const arweaveId = await arweave.add(encryptedFile).catch((e: Error) => { - throw new WorkflowError({ - message: 'Failed to upload encrypted data', - errorCause: e, + const arweaveId = await arweave + .add(encryptedFile, { arweaveUploadApi }) + .catch((e: Error) => { + throw new WorkflowError({ + message: 'Failed to upload encrypted data', + errorCause: e, + }); }); - }); multiaddr = `${DEFAULT_ARWEAVE_GATEWAY}/${arweaveId}`; multiaddrBytes = new TextEncoder().encode(multiaddr); vOnStatusUpdate({ diff --git a/packages/sdk/src/lib/types/internalTypes.ts b/packages/sdk/src/lib/types/internalTypes.ts index 0215539e5..791cde3e7 100644 --- a/packages/sdk/src/lib/types/internalTypes.ts +++ b/packages/sdk/src/lib/types/internalTypes.ts @@ -17,6 +17,10 @@ export type DataProtectorContractConsumer = { dataprotectorContractAddress: AddressOrENS; }; +export type ArweaveUploadConsumer = { + arweaveUploadApi?: string; +}; + export type SubgraphConsumer = { graphQLClient: GraphQLClient; }; diff --git a/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts b/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts index 86f4c29d6..8473ae76f 100644 --- a/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts +++ b/packages/sdk/tests/e2e/dataProtectorCore/protectData.test.ts @@ -14,6 +14,10 @@ import { getTestRpcProvider, getTestWeb3SignerProvider, } from '../../test-utils.js'; +import { + getArweaveUploadStubServer, + ArweaveUploadStubServer, +} from '../../utils/arweaveUploadApi.js'; describe('dataProtectorCore.protectData()', () => { let dataProtectorCore: IExecDataProtectorCore; @@ -224,6 +228,13 @@ describe('dataProtectorCore.protectData()', () => { // TODO use a local service for Arweave upload API describe('when `uploadMode: "arweave"`', () => { + let arweaveUploadServerStub: ArweaveUploadStubServer; + beforeAll(async () => { + arweaveUploadServerStub = await getArweaveUploadStubServer(); + }); + afterAll(async () => { + await arweaveUploadServerStub.stop(); + }); it( 'creates the protected data', async () => { @@ -277,6 +288,12 @@ describe('dataProtectorCore.protectData()', () => { }, }; + // override protected property to use stub server + dataProtectorCore[ + // eslint-disable-next-line @typescript-eslint/dot-notation + 'arweaveUploadApi' + ] = arweaveUploadServerStub.url; + const result = await dataProtectorCore.protectData({ data, uploadMode: 'arweave', @@ -292,7 +309,6 @@ describe('dataProtectorCore.protectData()', () => { expect(typeof result.encryptionKey).toBe('string'); expect(typeof result.multiaddr).toBe('string'); expect(result.multiaddr.startsWith(DEFAULT_ARWEAVE_GATEWAY)).toBe(true); - console.log(result.multiaddr); const ethProvider = getTestRpcProvider(); const iexecOptions = getTestIExecOption(); diff --git a/packages/sdk/tests/utils/arweaveUploadApi.ts b/packages/sdk/tests/utils/arweaveUploadApi.ts new file mode 100644 index 000000000..ab84ef940 --- /dev/null +++ b/packages/sdk/tests/utils/arweaveUploadApi.ts @@ -0,0 +1,43 @@ +import { AddressInfo } from 'net'; +import express from 'express'; + +// stub for https://github.com/iExecBlockchainComputing/iexec-arweave-api +const app = express(); +app.use('/upload', (req, res) => { + const fileId = 'd1BnXCOft0eM8usWdgd172zLaktRTmjmTp2RoX-Gb1M'; + res + .status(200) + .json({ arweaveId: fileId, url: `https://arweave.net/${fileId}` }); +}); + +export type ArweaveUploadStubServer = { + url: string; + stop: () => Promise; +}; + +export const getArweaveUploadStubServer: () => Promise = + async () => + new Promise((resolve, reject) => { + const server = app.listen((listenErr) => { + if (listenErr) { + reject(listenErr); + } else { + const addressInfo = server.address() as AddressInfo; + const url = `http://127.0.0.1:${addressInfo.port}`; + const stop = () => + new Promise((res, rej) => + server.close((closeErr) => { + if (closeErr) { + rej(closeErr); + } else { + res(); + } + }) + ); + resolve({ + url, + stop, + }); + } + }); + }); From 3b9ce180f594f1194ccd53eef1ef56c10ec32ff6 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:29:36 +0200 Subject: [PATCH 7/7] feat(sdk): add support for URL in multiaddr --- .../sdk/src/utils/getMultiaddrAsString.ts | 27 ++++++++++++++----- .../unit/utils/getMultiaddrAsString.test.ts | 18 +++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/utils/getMultiaddrAsString.ts b/packages/sdk/src/utils/getMultiaddrAsString.ts index 8ed4520d2..f53307dd4 100644 --- a/packages/sdk/src/utils/getMultiaddrAsString.ts +++ b/packages/sdk/src/utils/getMultiaddrAsString.ts @@ -22,15 +22,28 @@ export function getMultiaddrAsString({ if (!multiAddrAsBytesArray) { return undefined; } + const multiaddrAsBytes = new Uint8Array(multiAddrAsBytesArray); + + // try utf8 encoded url + try { + const decodedString = new TextDecoder().decode(multiaddrAsBytes); + if (decodedString.startsWith('https://')) { + return decodedString; + } + } catch { + // noop + } + + // try machine format multiaddr try { - const multiaddrAsBytes = new Uint8Array(multiAddrAsBytesArray); const decodedMultiaddr = multiaddr(multiaddrAsBytes); return decodedMultiaddr.toString(); - } catch (err) { - console.warn( - `[getMultiaddrAsString] ERROR: "${multiaddrAsHexString}" is not a valid hex input string?`, - err - ); - return undefined; + } catch { + // noop } + + console.warn( + `[getMultiaddrAsString] ERROR: "${multiaddrAsHexString}" is not a valid hex encoded multiaddr?` + ); + return undefined; } diff --git a/packages/sdk/tests/unit/utils/getMultiaddrAsString.test.ts b/packages/sdk/tests/unit/utils/getMultiaddrAsString.test.ts index c545f8a23..24f01859c 100644 --- a/packages/sdk/tests/unit/utils/getMultiaddrAsString.test.ts +++ b/packages/sdk/tests/unit/utils/getMultiaddrAsString.test.ts @@ -48,4 +48,22 @@ describe('getMultiaddrAsString', () => { ); }); }); + + describe('When giving hex encoded URL', () => { + it('should return a human readable URL', () => { + // --- GIVEN + const multiaddrAsHexString = + '0x68747470733a2f2f617277656176652e6e65742f6431426e58434f667430654d387573576467643137327a4c616b7452546d6a6d547032526f582d4762314d'; + + // --- WHEN + const decodedMultiaddr = getMultiaddrAsString({ + multiaddrAsHexString, + }); + + // --- THEN + expect(decodedMultiaddr).toEqual( + 'https://arweave.net/d1BnXCOft0eM8usWdgd172zLaktRTmjmTp2RoX-Gb1M' + ); + }); + }); });