diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 835e8083..af7afde5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,6 +79,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Codegen subgraph + run: pnpm --filter @dealbot/subgraph codegen + - name: Run unit tests run: pnpm test diff --git a/apps/backend/.env.example b/apps/backend/.env.example index 6815a66f..26469c52 100644 --- a/apps/backend/.env.example +++ b/apps/backend/.env.example @@ -23,7 +23,8 @@ WALLET_ADDRESS=0x0000000000000000000000000000000000000000 WALLET_PRIVATE_KEY=your_private_key_here CHECK_DATASET_CREATION_FEES=true USE_ONLY_APPROVED_PROVIDERS=true -PDP_SUBGRAPH_ENDPOINT=https://api.thegraph.com/subgraphs/filecoin/pdp +# Point at the dealbot-owned subgraph on Goldsky (see apps/subgraph/README.md). +SUBGRAPH_ENDPOINT=https://api.goldsky.com/api/public//subgraphs/dealbot-subgraph//gn # Minimum number of datasets per SP (default: 1). When > 1, a separate data_set_creation job provisions extra datasets. MIN_NUM_DATASETS_FOR_CHECKS=1 @@ -52,6 +53,9 @@ DEALBOT_MAINTENANCE_WINDOW_MINUTES=20 DEALS_PER_SP_PER_HOUR=2 DATASET_CREATIONS_PER_SP_PER_HOUR=1 RETRIEVALS_PER_SP_PER_HOUR=1 +RETRIEVALS_ANON_PER_SP_PER_HOUR= +ANON_RETRIEVAL_BLOCK_SAMPLE_COUNT=5 +METRICS_PER_HOUR=2 PG_BOSS_LOCAL_CONCURRENCY=20 JOB_SCHEDULER_POLL_SECONDS=300 JOB_WORKER_POLL_SECONDS=60 @@ -60,6 +64,7 @@ JOB_SCHEDULE_PHASE_SECONDS=0 JOB_ENQUEUE_JITTER_SECONDS=0 DEAL_JOB_TIMEOUT_SECONDS=360 # 6m: Max runtime for deal jobs (TODO: reduce default to 3m) RETRIEVAL_JOB_TIMEOUT_SECONDS=60 # 1m: Max runtime for retrieval jobs (TODO: reduce default to 30s) +ANON_RETRIEVAL_JOB_TIMEOUT_SECONDS=360 # 6m: Max runtime for anon retrieval jobs (pieces up to ~70 MiB) IPFS_BLOCK_FETCH_CONCURRENCY=6 # Parallel block fetches when validating IPFS DAGs DEALBOT_PGBOSS_POOL_MAX=1 DEALBOT_PGBOSS_SCHEDULER_ENABLED=true @@ -73,9 +78,13 @@ PROXY_LIST=http://username:password@host:port,http://username:password@host:port PROXY_LOCATIONS=l1,l2 # Timeout Configuration (in milliseconds) -CONNECT_TIMEOUT_MS=10000 # 10s: Initial connection timeout -HTTP_REQUEST_TIMEOUT_MS=240000 # 4m: Total transfer timeout for HTTP/1.1 (10MiB @ 170KB/s + overhead) -HTTP2_REQUEST_TIMEOUT_MS=240000 # 4m: Total transfer timeout for HTTP/2 (10MiB @ 170KB/s + overhead) +CONNECT_TIMEOUT_MS=10000 # 10s: Connection + response-headers timeout (scoped to the header phase only) +# HTTP_REQUEST_TIMEOUT_MS and HTTP2_REQUEST_TIMEOUT_MS default to the longest job timeout above +# (max of DEAL_/RETRIEVAL_/ANON_RETRIEVAL_/DATA_SET_CREATION_/MAX_PIECE_CLEANUP_ * 1000 ms) so the +# HTTP-level ceiling never pre-empts a job-scoped AbortSignal. Only override when you have a non-job +# caller of HttpClientService that needs a specific deadline. +# HTTP_REQUEST_TIMEOUT_MS=360000 +# HTTP2_REQUEST_TIMEOUT_MS=360000 # SP Blocklists configuration # BLOCKED_SP_IDS=1234,5678 diff --git a/apps/backend/.tool-versions b/apps/backend/.tool-versions new file mode 100644 index 00000000..7352e387 --- /dev/null +++ b/apps/backend/.tool-versions @@ -0,0 +1,2 @@ +nodejs 25.8.1 +pnpm 10.33.0 diff --git a/apps/backend/README.md b/apps/backend/README.md index 19ee970a..4805080f 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -104,7 +104,7 @@ All configuration is done via environment variables in `.env`. | `CHECK_DATASET_CREATION_FEES` | Check fees before dataset creation | `true` | | `ENABLE_IPNI_TESTING` | IPNI testing mode (`disabled`/`random`/`always`) | `always` | | `USE_ONLY_APPROVED_PROVIDERS` | Only use approved storage providers | `true` | -| `PDP_SUBGRAPH_ENDPOINT` | PDP subgraph API endpoint for PDP proof-set/data-retention | `https://api.thegraph.com/subgraphs/filecoin/pdp` | +| `SUBGRAPH_ENDPOINT` | Subgraph GraphQL endpoint for PDP proof-set/data-retention and anon-retrieval queries | `https://api.goldsky.com/api/public//subgraphs/dealbot-subgraph//gn` | ### Scheduling Configuration (pg-boss) diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 29751324..961daa87 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -12,6 +12,7 @@ import { JobsModule } from "./jobs/jobs.module.js"; import { MetricsPrometheusModule } from "./metrics-prometheus/metrics-prometheus.module.js"; import { ProvidersModule } from "./providers/providers.module.js"; import { RetrievalModule } from "./retrieval/retrieval.module.js"; +import { RetrievalAnonModule } from "./retrieval-anon/retrieval-anon.module.js"; @Module({ imports: [ @@ -26,6 +27,7 @@ import { RetrievalModule } from "./retrieval/retrieval.module.js"; JobsModule, DealModule, RetrievalModule, + RetrievalAnonModule, DataSourceModule, ProvidersModule, ...(process.env.ENABLE_DEV_MODE === "true" ? [DevToolsModule] : []), diff --git a/apps/backend/src/config/app.config.ts b/apps/backend/src/config/app.config.ts index a8010bab..255a6217 100644 --- a/apps/backend/src/config/app.config.ts +++ b/apps/backend/src/config/app.config.ts @@ -56,7 +56,7 @@ export const configValidationSchema = Joi.object({ USE_ONLY_APPROVED_PROVIDERS: Joi.boolean().default(true), DEALBOT_DATASET_VERSION: Joi.string().optional(), MIN_NUM_DATASETS_FOR_CHECKS: Joi.number().integer().min(1).default(1), - PDP_SUBGRAPH_ENDPOINT: Joi.string().uri().optional().allow(""), + SUBGRAPH_ENDPOINT: Joi.string().uri().optional().allow(""), // Scheduling PROVIDERS_REFRESH_INTERVAL_SECONDS: Joi.number().default(4 * 3600), @@ -80,6 +80,7 @@ export const configValidationSchema = Joi.object({ DEALS_PER_SP_PER_HOUR: Joi.number().min(0.001).max(20).default(4), DATASET_CREATIONS_PER_SP_PER_HOUR: Joi.number().min(0.001).max(20).default(1), RETRIEVALS_PER_SP_PER_HOUR: Joi.number().min(0.001).max(20).default(2), + RETRIEVALS_ANON_PER_SP_PER_HOUR: Joi.number().min(0.001).max(20).optional(), // Polling interval for pg-boss scheduler (lower = more responsive, higher = less DB chatter). JOB_SCHEDULER_POLL_SECONDS: Joi.number().min(60).default(300), JOB_WORKER_POLL_SECONDS: Joi.number().min(5).default(60), @@ -91,8 +92,10 @@ export const configValidationSchema = Joi.object({ JOB_ENQUEUE_JITTER_SECONDS: Joi.number().min(0).default(0), DEAL_JOB_TIMEOUT_SECONDS: Joi.number().min(120).default(360), // 6 minutes max runtime for data storage jobs (TODO: reduce default to 3 minutes) RETRIEVAL_JOB_TIMEOUT_SECONDS: Joi.number().min(60).default(60), // 1 minute max runtime for retrieval jobs (TODO: reduce default to 30 seconds) + ANON_RETRIEVAL_JOB_TIMEOUT_SECONDS: Joi.number().min(60).default(360), // 6 minutes max runtime for anon retrieval jobs (pieces can be up to ~70 MiB) DATA_SET_CREATION_JOB_TIMEOUT_SECONDS: Joi.number().min(60).default(300), // 5 minutes max runtime for dataset creation jobs IPFS_BLOCK_FETCH_CONCURRENCY: Joi.number().integer().min(1).max(32).default(6), + ANON_RETRIEVAL_BLOCK_SAMPLE_COUNT: Joi.number().integer().min(1).max(50).default(5), // Piece Cleanup MAX_DATASET_STORAGE_SIZE_BYTES: Joi.number() @@ -124,8 +127,9 @@ export const configValidationSchema = Joi.object({ // Timeouts (in milliseconds) CONNECT_TIMEOUT_MS: Joi.number().min(1000).default(10000), // 10 seconds to establish connection/receive headers - HTTP_REQUEST_TIMEOUT_MS: Joi.number().min(1000).default(240000), // 4 minutes total for HTTP requests (10MiB @ 170KB/s + overhead) - HTTP2_REQUEST_TIMEOUT_MS: Joi.number().min(1000).default(240000), // 4 minutes total for HTTP/2 requests (10MiB @ 170KB/s + overhead) + // Defaults intentionally omitted so loadConfig can derive them from the longest job timeout. + HTTP_REQUEST_TIMEOUT_MS: Joi.number().min(1000).optional(), + HTTP2_REQUEST_TIMEOUT_MS: Joi.number().min(1000).optional(), IPNI_VERIFICATION_TIMEOUT_MS: Joi.number().min(1000).default(60000), // 60 seconds max time to wait for IPNI verification IPNI_VERIFICATION_POLLING_MS: Joi.number().min(250).default(2000), // 2 seconds between IPNI verification polls @@ -165,7 +169,7 @@ export interface IBlockchainConfig { useOnlyApprovedProviders: boolean; dealbotDataSetVersion?: string; minNumDataSetsForChecks: number; - pdpSubgraphEndpoint?: string; + subgraphEndpoint?: string; } export interface ISchedulingConfig { @@ -256,6 +260,14 @@ export interface IJobsConfig { * Uses AbortController to actively cancel job execution. */ retrievalJobTimeoutSeconds: number; + /** + * Maximum runtime (seconds) for anonymous retrieval jobs before forced abort. + * + * Anonymous retrievals fetch arbitrary pieces (up to ~70 MiB), so this is + * typically larger than `retrievalJobTimeoutSeconds`. Uses AbortController + * to actively cancel job execution while still persisting partial metrics. + */ + anonRetrievalJobTimeoutSeconds: number; /** * Target number of piece cleanup runs per storage provider per hour. * @@ -270,6 +282,12 @@ export interface IJobsConfig { * Only used when `DEALBOT_JOBS_MODE=pgboss`. */ maxPieceCleanupRuntimeSeconds: number; + + /** + * Target number of anonymous retrieval tests per storage provider per hour. + * Defaults to retrievalsPerSpPerHour when not set. + */ + retrievalsAnonPerSpPerHour: number; } export interface IDatasetConfig { @@ -287,6 +305,10 @@ export interface ITimeoutConfig { export interface IRetrievalConfig { ipfsBlockFetchConcurrency: number; + /** + * Number of CAR blocks to sample for IPNI + block-fetch validation. + */ + anonBlockSampleCount: number; } export interface IPieceCleanupConfig { @@ -315,6 +337,43 @@ export interface IConfig { } export function loadConfig(): IConfig { + const jobTimeoutSeconds = { + deal: Number.parseInt(process.env.DEAL_JOB_TIMEOUT_SECONDS || "360", 10), + retrieval: Number.parseInt(process.env.RETRIEVAL_JOB_TIMEOUT_SECONDS || "60", 10), + anonRetrieval: Number.parseInt(process.env.ANON_RETRIEVAL_JOB_TIMEOUT_SECONDS || "360", 10), + dataSetCreation: Number.parseInt(process.env.DATA_SET_CREATION_JOB_TIMEOUT_SECONDS || "300", 10), + pieceCleanup: Number.parseInt(process.env.MAX_PIECE_CLEANUP_RUNTIME_SECONDS || "300", 10), + }; + + // HTTP-level request timeouts default to the longest job timeout so the + // per-request ceiling never caps below the per-job budget. Any job-scoped + // AbortSignal fires first and is authoritative; the HTTP timer only kicks + // in for callers that do not pass a parent signal. + const longestJobTimeoutMs = Math.max(...Object.values(jobTimeoutSeconds)) * 1000; + + const httpRequestTimeoutMs = Number.parseInt(process.env.HTTP_REQUEST_TIMEOUT_MS || String(longestJobTimeoutMs), 10); + const http2RequestTimeoutMs = Number.parseInt( + process.env.HTTP2_REQUEST_TIMEOUT_MS || String(longestJobTimeoutMs), + 10, + ); + + // Misconfiguration guard: if someone explicitly sets an HTTP timeout below + // the longest job timeout, the HTTP-level timer will abort in-flight work + // before the job signal has a chance to report it. Warn loudly so this is + // caught at boot rather than inferred from short-timeout incidents later. + for (const [name, value] of [ + ["HTTP_REQUEST_TIMEOUT_MS", httpRequestTimeoutMs], + ["HTTP2_REQUEST_TIMEOUT_MS", http2RequestTimeoutMs], + ] as const) { + if (value < longestJobTimeoutMs) { + // eslint-disable-next-line no-console + console.warn( + `[config] ${name}=${value}ms is lower than the longest job timeout (${longestJobTimeoutMs}ms). ` + + `HTTP requests may abort before the job signal fires, producing short, unexplained timeouts.`, + ); + } + } + return { app: { env: process.env.NODE_ENV || "development", @@ -356,7 +415,7 @@ export function loadConfig(): IConfig { useOnlyApprovedProviders: process.env.USE_ONLY_APPROVED_PROVIDERS !== "false", dealbotDataSetVersion: process.env.DEALBOT_DATASET_VERSION, minNumDataSetsForChecks: Number.parseInt(process.env.MIN_NUM_DATASETS_FOR_CHECKS || "1", 10), - pdpSubgraphEndpoint: process.env.PDP_SUBGRAPH_ENDPOINT || "", + subgraphEndpoint: process.env.SUBGRAPH_ENDPOINT || "", }, scheduling: { providersRefreshIntervalSeconds: Number.parseInt(process.env.PROVIDERS_REFRESH_INTERVAL_SECONDS || "14400", 10), @@ -379,11 +438,15 @@ export function loadConfig(): IConfig { catchupMaxEnqueue: Number.parseInt(process.env.JOB_CATCHUP_MAX_ENQUEUE || "10", 10), schedulePhaseSeconds: Number.parseInt(process.env.JOB_SCHEDULE_PHASE_SECONDS || "0", 10), enqueueJitterSeconds: Number.parseInt(process.env.JOB_ENQUEUE_JITTER_SECONDS || "0", 10), - dealJobTimeoutSeconds: Number.parseInt(process.env.DEAL_JOB_TIMEOUT_SECONDS || "360", 10), - retrievalJobTimeoutSeconds: Number.parseInt(process.env.RETRIEVAL_JOB_TIMEOUT_SECONDS || "60", 10), - dataSetCreationJobTimeoutSeconds: Number.parseInt(process.env.DATA_SET_CREATION_JOB_TIMEOUT_SECONDS || "300", 10), + dealJobTimeoutSeconds: jobTimeoutSeconds.deal, + retrievalJobTimeoutSeconds: jobTimeoutSeconds.retrieval, + anonRetrievalJobTimeoutSeconds: jobTimeoutSeconds.anonRetrieval, + retrievalsAnonPerSpPerHour: Number.parseFloat( + process.env.RETRIEVALS_ANON_PER_SP_PER_HOUR || process.env.RETRIEVALS_PER_SP_PER_HOUR || "2", + ), + dataSetCreationJobTimeoutSeconds: jobTimeoutSeconds.dataSetCreation, pieceCleanupPerSpPerHour: Number.parseFloat(process.env.JOB_PIECE_CLEANUP_PER_SP_PER_HOUR || String(1 / 24)), - maxPieceCleanupRuntimeSeconds: Number.parseInt(process.env.MAX_PIECE_CLEANUP_RUNTIME_SECONDS || "300", 10), + maxPieceCleanupRuntimeSeconds: jobTimeoutSeconds.pieceCleanup, }, dataset: { localDatasetsPath: process.env.DEALBOT_LOCAL_DATASETS_PATH || DEFAULT_LOCAL_DATASETS_PATH, @@ -405,13 +468,14 @@ export function loadConfig(): IConfig { }, timeouts: { connectTimeoutMs: Number.parseInt(process.env.CONNECT_TIMEOUT_MS || "10000", 10), - httpRequestTimeoutMs: Number.parseInt(process.env.HTTP_REQUEST_TIMEOUT_MS || "240000", 10), - http2RequestTimeoutMs: Number.parseInt(process.env.HTTP2_REQUEST_TIMEOUT_MS || "240000", 10), + httpRequestTimeoutMs, + http2RequestTimeoutMs, ipniVerificationTimeoutMs: Number.parseInt(process.env.IPNI_VERIFICATION_TIMEOUT_MS || "60000", 10), ipniVerificationPollingMs: Number.parseInt(process.env.IPNI_VERIFICATION_POLLING_MS || "2000", 10), }, retrieval: { ipfsBlockFetchConcurrency: Number.parseInt(process.env.IPFS_BLOCK_FETCH_CONCURRENCY || "6", 10), + anonBlockSampleCount: Number.parseInt(process.env.ANON_RETRIEVAL_BLOCK_SAMPLE_COUNT || "5", 10), }, pieceCleanup: { maxDatasetStorageSizeBytes: Number.parseInt( diff --git a/apps/backend/src/data-retention/data-retention.module.ts b/apps/backend/src/data-retention/data-retention.module.ts index f459570a..f0aec1ec 100644 --- a/apps/backend/src/data-retention/data-retention.module.ts +++ b/apps/backend/src/data-retention/data-retention.module.ts @@ -2,12 +2,12 @@ import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { DataRetentionBaseline } from "../database/entities/data-retention-baseline.entity.js"; import { StorageProvider } from "../database/entities/storage-provider.entity.js"; -import { PdpSubgraphModule } from "../pdp-subgraph/pdp-subgraph.module.js"; +import { SubgraphModule } from "../subgraph/subgraph.module.js"; import { WalletSdkModule } from "../wallet-sdk/wallet-sdk.module.js"; import { DataRetentionService } from "./data-retention.service.js"; @Module({ - imports: [WalletSdkModule, PdpSubgraphModule, TypeOrmModule.forFeature([DataRetentionBaseline, StorageProvider])], + imports: [WalletSdkModule, SubgraphModule, TypeOrmModule.forFeature([DataRetentionBaseline, StorageProvider])], providers: [DataRetentionService], exports: [DataRetentionService], }) diff --git a/apps/backend/src/data-retention/data-retention.service.spec.ts b/apps/backend/src/data-retention/data-retention.service.spec.ts index 17151bd1..cf74d9ad 100644 --- a/apps/backend/src/data-retention/data-retention.service.spec.ts +++ b/apps/backend/src/data-retention/data-retention.service.spec.ts @@ -6,8 +6,8 @@ import type { IConfig } from "../config/app.config.js"; import type { DataRetentionBaseline } from "../database/entities/data-retention-baseline.entity.js"; import { StorageProvider } from "../database/entities/storage-provider.entity.js"; import { buildCheckMetricLabels } from "../metrics-prometheus/check-metric-labels.js"; -import type { PDPSubgraphService } from "../pdp-subgraph/pdp-subgraph.service.js"; -import type { ProviderDataSetResponse } from "../pdp-subgraph/types.js"; +import type { SubgraphService } from "../subgraph/subgraph.service.js"; +import type { ProviderDataSetResponse } from "../subgraph/types.js"; import type { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; import { DataRetentionService } from "./data-retention.service.js"; @@ -35,7 +35,7 @@ describe("DataRetentionService", () => { let walletSdkServiceMock: { getTestingProviders: ReturnType; }; - let pdpSubgraphServiceMock: { + let subgraphServiceMock: { fetchSubgraphMeta: ReturnType; fetchProvidersWithDatasets: ReturnType; }; @@ -62,7 +62,7 @@ describe("DataRetentionService", () => { configServiceMock = { get: vi.fn((key: keyof IConfig) => { if (key === "blockchain") { - return { pdpSubgraphEndpoint: "https://example.com/subgraph" }; + return { subgraphEndpoint: "https://example.com/subgraph" }; } if (key === "spBlocklists") { return { ids: new Set(), addresses: new Set() }; @@ -88,7 +88,7 @@ describe("DataRetentionService", () => { ]), }; - pdpSubgraphServiceMock = { + subgraphServiceMock = { fetchSubgraphMeta: vi.fn().mockResolvedValue({ _meta: { block: { @@ -124,7 +124,7 @@ describe("DataRetentionService", () => { service = new DataRetentionService( configServiceMock, walletSdkServiceMock as unknown as WalletSdkService, - pdpSubgraphServiceMock as unknown as PDPSubgraphService, + subgraphServiceMock as unknown as SubgraphService, mockBaselineRepository as unknown as Repository, mockSPRepository as unknown as Repository, counterMock as unknown as Counter, @@ -132,15 +132,15 @@ describe("DataRetentionService", () => { ); }); - it("returns early when pdpSubgraphEndpoint is empty", async () => { + it("returns early when subgraphEndpoint is empty", async () => { (configServiceMock.get as ReturnType).mockReturnValue({ - pdpSubgraphEndpoint: "", + subgraphEndpoint: "", }); await service.pollDataRetention(); - expect(pdpSubgraphServiceMock.fetchSubgraphMeta).not.toHaveBeenCalled(); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchSubgraphMeta).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); }); it("returns early when no testing providers configured", async () => { @@ -148,31 +148,31 @@ describe("DataRetentionService", () => { await service.pollDataRetention(); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); }); it("returns early when all providers are blocked for data-retention", async () => { (configServiceMock.get as ReturnType).mockImplementation((key: string) => { - if (key === "blockchain") return { pdpSubgraphEndpoint: "https://example.com/subgraph" }; + if (key === "blockchain") return { subgraphEndpoint: "https://example.com/subgraph" }; if (key === "spBlocklists") return { ids: new Set(), addresses: new Set([PROVIDER_A, PROVIDER_B]) }; }); await service.pollDataRetention(); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); }); it("excludes blocked providers from data-retention polling while retaining unblocked ones", async () => { (configServiceMock.get as ReturnType).mockImplementation((key: string) => { - if (key === "blockchain") return { pdpSubgraphEndpoint: "https://example.com/subgraph" }; + if (key === "blockchain") return { subgraphEndpoint: "https://example.com/subgraph" }; if (key === "spBlocklists") return { ids: new Set(), addresses: new Set([PROVIDER_A]) }; }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); const allAddressesPolled: string[] = ( - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mock.calls as [{ addresses: string[] }][] + subgraphServiceMock.fetchProvidersWithDatasets.mock.calls as [{ addresses: string[] }][] ).flatMap(([{ addresses }]) => addresses); expect(allAddressesPolled).toContain(PROVIDER_B.toLowerCase()); expect(allAddressesPolled).not.toContain(PROVIDER_A.toLowerCase()); @@ -183,16 +183,16 @@ describe("DataRetentionService", () => { await service.pollDataRetention(); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchProvidersWithDatasets).not.toHaveBeenCalled(); }); it("sets baseline on first poll without emitting counters (fresh deploy / new provider)", async () => { - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); - expect(pdpSubgraphServiceMock.fetchSubgraphMeta).toHaveBeenCalled(); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledWith({ + expect(subgraphServiceMock.fetchSubgraphMeta).toHaveBeenCalled(); + expect(subgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledWith({ blockNumber: 1200, addresses: [PROVIDER_A, PROVIDER_B], }); @@ -216,20 +216,20 @@ describe("DataRetentionService", () => { it("computes deltas correctly on consecutive polls", async () => { // First poll: blockNumber=1200 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); const firstCallCount = counterMock.labels.mock.calls.length; // Second poll: blockNumber=1300, provider totals changed - pdpSubgraphServiceMock.fetchSubgraphMeta.mockResolvedValueOnce({ + subgraphServiceMock.fetchSubgraphMeta.mockResolvedValueOnce({ _meta: { block: { number: 1300, }, }, }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 12n, totalProvingPeriods: 105n, @@ -243,7 +243,7 @@ describe("DataRetentionService", () => { }); it("does not increment counters when deltas are zero", async () => { - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); // First poll await service.pollDataRetention(); @@ -265,7 +265,7 @@ describe("DataRetentionService", () => { const providerA = makeProvider({ address: PROVIDER_A, totalFaultedPeriods: 5n }); const providerB = makeProvider({ address: PROVIDER_B, totalFaultedPeriods: 20n }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([providerA, providerB]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([providerA, providerB]); await service.pollDataRetention(); @@ -287,7 +287,7 @@ describe("DataRetentionService", () => { ]); const provider = makeProvider(); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); await service.pollDataRetention(); @@ -310,7 +310,7 @@ describe("DataRetentionService", () => { }); it("handles empty providers array without errors", async () => { - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([]); await service.pollDataRetention(); @@ -324,7 +324,7 @@ describe("DataRetentionService", () => { ]); const provider = makeProvider(); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); await service.pollDataRetention(); @@ -347,7 +347,7 @@ describe("DataRetentionService", () => { }); it("catches and logs errors without rethrowing", async () => { - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockRejectedValueOnce(new Error("subgraph down")); + subgraphServiceMock.fetchProvidersWithDatasets.mockRejectedValueOnce(new Error("subgraph down")); // Should not throw await expect(service.pollDataRetention()).resolves.toBeUndefined(); @@ -355,14 +355,14 @@ describe("DataRetentionService", () => { it("resets baseline on negative deltas without incrementing counters", async () => { // First poll: high values - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 100n, totalProvingPeriods: 200n }), ]); await service.pollDataRetention(); counterMock.labels.mockClear(); // Second poll: lower values (e.g., chain reorg or subgraph correction) - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 50n, totalProvingPeriods: 100n }), ]); await service.pollDataRetention(); @@ -371,7 +371,7 @@ describe("DataRetentionService", () => { expect(counterMock.labels).not.toHaveBeenCalled(); // Third poll: values increase from new baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 52n, totalProvingPeriods: 105n }), ]); await service.pollDataRetention(); @@ -389,7 +389,7 @@ describe("DataRetentionService", () => { { providerAddress: PROVIDER_A, faultedPeriods: "0", successPeriods: "0", lastBlockNumber: "1000" }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: largeValue, totalProvingPeriods: largeValue * 2n }), ]); @@ -413,7 +413,7 @@ describe("DataRetentionService", () => { { providerAddress: PROVIDER_A, faultedPeriods: "0", successPeriods: "0", lastBlockNumber: "1000" }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: maxSafeInt, totalProvingPeriods: maxSafeInt * 2n }), ]); @@ -433,7 +433,7 @@ describe("DataRetentionService", () => { totalFaultedPeriods: 5n, totalProvingPeriods: 50n, }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([provider]); await service.pollDataRetention(); @@ -452,18 +452,18 @@ describe("DataRetentionService", () => { })); walletSdkServiceMock.getTestingProviders.mockReturnValueOnce(manyProviders); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([]); await service.pollDataRetention(); // Should be called twice: once for first 50, once for remaining 25 - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledTimes(2); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenNthCalledWith(1, { + expect(subgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledTimes(2); + expect(subgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenNthCalledWith(1, { addresses: expect.arrayContaining([expect.any(String)]), blockNumber: 1200, }); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets.mock.calls[0][0].addresses).toHaveLength(50); - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets.mock.calls[1][0].addresses).toHaveLength(25); + expect(subgraphServiceMock.fetchProvidersWithDatasets.mock.calls[0][0].addresses).toHaveLength(50); + expect(subgraphServiceMock.fetchProvidersWithDatasets.mock.calls[1][0].addresses).toHaveLength(25); }); it("continues processing next batch if one batch fails", async () => { @@ -476,20 +476,20 @@ describe("DataRetentionService", () => { walletSdkServiceMock.getTestingProviders.mockReturnValueOnce(manyProviders); // First batch fails, second succeeds - pdpSubgraphServiceMock.fetchProvidersWithDatasets + subgraphServiceMock.fetchProvidersWithDatasets .mockRejectedValueOnce(new Error("Subgraph timeout")) .mockResolvedValueOnce([]); await service.pollDataRetention(); // Both batches should be attempted - expect(pdpSubgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledTimes(2); + expect(subgraphServiceMock.fetchProvidersWithDatasets).toHaveBeenCalledTimes(2); }); it("logs error and skips counter update when provider not found in cache but returned from subgraph", async () => { // Provider C not in cache const PROVIDER_C = "0x1234567890123456789012345678901234567890"; - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_C })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_C })]); await service.pollDataRetention(); @@ -500,7 +500,7 @@ describe("DataRetentionService", () => { describe("cleanupStaleProviders", () => { it("does not cleanup when no stale providers exist", async () => { // First poll establishes baseline for both providers - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_A }), makeProvider({ address: PROVIDER_B }), ]); @@ -513,7 +513,7 @@ describe("DataRetentionService", () => { it("successfully cleans up stale provider with valid database entry", async () => { // First poll: establish baseline for PROVIDER_A - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: PROVIDER_A removed from active list, only PROVIDER_B active @@ -535,7 +535,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -566,7 +566,7 @@ describe("DataRetentionService", () => { it("skips cleanup entirely when database fetch fails", async () => { // First poll: establish baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: provider removed, but DB fails @@ -581,7 +581,7 @@ describe("DataRetentionService", () => { mockSPRepository.find.mockRejectedValueOnce(new Error("Database connection failed")); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -601,7 +601,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_A, totalFaultedPeriods: 12n, totalProvingPeriods: 105n }), ]); @@ -614,7 +614,7 @@ describe("DataRetentionService", () => { it("retains baseline when provider not found in database", async () => { // First poll: establish baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: provider removed from active list @@ -630,7 +630,7 @@ describe("DataRetentionService", () => { // Database returns empty array (provider not found) mockSPRepository.find.mockResolvedValueOnce([]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -647,7 +647,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_A, totalFaultedPeriods: 12n, totalProvingPeriods: 105n }), ]); @@ -660,7 +660,7 @@ describe("DataRetentionService", () => { it("retains baseline when provider has null providerId", async () => { // First poll: establish baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: provider removed @@ -683,7 +683,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -693,7 +693,7 @@ describe("DataRetentionService", () => { it("retains baseline when counter removal throws error", async () => { // First poll: establish baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: provider removed @@ -720,7 +720,7 @@ describe("DataRetentionService", () => { throw new Error("Counter removal failed"); }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -737,7 +737,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_A, totalFaultedPeriods: 12n, totalProvingPeriods: 110n }), ]); @@ -758,7 +758,7 @@ describe("DataRetentionService", () => { { id: 3, serviceProvider: PROVIDER_C, name: "Provider C", isApproved: true }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_A }), makeProvider({ address: PROVIDER_B }), makeProvider({ address: PROVIDER_C }), @@ -776,7 +776,7 @@ describe("DataRetentionService", () => { { address: PROVIDER_C, name: "Provider C", providerId: 3, isApproved: true }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); @@ -792,7 +792,7 @@ describe("DataRetentionService", () => { it("skips cleanup when processing errors occurred", async () => { // First poll: establish baseline - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: provider removed, but processing has errors @@ -801,7 +801,7 @@ describe("DataRetentionService", () => { ]); // Simulate processing error - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockRejectedValueOnce(new Error("Processing failed")); + subgraphServiceMock.fetchProvidersWithDatasets.mockRejectedValueOnce(new Error("Processing failed")); await service.pollDataRetention(); @@ -818,7 +818,7 @@ describe("DataRetentionService", () => { { id: 1, serviceProvider: PROVIDER_MIXED_CASE, name: "Provider A", isApproved: true }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ address: PROVIDER_MIXED_CASE.toLowerCase() as `0x${string}` }), ]); @@ -838,7 +838,7 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -862,7 +862,7 @@ describe("DataRetentionService", () => { // Subgraph returns same values: totalFaultedPeriods=10, totalProvingPeriods=100 // confirmedTotalSuccess = 100 - 10 = 90 // With DB baseline: faultedDelta = 10 - 10 = 0, successDelta = 90 - 90 = 0 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); @@ -884,7 +884,7 @@ describe("DataRetentionService", () => { // Subgraph returns: totalFaultedPeriods=10, totalProvingPeriods=100 // confirmedTotalSuccess = 100 - 10 = 90 // faultedDelta = 10 - 8 = 2, successDelta = 90 - 85 = 5 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); @@ -899,7 +899,7 @@ describe("DataRetentionService", () => { }); it("only loads baselines from DB once across multiple polls", async () => { - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); await service.pollDataRetention(); await service.pollDataRetention(); @@ -919,12 +919,12 @@ describe("DataRetentionService", () => { }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValue([makeProvider()]); // First poll: DB load fails, poll bails out to avoid emitting bloated values await service.pollDataRetention(); expect(mockBaselineRepository.find).toHaveBeenCalledTimes(1); - expect(pdpSubgraphServiceMock.fetchSubgraphMeta).not.toHaveBeenCalled(); + expect(subgraphServiceMock.fetchSubgraphMeta).not.toHaveBeenCalled(); expect(counterMock.labels).not.toHaveBeenCalled(); // Second poll: DB load succeeds, baselines restored, normal delta computation @@ -937,16 +937,16 @@ describe("DataRetentionService", () => { it("emits real deltas on second poll after fresh deploy baseline-only first poll", async () => { // First poll: fresh deploy, no baselines in DB // Baseline set to: faultedPeriods=10, successPeriods=90 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); counterMock.labels.mockClear(); counterMock.inc.mockClear(); // Second poll: values have increased - pdpSubgraphServiceMock.fetchSubgraphMeta.mockResolvedValueOnce({ + subgraphServiceMock.fetchSubgraphMeta.mockResolvedValueOnce({ _meta: { block: { number: 1300 } }, }); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 12n, totalProvingPeriods: 105n }), ]); @@ -960,7 +960,7 @@ describe("DataRetentionService", () => { it("deletes baseline from DB when stale provider is cleaned up", async () => { // First poll: establish baseline for PROVIDER_A - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: PROVIDER_A removed from active list @@ -972,7 +972,7 @@ describe("DataRetentionService", () => { { address: PROVIDER_A, name: "Provider A", providerId: 1, isApproved: true }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); @@ -985,7 +985,7 @@ describe("DataRetentionService", () => { it("emits overdue gauge on first poll (baseline-only)", async () => { // Provider is overdue: currentBlock=1200, // estimatedOverduePeriods = (1200 - 901) / 100 = 2.99 -> 2 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); @@ -1002,7 +1002,7 @@ describe("DataRetentionService", () => { it("emits overdue gauge = 0 when provider is not overdue", async () => { // nextDeadline=2000 > currentBlock=1200 - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ proofSets: [] })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ proofSets: [] })]); await service.pollDataRetention(); @@ -1011,7 +1011,7 @@ describe("DataRetentionService", () => { it("emits overdue gauge even on negative delta (baseline reset)", async () => { // First poll: high values - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 100n, totalProvingPeriods: 200n }), ]); await service.pollDataRetention(); @@ -1019,7 +1019,7 @@ describe("DataRetentionService", () => { gaugeMock.set.mockClear(); // Second poll: lower values (negative delta) but still overdue - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 50n, totalProvingPeriods: 100n }), ]); await service.pollDataRetention(); @@ -1031,7 +1031,7 @@ describe("DataRetentionService", () => { it("naturally resets gauge to 0 when subgraph catches up", async () => { // First poll: provider is overdue (currentBlock=1200, nextDeadline=1000) - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider()]); await service.pollDataRetention(); expect(gaugeMock.set).toHaveBeenCalledWith(2); @@ -1040,7 +1040,7 @@ describe("DataRetentionService", () => { gaugeMock.set.mockClear(); // Second poll: subgraph caught up, nextDeadline advanced past currentBlock - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([ makeProvider({ totalFaultedPeriods: 12n, totalProvingPeriods: 102n, @@ -1056,7 +1056,7 @@ describe("DataRetentionService", () => { it("removes overdue gauge when stale provider is cleaned up", async () => { // First poll: establish baseline for PROVIDER_A - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_A })]); await service.pollDataRetention(); // Second poll: PROVIDER_A removed from active list @@ -1068,7 +1068,7 @@ describe("DataRetentionService", () => { { address: PROVIDER_A, name: "Provider A", providerId: 1, isApproved: true }, ]); - pdpSubgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); + subgraphServiceMock.fetchProvidersWithDatasets.mockResolvedValueOnce([makeProvider({ address: PROVIDER_B })]); await service.pollDataRetention(); diff --git a/apps/backend/src/data-retention/data-retention.service.ts b/apps/backend/src/data-retention/data-retention.service.ts index f4d7ec6d..16f87d41 100644 --- a/apps/backend/src/data-retention/data-retention.service.ts +++ b/apps/backend/src/data-retention/data-retention.service.ts @@ -10,8 +10,8 @@ import { IConfig } from "../config/app.config.js"; import { DataRetentionBaseline } from "../database/entities/data-retention-baseline.entity.js"; import { StorageProvider } from "../database/entities/storage-provider.entity.js"; import { buildCheckMetricLabels, CheckMetricLabels } from "../metrics-prometheus/check-metric-labels.js"; -import { PDPSubgraphService } from "../pdp-subgraph/pdp-subgraph.service.js"; -import { type ProviderDataSetResponse } from "../pdp-subgraph/types.js"; +import { SubgraphService } from "../subgraph/subgraph.service.js"; +import { type ProviderDataSetResponse } from "../subgraph/types.js"; import { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; import { type PDPProviderEx } from "../wallet-sdk/wallet-sdk.types.js"; @@ -44,7 +44,7 @@ export class DataRetentionService { constructor( private readonly configService: ConfigService, private readonly walletSdkService: WalletSdkService, - private readonly pdpSubgraphService: PDPSubgraphService, + private readonly subgraphService: SubgraphService, @InjectRepository(DataRetentionBaseline) private readonly baselineRepository: Repository, @InjectRepository(StorageProvider) @@ -63,10 +63,10 @@ export class DataRetentionService { * challenge delta since the last poll. */ async pollDataRetention(): Promise { - const pdpSubgraphEndpoint = this.configService.get("blockchain").pdpSubgraphEndpoint; - if (!pdpSubgraphEndpoint) { + const subgraphEndpoint = this.configService.get("blockchain").subgraphEndpoint; + if (!subgraphEndpoint) { this.logger.warn({ - event: "pdp_subgraph_endpoint_not_configured", + event: "subgraph_endpoint_not_configured", message: "No PDP subgraph endpoint configured", }); return; @@ -80,7 +80,7 @@ export class DataRetentionService { } try { - const subgraphMeta = await this.pdpSubgraphService.fetchSubgraphMeta(); + const subgraphMeta = await this.subgraphService.fetchSubgraphMeta(); const allProviderInfos = this.walletSdkService.getTestingProviders(); const spBlocklists = this.configService.get("spBlocklists"); const providerInfos = allProviderInfos?.filter((p) => !isSpBlocked(spBlocklists, p.serviceProvider, p.id)); @@ -109,7 +109,7 @@ export class DataRetentionService { ); try { - const providersFromSubgraph = await this.pdpSubgraphService.fetchProvidersWithDatasets({ + const providersFromSubgraph = await this.subgraphService.fetchProvidersWithDatasets({ blockNumber, addresses: batchAddresses, }); diff --git a/apps/backend/src/database/database.module.ts b/apps/backend/src/database/database.module.ts index 9249c3a9..f3f9ed09 100644 --- a/apps/backend/src/database/database.module.ts +++ b/apps/backend/src/database/database.module.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from "url"; import { toStructuredError } from "../common/logging.js"; import { createPinoExitLogger } from "../common/pino.config.js"; import type { IAppConfig, IConfig, IDatabaseConfig } from "../config/app.config.js"; +import { AnonRetrieval } from "./entities/anon-retrieval.entity.js"; import { DataRetentionBaseline } from "./entities/data-retention-baseline.entity.js"; import { Deal } from "./entities/deal.entity.js"; import { JobScheduleState } from "./entities/job-schedule-state.entity.js"; @@ -49,7 +50,7 @@ function toSafeDataSourceContext(options: DataSourceOptions): Record { + await queryRunner.query(` + CREATE TABLE anon_retrievals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + sp_address VARCHAR NOT NULL, + piece_cid VARCHAR NOT NULL, + data_set_id BIGINT NOT NULL, + piece_id BIGINT NOT NULL, + raw_size BIGINT NOT NULL, + with_ipfs_indexing BOOLEAN NOT NULL, + ipfs_root_cid VARCHAR NULL, + service_type VARCHAR NOT NULL DEFAULT 'direct_sp', + retrieval_endpoint VARCHAR NOT NULL, + status VARCHAR NOT NULL DEFAULT 'pending', + started_at TIMESTAMPTZ NOT NULL, + completed_at TIMESTAMPTZ NULL, + latency_ms INT NULL, + ttfb_ms INT NULL, + throughput_bps INT NULL, + bytes_retrieved BIGINT NULL, + response_code INT NULL, + error_message VARCHAR NULL, + commp_valid BOOLEAN NULL, + car_valid BOOLEAN NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() + ) + `); + + // Per-SP dashboards. + await queryRunner.query(` + CREATE INDEX "IDX_anon_retrievals_sp_address" + ON anon_retrievals (sp_address) + `); + + // Used by the recent-dedup query in AnonPieceSelectorService — keeps the + // most-recently-tested CIDs out of the next selection. + await queryRunner.query(` + CREATE INDEX "IDX_anon_retrievals_piece_cid" + ON anon_retrievals (piece_cid) + `); + + // Supports "last N anonymous retrievals" ordering used by the selector. + await queryRunner.query(` + CREATE INDEX "IDX_anon_retrievals_created_at" + ON anon_retrievals (created_at DESC) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS anon_retrievals`); + } +} diff --git a/apps/backend/src/http-client/http-client.service.spec.ts b/apps/backend/src/http-client/http-client.service.spec.ts index 96604139..511910ba 100644 --- a/apps/backend/src/http-client/http-client.service.spec.ts +++ b/apps/backend/src/http-client/http-client.service.spec.ts @@ -64,25 +64,94 @@ describe("HttpClientService", () => { expect(config.timeout).toBe(120000); }); - it("times out HTTP/2 requests using the connection timeout", async () => { + it("passes the configured headersTimeout to undici and translates its error", async () => { const service = await createService(); - if (typeof AbortSignal.timeout !== "function") { - (AbortSignal as any).timeout = () => new AbortController().signal; + let receivedHeadersTimeout: number | undefined; + undiciRequestMock.mockImplementationOnce((_url: string, options: { headersTimeout?: number }) => { + receivedHeadersTimeout = options.headersTimeout; + const err = new Error("Headers Timeout Error") as Error & { code?: string }; + err.name = "HeadersTimeoutError"; + err.code = "UND_ERR_HEADERS_TIMEOUT"; + return Promise.reject(err); + }); + + await expect(service.requestWithMetrics("http://example.com", { httpVersion: "2" })).rejects.toThrow( + "HTTP/2 connection/headers timed out after 25ms", + ); + + expect(receivedHeadersTimeout).toBe(25); + }); + + it("keeps the request signal alive after the connect timeout window elapses", async () => { + const service = await createService(); + + // Previously, connectTimeoutMs (25ms) was folded into the request signal, + // so any download lasting longer than 25ms was aborted mid-stream. The + // signal must now stay live until the transfer timeout or parent signal + // fires. + let sawAbortBeforeResolve = false; + undiciRequestMock.mockImplementationOnce(async (_url: string, options: { signal?: AbortSignal }) => { + await new Promise((r) => setTimeout(r, 75)); + sawAbortBeforeResolve = options.signal?.aborted === true; + async function* body() { + yield Buffer.from("ok"); + } + return { statusCode: 200, body: body() }; + }); + + const result = await service.requestWithMetrics("http://example.com", { httpVersion: "2" }); + + expect(sawAbortBeforeResolve).toBe(false); + expect(result.aborted).toBeUndefined(); + expect(result.metrics.statusCode).toBe(200); + }); + + it("returns partial bytes and metrics when HTTP/2 download is aborted after headers", async () => { + const service = await createService(); + + const parentAbort = new AbortController(); + + async function* abortingBody() { + yield Buffer.from("hello"); + yield Buffer.from(" world"); + // Simulate an abort mid-stream after two chunks. + parentAbort.abort(new Error("Anon retrieval job timeout (60s) for sp1")); + throw new Error("aborted"); } - undiciRequestMock.mockImplementationOnce((_url: string, options: { signal?: AbortSignal }) => { - return new Promise((_resolve, reject) => { - options.signal?.addEventListener("abort", () => reject(new Error("aborted")), { once: true }); - }); + undiciRequestMock.mockImplementationOnce(async () => ({ + statusCode: 200, + body: abortingBody(), + })); + + const result = await service.requestWithMetrics("http://example.com/piece", { + httpVersion: "2", + signal: parentAbort.signal, }); - vi.useFakeTimers(); + expect(result.aborted).toBe(true); + expect(result.abortReason).toContain("timeout"); + expect(result.metrics.statusCode).toBe(200); + expect(result.metrics.responseSize).toBe(11); + expect(Buffer.isBuffer(result.data) ? result.data.toString() : "").toBe("hello world"); + }); + + it("rethrows non-abort download errors on HTTP/2", async () => { + const service = await createService(); - const promise = service.requestWithMetrics("http://example.com", { httpVersion: "2" }); - const assertion = expect(promise).rejects.toThrow("HTTP/2 connection/headers timed out after 25ms"); - await vi.advanceTimersByTimeAsync(25); + async function* brokenBody() { + yield Buffer.from("partial"); + throw new Error("network reset"); + } + + undiciRequestMock.mockImplementationOnce(async () => ({ + statusCode: 200, + body: brokenBody(), + })); - await assertion; + await expect(service.requestWithMetrics("http://example.com/piece", { httpVersion: "2" })).rejects.toThrow( + "network reset", + ); }); }); diff --git a/apps/backend/src/http-client/http-client.service.ts b/apps/backend/src/http-client/http-client.service.ts index 48e10e5c..81140162 100644 --- a/apps/backend/src/http-client/http-client.service.ts +++ b/apps/backend/src/http-client/http-client.service.ts @@ -81,12 +81,11 @@ export class HttpClientService { let ttfbTime = 0; let statusCode = 0; - /** - * Dual-timeout strategy for HTTP/2 requests: - * 1. AbortSignal.timeout() - Undici's native timeout (10 min default) - * 2. AbortSignal.timeout() for connection/headers (10 sec default) - */ - const { signal, connectTimeoutSignal } = this.buildHttp2Signals(options.signal); + // Dual-timeout strategy for HTTP/2 requests: + // - `headersTimeout` (undici): scopes the connect + response-headers phase. + // - Combined AbortSignal: transfer-timeout ceiling + parent (job) signal. + const transferTimeoutSignal = AbortSignal.timeout(this.http2TimeoutMs); + const signal = options.signal ? anySignal([transferTimeoutSignal, options.signal]) : transferTimeoutSignal; const requestOptions: any = { method, headers: { @@ -94,6 +93,7 @@ export class HttpClientService { ...headers, }, signal, + headersTimeout: this.connectTimeoutMs, }; if (data) { @@ -105,7 +105,8 @@ export class HttpClientService { try { response = await undiciRequest(url, requestOptions); } catch (error) { - if (connectTimeoutSignal.aborted) { + // discern connection error from transfer error + if (isHeadersTimeoutError(error)) { throw new Error(`HTTP/2 connection/headers timed out after ${this.connectTimeoutMs}ms`); } throw error; @@ -115,8 +116,15 @@ export class HttpClientService { statusCode = response.statusCode; const chunks: Buffer[] = []; - for await (const chunk of response.body) { - chunks.push(Buffer.from(chunk)); + let downloadError: unknown; + try { + for await (const chunk of response.body) { + chunks.push(Buffer.from(chunk)); + } + } catch (error) { + // Download-phase failures (e.g. abort signal) fall through so we can + // return the partial buffer + metrics collected so far. + downloadError = error; } const dataBuffer = Buffer.concat(chunks); @@ -133,6 +141,29 @@ export class HttpClientService { httpVersion: "2", }; + if (downloadError !== undefined) { + const aborted = options.signal?.aborted === true || isAbortLikeError(downloadError); + if (!aborted) { + throw downloadError; + } + const abortReason = describeAbortReason(options.signal, downloadError); + this.logger.warn({ + event: "http2_download_aborted", + message: "HTTP/2 download aborted after headers; returning partial data", + url, + bytesReceived: dataBuffer.length, + totalTime: metrics.totalTime, + ttfb: metrics.ttfb, + abortReason, + }); + return { + data: dataBuffer as T, + metrics, + aborted: true, + abortReason, + }; + } + return { data: dataBuffer as T, metrics, @@ -255,24 +286,28 @@ export class HttpClientService { // Fallback for objects/arrays return Buffer.from(JSON.stringify(data)); } +} - private buildHttp2Signals(parentSignal?: AbortSignal): { - signal: AbortSignal; - connectTimeoutSignal: AbortSignal; - } { - const transferTimeoutSignal = AbortSignal.timeout(this.http2TimeoutMs); - const connectTimeoutSignal = AbortSignal.timeout(this.connectTimeoutMs); +function isAbortLikeError(error: unknown): boolean { + if (error instanceof Error) { + return error.name === "AbortError" || error.name === "TimeoutError" || /abort/i.test(error.message); + } + return false; +} - if (parentSignal) { - return { - signal: anySignal([transferTimeoutSignal, connectTimeoutSignal, parentSignal]), - connectTimeoutSignal, - }; - } +/** + * Determines if a given error represents a "Headers Timeout" error. + */ +function isHeadersTimeoutError(error: unknown): boolean { + if (!(error instanceof Error)) return false; + const code = (error as Error & { code?: string }).code; + return error.name === "HeadersTimeoutError" || code === "UND_ERR_HEADERS_TIMEOUT"; +} - return { - signal: anySignal([transferTimeoutSignal, connectTimeoutSignal]), - connectTimeoutSignal, - }; - } +function describeAbortReason(signal: AbortSignal | undefined, fallback: unknown): string { + const reason = signal?.reason; + if (reason instanceof Error && reason.message) return reason.message; + if (typeof reason === "string" && reason.length > 0) return reason; + if (fallback instanceof Error && fallback.message) return fallback.message; + return "aborted"; } diff --git a/apps/backend/src/http-client/types.ts b/apps/backend/src/http-client/types.ts index 7e48ce7d..26892ee6 100644 --- a/apps/backend/src/http-client/types.ts +++ b/apps/backend/src/http-client/types.ts @@ -13,4 +13,6 @@ export interface RequestMetrics { export interface RequestWithMetrics { data: T; metrics: RequestMetrics; + aborted?: boolean; // Set when the request was aborted mid-download after response headers arrived. + abortReason?: string; // Error message when `aborted` is true; human-readable summary of the abort reason. } diff --git a/apps/backend/src/jobs/job-queues.ts b/apps/backend/src/jobs/job-queues.ts index 9488ce7b..db475d49 100644 --- a/apps/backend/src/jobs/job-queues.ts +++ b/apps/backend/src/jobs/job-queues.ts @@ -7,3 +7,4 @@ export const LEGACY_DEAL_QUEUE = "deal.run"; export const LEGACY_RETRIEVAL_QUEUE = "retrieval.run"; export const DATA_RETENTION_POLL_QUEUE = "data.retention.poll"; export const PROVIDERS_REFRESH_QUEUE = "providers.refresh"; +export const RETRIEVAL_ANON_QUEUE = "retrieval.anon.run"; diff --git a/apps/backend/src/jobs/jobs.module.ts b/apps/backend/src/jobs/jobs.module.ts index 15ad4d64..69f1edb1 100644 --- a/apps/backend/src/jobs/jobs.module.ts +++ b/apps/backend/src/jobs/jobs.module.ts @@ -7,6 +7,7 @@ import { StorageProvider } from "../database/entities/storage-provider.entity.js import { DealModule } from "../deal/deal.module.js"; import { PieceCleanupModule } from "../piece-cleanup/piece-cleanup.module.js"; import { RetrievalModule } from "../retrieval/retrieval.module.js"; +import { RetrievalAnonModule } from "../retrieval-anon/retrieval-anon.module.js"; import { WalletSdkModule } from "../wallet-sdk/wallet-sdk.module.js"; import { JobsService } from "./jobs.service.js"; import { JobScheduleRepository } from "./repositories/job-schedule.repository.js"; @@ -17,6 +18,7 @@ import { JobScheduleRepository } from "./repositories/job-schedule.repository.js TypeOrmModule.forFeature([StorageProvider, JobScheduleState]), DealModule, RetrievalModule, + RetrievalAnonModule, WalletSdkModule, DataRetentionModule, PieceCleanupModule, diff --git a/apps/backend/src/jobs/jobs.service.spec.ts b/apps/backend/src/jobs/jobs.service.spec.ts index 5b8c58bc..aaa1c41f 100644 --- a/apps/backend/src/jobs/jobs.service.spec.ts +++ b/apps/backend/src/jobs/jobs.service.spec.ts @@ -29,18 +29,18 @@ describe("JobsService schedule rows", () => { }; let dataRetentionServiceMock: { pollDataRetention: ReturnType }; let metricsMocks: { - jobsQueuedGauge: JobsServiceDeps[8]; - jobsRetryScheduledGauge: JobsServiceDeps[9]; - oldestQueuedAgeGauge: JobsServiceDeps[10]; - oldestInFlightAgeGauge: JobsServiceDeps[11]; - jobsInFlightGauge: JobsServiceDeps[12]; - jobsEnqueueAttemptsCounter: JobsServiceDeps[13]; - jobsStartedCounter: JobsServiceDeps[14]; - jobsCompletedCounter: JobsServiceDeps[15]; - jobsPausedGauge: JobsServiceDeps[16]; - jobDuration: JobsServiceDeps[17]; - storageProvidersActive: JobsServiceDeps[18]; - storageProvidersTested: JobsServiceDeps[19]; + jobsQueuedGauge: JobsServiceDeps[9]; + jobsRetryScheduledGauge: JobsServiceDeps[10]; + oldestQueuedAgeGauge: JobsServiceDeps[11]; + oldestInFlightAgeGauge: JobsServiceDeps[12]; + jobsInFlightGauge: JobsServiceDeps[13]; + jobsEnqueueAttemptsCounter: JobsServiceDeps[14]; + jobsStartedCounter: JobsServiceDeps[15]; + jobsCompletedCounter: JobsServiceDeps[16]; + jobsPausedGauge: JobsServiceDeps[17]; + jobDuration: JobsServiceDeps[18]; + storageProvidersActive: JobsServiceDeps[19]; + storageProvidersTested: JobsServiceDeps[20]; }; let baseConfigValues: Partial; let configService: JobsServiceDeps[0]; @@ -51,21 +51,22 @@ describe("JobsService schedule rows", () => { jobScheduleRepository: JobsServiceDeps[2]; dealService: JobsServiceDeps[3]; retrievalService: JobsServiceDeps[4]; - walletSdkService: JobsServiceDeps[5]; - dataRetentionService: JobsServiceDeps[6]; - pieceCleanupService: JobsServiceDeps[7]; - jobsQueuedGauge: JobsServiceDeps[8]; - jobsRetryScheduledGauge: JobsServiceDeps[9]; - oldestQueuedAgeGauge: JobsServiceDeps[10]; - oldestInFlightAgeGauge: JobsServiceDeps[11]; - jobsInFlightGauge: JobsServiceDeps[12]; - jobsEnqueueAttemptsCounter: JobsServiceDeps[13]; - jobsStartedCounter: JobsServiceDeps[14]; - jobsCompletedCounter: JobsServiceDeps[15]; - jobsPausedGauge: JobsServiceDeps[16]; - jobDuration: JobsServiceDeps[17]; - storageProvidersActive: JobsServiceDeps[18]; - storageProvidersTested: JobsServiceDeps[19]; + anonRetrievalService: JobsServiceDeps[5]; + walletSdkService: JobsServiceDeps[6]; + dataRetentionService: JobsServiceDeps[7]; + pieceCleanupService: JobsServiceDeps[8]; + jobsQueuedGauge: JobsServiceDeps[9]; + jobsRetryScheduledGauge: JobsServiceDeps[10]; + oldestQueuedAgeGauge: JobsServiceDeps[11]; + oldestInFlightAgeGauge: JobsServiceDeps[12]; + jobsInFlightGauge: JobsServiceDeps[13]; + jobsEnqueueAttemptsCounter: JobsServiceDeps[14]; + jobsStartedCounter: JobsServiceDeps[15]; + jobsCompletedCounter: JobsServiceDeps[16]; + jobsPausedGauge: JobsServiceDeps[17]; + jobDuration: JobsServiceDeps[18]; + storageProvidersActive: JobsServiceDeps[19]; + storageProvidersTested: JobsServiceDeps[20]; }>, ) => JobsService; @@ -95,18 +96,18 @@ describe("JobsService schedule rows", () => { }; metricsMocks = { - jobsQueuedGauge: { set: vi.fn() } as unknown as JobsServiceDeps[8], - jobsRetryScheduledGauge: { set: vi.fn() } as unknown as JobsServiceDeps[9], - oldestQueuedAgeGauge: { set: vi.fn() } as unknown as JobsServiceDeps[10], - oldestInFlightAgeGauge: { set: vi.fn() } as unknown as JobsServiceDeps[11], - jobsInFlightGauge: { set: vi.fn() } as unknown as JobsServiceDeps[12], - jobsEnqueueAttemptsCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[13], - jobsStartedCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[14], - jobsCompletedCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[15], - jobsPausedGauge: { set: vi.fn() } as unknown as JobsServiceDeps[16], - jobDuration: { observe: vi.fn() } as unknown as JobsServiceDeps[17], - storageProvidersActive: { set: vi.fn() } as unknown as JobsServiceDeps[18], - storageProvidersTested: { set: vi.fn() } as unknown as JobsServiceDeps[19], + jobsQueuedGauge: { set: vi.fn() } as unknown as JobsServiceDeps[9], + jobsRetryScheduledGauge: { set: vi.fn() } as unknown as JobsServiceDeps[10], + oldestQueuedAgeGauge: { set: vi.fn() } as unknown as JobsServiceDeps[11], + oldestInFlightAgeGauge: { set: vi.fn() } as unknown as JobsServiceDeps[12], + jobsInFlightGauge: { set: vi.fn() } as unknown as JobsServiceDeps[13], + jobsEnqueueAttemptsCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[14], + jobsStartedCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[15], + jobsCompletedCounter: { inc: vi.fn() } as unknown as JobsServiceDeps[16], + jobsPausedGauge: { set: vi.fn() } as unknown as JobsServiceDeps[17], + jobDuration: { observe: vi.fn() } as unknown as JobsServiceDeps[18], + storageProvidersActive: { set: vi.fn() } as unknown as JobsServiceDeps[19], + storageProvidersTested: { set: vi.fn() } as unknown as JobsServiceDeps[20], }; const emptySpBlocklists: ISpBlocklistConfig = { @@ -132,6 +133,7 @@ describe("JobsService schedule rows", () => { dataSetCreationJobTimeoutSeconds: 300, pieceCleanupPerSpPerHour: 1, maxPieceCleanupRuntimeSeconds: 300, + retrievalsAnonPerSpPerHour: 2, } as IConfig["jobs"], database: { host: "localhost", @@ -157,9 +159,10 @@ describe("JobsService schedule rows", () => { overrides.jobScheduleRepository ?? (jobScheduleRepositoryMock as unknown as JobsServiceDeps[2]), overrides.dealService ?? ({} as JobsServiceDeps[3]), overrides.retrievalService ?? ({} as JobsServiceDeps[4]), - overrides.walletSdkService ?? ({} as JobsServiceDeps[5]), - overrides.dataRetentionService ?? (dataRetentionServiceMock as unknown as JobsServiceDeps[6]), - overrides.pieceCleanupService ?? ({} as JobsServiceDeps[7]), + overrides.anonRetrievalService ?? ({} as JobsServiceDeps[5]), + overrides.walletSdkService ?? ({} as JobsServiceDeps[6]), + overrides.dataRetentionService ?? (dataRetentionServiceMock as unknown as JobsServiceDeps[7]), + overrides.pieceCleanupService ?? ({} as JobsServiceDeps[8]), overrides.jobsQueuedGauge ?? metricsMocks.jobsQueuedGauge, overrides.jobsRetryScheduledGauge ?? metricsMocks.jobsRetryScheduledGauge, overrides.oldestQueuedAgeGauge ?? metricsMocks.oldestQueuedAgeGauge, @@ -283,7 +286,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); // Trigger the timeout immediately by using fake timers @@ -342,7 +345,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, retrievalService: retrievalService as unknown as ConstructorParameters[4], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); vi.useFakeTimers(); @@ -381,7 +384,7 @@ describe("JobsService schedule rows", () => { service = buildService({ retrievalService: retrievalService as unknown as ConstructorParameters[4], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleRetrievalJob", { @@ -421,7 +424,7 @@ describe("JobsService schedule rows", () => { service = buildService({ retrievalService: retrievalService as unknown as ConstructorParameters[4], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await expect( @@ -614,12 +617,13 @@ describe("JobsService schedule rows", () => { // Check upserts for providerB const upsertCalls = jobScheduleRepositoryMock.upsertSchedule.mock.calls; const upsertsForB = upsertCalls.filter((call) => call[1] === providerB.address); - expect(upsertsForB).toHaveLength(4); + expect(upsertsForB).toHaveLength(5); expect(upsertsForB.map((call) => call[0]).sort()).toEqual([ "data_set_creation", "deal", "piece_cleanup", "retrieval", + "retrieval_anon", ]); }); @@ -925,7 +929,7 @@ describe("JobsService schedule rows", () => { service = buildService({ dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDealJob", { @@ -964,8 +968,8 @@ describe("JobsService schedule rows", () => { service = buildService({ dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], - pieceCleanupService: pieceCleanupService as unknown as JobsServiceDeps[7], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], + pieceCleanupService: pieceCleanupService as unknown as JobsServiceDeps[8], }); await callPrivate(service, "handleDealJob", { @@ -1006,7 +1010,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDealJob", { @@ -1059,7 +1063,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDealJob", { @@ -1112,7 +1116,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDealJob", { @@ -1167,7 +1171,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDealJob", { @@ -1197,7 +1201,7 @@ describe("JobsService schedule rows", () => { service = buildService({ dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDataSetCreationJob", { @@ -1238,7 +1242,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDataSetCreationJob", { @@ -1278,7 +1282,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDataSetCreationJob", { @@ -1320,7 +1324,7 @@ describe("JobsService schedule rows", () => { service = buildService({ configService, dealService: dealService as unknown as ConstructorParameters[3], - walletSdkService: walletSdkService as unknown as ConstructorParameters[5], + walletSdkService: walletSdkService as unknown as ConstructorParameters[6], }); await callPrivate(service, "handleDataSetCreationJob", { @@ -1468,7 +1472,7 @@ describe("JobsService schedule rows", () => { service = buildService({ dealService: dealService as unknown as JobsServiceDeps[3], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }); await callPrivate(service, "handleDealJob", { @@ -1492,7 +1496,7 @@ describe("JobsService schedule rows", () => { service = buildService({ retrievalService: retrievalService as unknown as JobsServiceDeps[4], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }); await callPrivate(service, "handleRetrievalJob", { @@ -1520,7 +1524,7 @@ describe("JobsService schedule rows", () => { service = buildService({ dealService: dealService as unknown as JobsServiceDeps[3], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }); await callPrivate(service, "handleDataSetCreationJob", { @@ -1561,7 +1565,7 @@ describe("JobsService schedule rows", () => { intervalSeconds: 60, service: buildService({ dealService: dealService as unknown as JobsServiceDeps[3], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }), expectCheckNotRun: () => expect(dealService.createDealForProvider).not.toHaveBeenCalled(), }, @@ -1571,7 +1575,7 @@ describe("JobsService schedule rows", () => { intervalSeconds: 60, service: buildService({ retrievalService: retrievalService as unknown as JobsServiceDeps[4], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }), expectCheckNotRun: () => expect(retrievalService.performRandomRetrievalForProvider).not.toHaveBeenCalled(), }, @@ -1581,7 +1585,7 @@ describe("JobsService schedule rows", () => { intervalSeconds: 3600, service: buildService({ dealService: dataSetDealService as unknown as JobsServiceDeps[3], - walletSdkService: walletSdkService as unknown as JobsServiceDeps[5], + walletSdkService: walletSdkService as unknown as JobsServiceDeps[6], }), expectCheckNotRun: () => expect(dataSetDealService.createDataSetWithPiece).not.toHaveBeenCalled(), }, diff --git a/apps/backend/src/jobs/jobs.service.ts b/apps/backend/src/jobs/jobs.service.ts index 01357225..d1316074 100644 --- a/apps/backend/src/jobs/jobs.service.ts +++ b/apps/backend/src/jobs/jobs.service.ts @@ -15,18 +15,32 @@ import { StorageProvider } from "../database/entities/storage-provider.entity.js import { DealService } from "../deal/deal.service.js"; import { PieceCleanupService } from "../piece-cleanup/piece-cleanup.service.js"; import { RetrievalService } from "../retrieval/retrieval.service.js"; +import { AnonRetrievalService } from "../retrieval-anon/anon-retrieval.service.js"; import { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; import { provisionNextMissingDataSet } from "./data-set-creation.handler.js"; -import { DATA_RETENTION_POLL_QUEUE, PROVIDERS_REFRESH_QUEUE, SP_WORK_QUEUE } from "./job-queues.js"; +import { + DATA_RETENTION_POLL_QUEUE, + PROVIDERS_REFRESH_QUEUE, + RETRIEVAL_ANON_QUEUE, + SP_WORK_QUEUE, +} from "./job-queues.js"; import { JobScheduleRepository } from "./repositories/job-schedule.repository.js"; -type SpJobType = "deal" | "retrieval" | "data_set_creation" | "piece_cleanup"; -const SP_JOB_TYPES: ReadonlySet = new Set(["deal", "retrieval", "data_set_creation", "piece_cleanup"]); +type SpJobType = "deal" | "retrieval" | "data_set_creation" | "retrieval_anon" | "piece_cleanup"; +const SP_JOB_TYPES: ReadonlySet = new Set([ + "deal", + "retrieval", + "retrieval_anon", + "data_set_creation", + "piece_cleanup", +]); + function isSpJobType(jobType: string): jobType is SpJobType { return SP_JOB_TYPES.has(jobType); } type SpJobData = { jobType: SpJobType; spAddress: string; intervalSeconds: number }; +type AnonRetrievalJobData = { spAddress: string; intervalSeconds: number }; type ProvidersRefreshJobData = { intervalSeconds: number }; type SpJob = Job; type DataRetentionJobData = { intervalSeconds: number }; @@ -57,6 +71,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { private readonly jobScheduleRepository: JobScheduleRepository, private readonly dealService: DealService, private readonly retrievalService: RetrievalService, + private readonly anonRetrievalService: AnonRetrievalService, private readonly walletSdkService: WalletSdkService, private readonly dataRetentionService: DataRetentionService, private readonly pieceCleanupService: PieceCleanupService, @@ -257,6 +272,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { await boss.createQueue(SP_WORK_QUEUE, { policy: "singleton" }); await boss.createQueue(PROVIDERS_REFRESH_QUEUE); await boss.createQueue(DATA_RETENTION_POLL_QUEUE); + await boss.createQueue(RETRIEVAL_ANON_QUEUE); } private registerWorkers(): void { @@ -334,6 +350,23 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { error: toStructuredError(error), }), ); + void this.boss + .work( + RETRIEVAL_ANON_QUEUE, + { batchSize: 1, localConcurrency: spConcurrency, pollingIntervalSeconds: workerPollSeconds }, + async ([job]) => { + if (!job) return; + await this.handleAnonRetrievalJob(job); + }, + ) + .catch((error) => + this.logger.error({ + event: "worker_register_failed", + message: "Failed to register worker", + queue: RETRIEVAL_ANON_QUEUE, + error: toStructuredError(error), + }), + ); } private getMaintenanceWindowStatus(now: Date = new Date()) { @@ -621,6 +654,51 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { }); } + private async handleAnonRetrievalJob(job: Job): Promise { + const data = job.data; + const spAddress = data.spAddress; + + // Create AbortController for job timeout enforcement + const abortController = new AbortController(); + const timeoutSeconds = this.configService.get("jobs").anonRetrievalJobTimeoutSeconds; + const timeoutMs = Math.max(60000, timeoutSeconds * 1000); + const effectiveTimeoutSeconds = Math.round(timeoutMs / 1000); + const abortReason = new Error(`Anon retrieval job timeout (${effectiveTimeoutSeconds}s) for ${spAddress}`); + const timeoutId = setTimeout(() => { + abortController.abort(abortReason); + }, timeoutMs); + + await this.recordJobExecution("retrieval_anon", async () => { + const logContext = await this.resolveProviderJobContext(spAddress, job.id); + try { + await this.anonRetrievalService.performForProvider(spAddress, abortController.signal, logContext); + return "success"; + } catch (error) { + if (abortController.signal.aborted) { + const reason = abortController.signal.reason; + const reasonMessage = reason instanceof Error ? reason.message : String(reason ?? ""); + this.logger.error({ + ...logContext, + event: "anon_retrieval_job_aborted", + message: reasonMessage || "Anon retrieval job aborted after timeout", + timeoutSeconds: effectiveTimeoutSeconds, + error: toStructuredError(reason ?? error), + }); + return "aborted"; + } + this.logger.error({ + ...logContext, + event: "anon_retrieval_job_failed", + message: "Anon retrieval job failed", + error: toStructuredError(error), + }); + throw error; + } finally { + clearTimeout(timeoutId); + } + }); + } + private async handleDataRetentionJob(data: DataRetentionJobData): Promise { void data; await this.recordJobExecution("data_retention_poll", async () => { @@ -899,6 +977,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { private getIntervalSecondsForRates(): { dealIntervalSeconds: number; retrievalIntervalSeconds: number; + retrievalAnonIntervalSeconds: number; dataSetCreationIntervalSeconds: number; dataRetentionPollIntervalSeconds: number; providersRefreshIntervalSeconds: number; @@ -919,9 +998,13 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { const dataRetentionPollIntervalSeconds = scheduling.dataRetentionPollIntervalSeconds; const providersRefreshIntervalSeconds = scheduling.providersRefreshIntervalSeconds; + const retrievalsAnonPerHour = jobsConfig.retrievalsAnonPerSpPerHour; + const retrievalAnonIntervalSeconds = Math.max(1, Math.round(3600 / retrievalsAnonPerHour)); + return { dealIntervalSeconds, retrievalIntervalSeconds, + retrievalAnonIntervalSeconds, dataSetCreationIntervalSeconds, dataRetentionPollIntervalSeconds, providersRefreshIntervalSeconds, @@ -941,6 +1024,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { const { dealIntervalSeconds, retrievalIntervalSeconds, + retrievalAnonIntervalSeconds, dataSetCreationIntervalSeconds, dataRetentionPollIntervalSeconds, providersRefreshIntervalSeconds, @@ -958,6 +1042,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { const phaseMs = this.schedulePhaseSeconds() * 1000; const dealStartAt = new Date(now.getTime() + phaseMs); const retrievalStartAt = new Date(now.getTime() + phaseMs); + const retrievalAnonStartAt = new Date(now.getTime() + phaseMs); const dataSetCreationStartAt = new Date(now.getTime() + phaseMs); const dataRetentionPollStartAt = new Date(now.getTime() + phaseMs); const providersRefreshStartAt = new Date(now.getTime() + phaseMs); @@ -981,6 +1066,12 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { for (const address of unblockedAddresses) { await this.jobScheduleRepository.upsertSchedule("deal", address, dealIntervalSeconds, dealStartAt); await this.jobScheduleRepository.upsertSchedule("retrieval", address, retrievalIntervalSeconds, retrievalStartAt); + await this.jobScheduleRepository.upsertSchedule( + "retrieval_anon", + address, + retrievalAnonIntervalSeconds, + retrievalAnonStartAt, + ); if (minDataSets >= 1) { await this.jobScheduleRepository.upsertSchedule( "data_set_creation", @@ -1138,6 +1229,8 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { return SP_WORK_QUEUE; case "piece_cleanup": return SP_WORK_QUEUE; + case "retrieval_anon": + return RETRIEVAL_ANON_QUEUE; case "data_retention_poll": return DATA_RETENTION_POLL_QUEUE; case "providers_refresh": @@ -1157,6 +1250,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { if ( row.job_type === "deal" || row.job_type === "retrieval" || + row.job_type === "retrieval_anon" || row.job_type === "data_set_creation" || row.job_type === "piece_cleanup" ) { @@ -1229,6 +1323,7 @@ export class JobsService implements OnModuleInit, OnApplicationShutdown { const jobTypes: JobType[] = [ "deal", "retrieval", + "retrieval_anon", "data_set_creation", "piece_cleanup", "data_retention_poll", diff --git a/apps/backend/src/metrics-prometheus/check-metric-labels.ts b/apps/backend/src/metrics-prometheus/check-metric-labels.ts index d8447160..9d776586 100644 --- a/apps/backend/src/metrics-prometheus/check-metric-labels.ts +++ b/apps/backend/src/metrics-prometheus/check-metric-labels.ts @@ -1,4 +1,4 @@ -export type CheckType = "dataStorage" | "retrieval" | "dataRetention" | "dataSetCreation"; +export type CheckType = "dataStorage" | "retrieval" | "anon_retrieval" | "dataRetention" | "dataSetCreation"; export type ProviderStatus = "approved" | "unapproved"; export type CheckMetricLabels = { diff --git a/apps/backend/src/metrics-prometheus/check-metrics.service.ts b/apps/backend/src/metrics-prometheus/check-metrics.service.ts index 55975cad..85f1cdcf 100644 --- a/apps/backend/src/metrics-prometheus/check-metrics.service.ts +++ b/apps/backend/src/metrics-prometheus/check-metrics.service.ts @@ -248,3 +248,66 @@ export class DataSetCreationCheckMetrics { this.dataSetCreationStatusCounter.inc({ ...labels, value }); } } + +@Injectable() +export class AnonRetrievalCheckMetrics { + constructor( + @InjectMetric("anonPieceRetrievalFirstByteMs") + private readonly firstByteMs: Histogram, + @InjectMetric("anonPieceRetrievalLastByteMs") + private readonly lastByteMs: Histogram, + @InjectMetric("anonPieceRetrievalThroughputBps") + private readonly throughputBps: Histogram, + @InjectMetric("anonRetrievalCheckMs") + private readonly checkMs: Histogram, + @InjectMetric("anonRetrievalStatus") + private readonly statusCounter: Counter, + @InjectMetric("anonPieceHttpResponseCode") + private readonly httpResponseCounter: Counter, + @InjectMetric("anonCarParseStatus") + private readonly carParseCounter: Counter, + @InjectMetric("anonIpniStatus") + private readonly ipniCounter: Counter, + @InjectMetric("anonBlockFetchStatus") + private readonly blockFetchCounter: Counter, + ) {} + + observeFirstByteMs(labels: CheckMetricLabels, value: number | null | undefined): void { + observePositive(this.firstByteMs, labels, value); + } + + observeLastByteMs(labels: CheckMetricLabels, value: number | null | undefined): void { + observePositive(this.lastByteMs, labels, value); + } + + observeThroughput(labels: CheckMetricLabels, value: number | null | undefined): void { + observePositive(this.throughputBps, labels, value); + } + + observeCheckDuration(labels: CheckMetricLabels, value: number | null | undefined): void { + observePositive(this.checkMs, labels, value); + } + + recordStatus(labels: CheckMetricLabels, value: string): void { + this.statusCounter.inc({ ...labels, value }); + } + + recordHttpResponseCode(labels: CheckMetricLabels, statusCode: number): void { + this.httpResponseCounter.inc({ + ...labels, + value: classifyHttpResponseCode(statusCode), + }); + } + + recordCarParseStatus(labels: CheckMetricLabels, parseable: boolean): void { + this.carParseCounter.inc({ ...labels, value: parseable ? "parseable" : "not_parseable" }); + } + + recordIpniStatus(labels: CheckMetricLabels, value: "valid" | "invalid" | "skipped"): void { + this.ipniCounter.inc({ ...labels, value }); + } + + recordBlockFetchStatus(labels: CheckMetricLabels, value: "valid" | "invalid" | "skipped"): void { + this.blockFetchCounter.inc({ ...labels, value }); + } +} diff --git a/apps/backend/src/metrics-prometheus/metrics-prometheus.module.ts b/apps/backend/src/metrics-prometheus/metrics-prometheus.module.ts index 18bda30d..45f728b6 100644 --- a/apps/backend/src/metrics-prometheus/metrics-prometheus.module.ts +++ b/apps/backend/src/metrics-prometheus/metrics-prometheus.module.ts @@ -8,6 +8,7 @@ import { } from "@willsoto/nestjs-prometheus"; import { WalletSdkModule } from "../wallet-sdk/wallet-sdk.module.js"; import { + AnonRetrievalCheckMetrics, DataSetCreationCheckMetrics, DataStorageCheckMetrics, DiscoverabilityCheckMetrics, @@ -207,6 +208,56 @@ const metricProviders = [ help: "Estimated number of unrecorded overdue proving periods per provider. Resets to 0 when the subgraph catches up.", labelNames: ["checkType", "providerId", "providerName", "providerStatus"] as const, }), + // Anonymous Retrieval Metrics + makeHistogramProvider({ + name: "anonPieceRetrievalFirstByteMs", + help: "Time to first byte for anonymous piece retrievals via /piece/{cid} (ms)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus"] as const, + buckets: [1, 5, 10, 50, 100, 250, 500, 1000, 2000, 5000, 10000, 30000], + }), + makeHistogramProvider({ + name: "anonPieceRetrievalLastByteMs", + help: "Total time to retrieve an anonymous piece via /piece/{cid} (ms)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus"] as const, + buckets: [1, 5, 10, 50, 100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000, 120000, 300000], + }), + makeHistogramProvider({ + name: "anonPieceRetrievalThroughputBps", + help: "Throughput for anonymous piece retrievals (bytes/s)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus"] as const, + buckets: throughputBuckets, + }), + makeHistogramProvider({ + name: "anonRetrievalCheckMs", + help: "End-to-end anonymous retrieval check duration (ms)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus"] as const, + buckets: [100, 500, 1000, 2000, 5000, 10000, 30000, 60000, 120000, 300000, 600000], + }), + makeCounterProvider({ + name: "anonRetrievalStatus", + help: "Anonymous retrieval overall outcome", + labelNames: ["checkType", "providerId", "providerName", "providerStatus", "value"] as const, + }), + makeCounterProvider({ + name: "anonPieceHttpResponseCode", + help: "HTTP response codes for anonymous piece retrieval requests", + labelNames: ["checkType", "providerId", "providerName", "providerStatus", "value"] as const, + }), + makeCounterProvider({ + name: "anonCarParseStatus", + help: "Anonymous retrieval CAR parse outcomes (parseable / not_parseable)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus", "value"] as const, + }), + makeCounterProvider({ + name: "anonIpniStatus", + help: "Anonymous retrieval IPNI check outcomes (valid / invalid / skipped)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus", "value"] as const, + }), + makeCounterProvider({ + name: "anonBlockFetchStatus", + help: "Anonymous retrieval block fetch validation outcomes (valid / invalid / skipped)", + labelNames: ["checkType", "providerId", "providerName", "providerStatus", "value"] as const, + }), // Storage provider metrics: absolute counts, independent of query filters. makeGaugeProvider({ name: "storage_providers_active", @@ -333,6 +384,7 @@ const metricProviders = [ RetrievalCheckMetrics, DiscoverabilityCheckMetrics, DataSetCreationCheckMetrics, + AnonRetrievalCheckMetrics, WalletBalanceCollector, // HTTP metrics interceptor { @@ -347,6 +399,7 @@ const metricProviders = [ RetrievalCheckMetrics, DiscoverabilityCheckMetrics, DataSetCreationCheckMetrics, + AnonRetrievalCheckMetrics, WalletBalanceCollector, ], }) diff --git a/apps/backend/src/pdp-subgraph/pdp-subgraph.module.ts b/apps/backend/src/pdp-subgraph/pdp-subgraph.module.ts deleted file mode 100644 index 6e084fc1..00000000 --- a/apps/backend/src/pdp-subgraph/pdp-subgraph.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from "@nestjs/common"; -import { PDPSubgraphService } from "./pdp-subgraph.service.js"; - -@Module({ - providers: [PDPSubgraphService], - exports: [PDPSubgraphService], -}) -export class PdpSubgraphModule {} diff --git a/apps/backend/src/pdp-subgraph/queries.ts b/apps/backend/src/pdp-subgraph/queries.ts deleted file mode 100644 index a21a3991..00000000 --- a/apps/backend/src/pdp-subgraph/queries.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const Queries = { - GET_PROVIDERS_WITH_DATASETS: ` - query GetProvidersWithDataSet($addresses: [Bytes!], $blockNumber: BigInt!) { - providers(where: {address_in: $addresses}) { - address - totalFaultedPeriods - totalProvingPeriods - proofSets (where: {nextDeadline_lt: $blockNumber, status: PROVING}) { - nextDeadline - maxProvingPeriod - } - } - } - `, - GET_SUBGRAPH_META: ` - query GetSubgraphMeta { - _meta { - block { - number - } - } - } - `, -} as const; diff --git a/apps/backend/src/retrieval-anon/anon-piece-selector.service.spec.ts b/apps/backend/src/retrieval-anon/anon-piece-selector.service.spec.ts new file mode 100644 index 00000000..b822fe5f --- /dev/null +++ b/apps/backend/src/retrieval-anon/anon-piece-selector.service.spec.ts @@ -0,0 +1,168 @@ +import type { ConfigService } from "@nestjs/config"; +import type { Repository } from "typeorm"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { IConfig } from "../config/app.config.js"; +import type { AnonRetrieval } from "../database/entities/anon-retrieval.entity.js"; +import type { SampleAnonPieceParams, SubgraphService } from "../subgraph/subgraph.service.js"; +import type { AnonCandidatePiece } from "../subgraph/types.js"; +import { AnonPieceSelectorService } from "./anon-piece-selector.service.js"; + +const SP_ADDRESS = "0xAaAaAAaAaaaAaAAAAaaaaAAaaAaaaAAaaaaa1111"; +const DEALBOT_PAYER = "0xBbBBBbBBbbbBbBBBBBbbbbbBBbbBbbbBBbbbb2222"; + +const makePiece = (overrides: Partial = {}): AnonCandidatePiece => ({ + pieceCid: `baga6ea4seaqpiece${Math.random().toString(36).slice(2, 10)}`, + pieceId: "1", + dataSetId: "42", + rawSize: "1048576", + withIPFSIndexing: true, + ipfsRootCid: "bafyroot", + indexedAtBlock: 12345, + pdpPaymentEndEpoch: null, + ...overrides, +}); + +const makeRetrievalRepository = (recentPieceCids: string[]): Repository => { + const queryBuilder = { + select: vi.fn().mockReturnThis(), + orderBy: vi.fn().mockReturnThis(), + limit: vi.fn().mockReturnThis(), + getRawMany: vi.fn().mockResolvedValue(recentPieceCids.map((c) => ({ pieceCid: c }))), + }; + return { + createQueryBuilder: vi.fn().mockReturnValue(queryBuilder), + } as unknown as Repository; +}; + +const makeConfigService = (): ConfigService => + ({ + get: vi.fn((key: string) => { + if (key === "blockchain") { + return { walletAddress: DEALBOT_PAYER }; + } + return undefined; + }), + }) as unknown as ConfigService; + +describe("AnonPieceSelectorService", () => { + let subgraphService: SubgraphService; + let sampleAnonPiece: ReturnType; + + beforeEach(() => { + sampleAnonPiece = vi.fn(); + subgraphService = { sampleAnonPiece } as unknown as SubgraphService; + }); + + it("returns null when every fallback attempt yields no piece", async () => { + sampleAnonPiece.mockResolvedValue(null); + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result).toBeNull(); + expect(sampleAnonPiece).toHaveBeenCalled(); + }); + + it("returns the sampled piece with SP address lowercased", async () => { + sampleAnonPiece.mockResolvedValueOnce(makePiece({ pieceCid: "baga-the-one" })); + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result).not.toBeNull(); + expect(result?.pieceCid).toBe("baga-the-one"); + expect(result?.serviceProvider).toBe(SP_ADDRESS.toLowerCase()); + }); + + it("passes the dealbot payer address to sampleAnonPiece for exclusion", async () => { + sampleAnonPiece.mockResolvedValueOnce(makePiece()); + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + + await service.selectPieceForProvider(SP_ADDRESS); + + const call = sampleAnonPiece.mock.calls[0][0] as SampleAnonPieceParams; + expect(call.payer).toBe(DEALBOT_PAYER); + expect(call.serviceProvider).toBe(SP_ADDRESS); + }); + + it("redraws when the first sampled piece's payment has already terminated", async () => { + const staleCid = "baga-terminated"; + const freshCid = "baga-live"; + sampleAnonPiece + .mockResolvedValueOnce(makePiece({ pieceCid: staleCid, pdpPaymentEndEpoch: 100n, indexedAtBlock: 200 })) + .mockResolvedValueOnce(makePiece({ pieceCid: freshCid, pdpPaymentEndEpoch: null })); + + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result?.pieceCid).toBe(freshCid); + }); + + it("redraws when the first sampled piece was recently tested", async () => { + const staleCid = "baga-stale"; + const freshCid = "baga-fresh"; + sampleAnonPiece + .mockResolvedValueOnce(makePiece({ pieceCid: staleCid })) + .mockResolvedValueOnce(makePiece({ pieceCid: freshCid })); + + const service = new AnonPieceSelectorService( + subgraphService, + makeConfigService(), + makeRetrievalRepository([staleCid]), + ); + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result?.pieceCid).toBe(freshCid); + }); + + it("falls back to the opposite pool when the preferred one is empty", async () => { + // First pool call returns nothing twice (both attempts), second pool succeeds. + const fresh = makePiece({ pieceCid: "baga-other-pool" }); + sampleAnonPiece.mockResolvedValueOnce(null).mockResolvedValueOnce(null).mockResolvedValueOnce(fresh); + + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result?.pieceCid).toBe("baga-other-pool"); + + // The second (fallback) call should target the opposite pool. + const firstCall = sampleAnonPiece.mock.calls[0][0] as SampleAnonPieceParams; + const fallbackCall = sampleAnonPiece.mock.calls[2][0] as SampleAnonPieceParams; + expect(fallbackCall.pool).not.toBe(firstCall.pool); + }); + + it("widens size bucket to 'any' after both pools fail in the primary bucket", async () => { + // 4 empty attempts across (bucket × both pools × 2 draws each) then + // succeed on the first `any` bucket call. + sampleAnonPiece + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(makePiece({ pieceCid: "baga-any-bucket" })); + + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + const result = await service.selectPieceForProvider(SP_ADDRESS); + + expect(result?.pieceCid).toBe("baga-any-bucket"); + + // The 5th call (index 4) should be the widened-bucket attempt; its size + // range covers at least the 32 GiB ceiling of the "large" bucket. + const widened = sampleAnonPiece.mock.calls[4][0] as SampleAnonPieceParams; + expect(BigInt(widened.maxSize)).toBeGreaterThanOrEqual(32n * 1024n * 1024n * 1024n); + expect(widened.minSize).toBe("0"); + }); + + it("draws a fresh sampleKey for each subgraph call", async () => { + sampleAnonPiece.mockResolvedValueOnce(null).mockResolvedValueOnce(makePiece()); + + const service = new AnonPieceSelectorService(subgraphService, makeConfigService(), makeRetrievalRepository([])); + await service.selectPieceForProvider(SP_ADDRESS); + + const call1 = sampleAnonPiece.mock.calls[0][0] as SampleAnonPieceParams; + const call2 = sampleAnonPiece.mock.calls[1][0] as SampleAnonPieceParams; + expect(call1.sampleKey).toMatch(/^0x[0-9a-f]{64}$/); + expect(call2.sampleKey).toMatch(/^0x[0-9a-f]{64}$/); + expect(call1.sampleKey).not.toBe(call2.sampleKey); + }); +}); diff --git a/apps/backend/src/retrieval-anon/anon-piece-selector.service.ts b/apps/backend/src/retrieval-anon/anon-piece-selector.service.ts new file mode 100644 index 00000000..acc19832 --- /dev/null +++ b/apps/backend/src/retrieval-anon/anon-piece-selector.service.ts @@ -0,0 +1,208 @@ +import { randomBytes } from "node:crypto"; +import { Injectable, Logger } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { InjectRepository } from "@nestjs/typeorm"; +import type { Repository } from "typeorm"; +import type { IConfig } from "../config/app.config.js"; +import { AnonRetrieval } from "../database/entities/anon-retrieval.entity.js"; +import type { AnonPiecePool, SampleAnonPieceParams } from "../subgraph/subgraph.service.js"; +import { SubgraphService } from "../subgraph/subgraph.service.js"; +import type { AnonCandidatePiece } from "../subgraph/types.js"; +import type { AnonPiece } from "./types.js"; + +/** + * Number of most-recently-tested anonymous pieces to exclude from selection + * to avoid immediately retesting the same piece. Piece CIDs are globally + * unique and each one lives on a single SP's dataset, so scoping by CID + * is equivalent to scoping by (SP, CID) for this workload. + */ +const RECENT_DEDUP_WINDOW = 500; + +/** + * Piece size buckets, in raw (unpadded) bytes. Weighted sampling across + * these buckets keeps tests meaningful for bandwidth measurement without + * locking out SPs whose corpus skews small or large. + */ +type SizeBucket = "small" | "medium" | "large"; +type SizeRange = { min: bigint; max: bigint }; + +const MIB = 1024n * 1024n; + +// All downloads are buffered in-memory, so we need to keep piece sizes reasonable +const SIZE_BUCKETS: Record = { + small: { min: 1n * MIB, max: 20n * MIB - 1n }, + medium: { min: 20n * MIB, max: 100n * MIB - 1n }, + large: { min: 100n * MIB, max: 500n * MIB - 1n }, +}; + +/** Weights for choosing a bucket per selection. Must sum to 1. */ +const BUCKET_WEIGHTS: Record = { + small: 0.2, + medium: 0.5, + large: 0.3, +}; + +/** + * Probability the primary draw targets the withIPFSIndexing pool. + * The rest of the time we sample across all FWSS pieces so SPs can't + * optimise only their CAR corpus. + */ +const IPFS_INDEXED_SAMPLE_RATE = 0.8; + +@Injectable() +export class AnonPieceSelectorService { + private readonly logger = new Logger(AnonPieceSelectorService.name); + + constructor( + private readonly subgraphService: SubgraphService, + private readonly configService: ConfigService, + @InjectRepository(AnonRetrieval) + private readonly anonRetrievalRepository: Repository, + ) {} + + /** + * Select an anonymous piece to test against the given SP. + * + * Strategy: + * 1. Pick a size bucket by weighted random. + * 2. Pick a pool (`indexed` 80% / `any` 20%). + * 3. Generate a uniform-random sampleKey and query the subgraph for the + * smallest `Root.sampleKey ≥ $sampleKey` matching the filters. + * 4. Drop the pick if `pdpPaymentEndEpoch` has passed or it was tested + * recently; redraw once. + * 5. If still empty, fall back through: (same bucket, opposite pool) → + * (any bucket, indexed) → (any bucket, any). + */ + async selectPieceForProvider(spAddress: string): Promise { + const dealbotPayer = this.configService.get("blockchain", { infer: true }).walletAddress; + const recentlyTested = await this.loadRecentlyTestedPieceCids(); + + const bucket = this.pickBucket(); + const pool: AnonPiecePool = Math.random() < IPFS_INDEXED_SAMPLE_RATE ? "indexed" : "any"; + + const attempts: Array<{ bucket: SizeBucket | "any"; pool: AnonPiecePool }> = [ + { bucket, pool }, + { bucket, pool: pool === "indexed" ? "any" : "indexed" }, + { bucket: "any", pool: "indexed" }, + { bucket: "any", pool: "any" }, + ]; + + for (const attempt of attempts) { + const piece = await this.drawPiece({ + spAddress, + dealbotPayer, + bucket: attempt.bucket, + pool: attempt.pool, + recentlyTested, + }); + + if (piece) { + this.logger.log({ + event: "anon_piece_selected", + message: "Selected anonymous piece for retrieval test", + spAddress, + pieceCid: piece.pieceCid, + dataSetId: piece.dataSetId, + withIPFSIndexing: piece.withIPFSIndexing, + bucket: attempt.bucket, + pool: attempt.pool, + }); + return { + pieceCid: piece.pieceCid, + dataSetId: piece.dataSetId, + pieceId: piece.pieceId, + serviceProvider: spAddress.toLowerCase(), + withIPFSIndexing: piece.withIPFSIndexing, + ipfsRootCid: piece.ipfsRootCid, + rawSize: piece.rawSize, + }; + } + } + + this.logger.warn({ + event: "anon_no_candidates", + message: "No anonymous piece found after all fallbacks", + spAddress, + }); + return null; + } + + /** + * Try to draw a piece for one (bucket, pool) combination. Up to two draws + * with fresh sampleKeys, each filtered by dedup + epoch-termination. + */ + private async drawPiece(args: { + spAddress: string; + dealbotPayer: string; + bucket: SizeBucket | "any"; + pool: AnonPiecePool; + recentlyTested: Set; + }): Promise { + const range = args.bucket === "any" ? fullRange() : SIZE_BUCKETS[args.bucket]; + + for (let attempt = 0; attempt < 2; attempt++) { + const params: SampleAnonPieceParams = { + serviceProvider: args.spAddress, + payer: args.dealbotPayer, + sampleKey: randomSampleKey(), + minSize: range.min.toString(), + maxSize: range.max.toString(), + pool: args.pool, + }; + + const piece = await this.subgraphService.sampleAnonPiece(params); + if (!piece) { + continue; + } + + if (piece.pdpPaymentEndEpoch != null && piece.pdpPaymentEndEpoch <= BigInt(piece.indexedAtBlock)) { + continue; + } + + if (args.recentlyTested.has(piece.pieceCid)) { + continue; + } + + return piece; + } + + return null; + } + + private pickBucket(): SizeBucket { + const r = Math.random(); + let acc = 0; + for (const [name, weight] of Object.entries(BUCKET_WEIGHTS) as Array<[SizeBucket, number]>) { + acc += weight; + if (r < acc) { + return name; + } + } + return "medium"; + } + + /** + * Return the set of piece CIDs tested in the last RECENT_DEDUP_WINDOW + * anonymous retrievals across all SPs. + */ + private async loadRecentlyTestedPieceCids(): Promise> { + const rows = await this.anonRetrievalRepository + .createQueryBuilder("r") + .select("r.piece_cid", "pieceCid") + .orderBy("r.created_at", "DESC") + .limit(RECENT_DEDUP_WINDOW) + .getRawMany<{ pieceCid: string }>(); + + return new Set(rows.map((row) => row.pieceCid)); + } +} + +/** Uniform-random 32-byte sort key as `0x`-prefixed hex. */ +function randomSampleKey(): string { + return `0x${randomBytes(32).toString("hex")}`; +} + +/** The full size range (used when bucket fallback is "any"). */ +function fullRange(): SizeRange { + return { min: 0n, max: (1n << 63n) - 1n }; +} diff --git a/apps/backend/src/retrieval-anon/anon-retrieval.service.spec.ts b/apps/backend/src/retrieval-anon/anon-retrieval.service.spec.ts new file mode 100644 index 00000000..61e97105 --- /dev/null +++ b/apps/backend/src/retrieval-anon/anon-retrieval.service.spec.ts @@ -0,0 +1,189 @@ +import type { Repository } from "typeorm"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { AnonRetrieval } from "../database/entities/anon-retrieval.entity.js"; +import type { StorageProvider } from "../database/entities/storage-provider.entity.js"; +import { RetrievalStatus } from "../database/types.js"; +import type { AnonRetrievalCheckMetrics } from "../metrics-prometheus/check-metrics.service.js"; +import type { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; +import type { AnonPieceSelectorService } from "./anon-piece-selector.service.js"; +import { AnonRetrievalService } from "./anon-retrieval.service.js"; +import type { CarValidationService } from "./car-validation.service.js"; +import type { PieceRetrievalService } from "./piece-retrieval.service.js"; +import type { PieceRetrievalResult } from "./types.js"; + +const SP_ADDRESS = "0xaaaa0000000000000000000000000000000000aa"; + +const PIECE = { + pieceCid: "baga6ea4seaqpiece", + pieceId: "1", + dataSetId: "42", + rawSize: "1048576", + withIPFSIndexing: false, + ipfsRootCid: null, + serviceProvider: SP_ADDRESS, +}; + +function makeProvider(): StorageProvider { + return { + address: SP_ADDRESS, + providerId: 7, + name: "sp-test", + isApproved: true, + } as unknown as StorageProvider; +} + +function makeService(opts: { + pieceResult: PieceRetrievalResult; + fetchPieceImpl?: (signal?: AbortSignal) => Promise; +}): { + service: AnonRetrievalService; + saveSpy: ReturnType; + fetchSpy: ReturnType; +} { + const saveSpy = vi.fn(async (entity: AnonRetrieval) => entity); + const createdEntities: Partial[] = []; + const anonRetrievalRepository = { + create: vi.fn((data: Partial) => { + createdEntities.push(data); + return data; + }), + save: saveSpy, + } as unknown as Repository; + + const spRepository = { + findOne: vi.fn(async () => makeProvider()), + } as unknown as Repository; + + const anonPieceSelector = { + selectPieceForProvider: vi.fn(async () => PIECE), + } as unknown as AnonPieceSelectorService; + + const fetchSpy = vi.fn(opts.fetchPieceImpl ?? (async () => opts.pieceResult)); + const pieceRetrievalService = { + fetchPiece: fetchSpy, + } as unknown as PieceRetrievalService; + + const carValidationService = { + validateCarPiece: vi.fn(), + } as unknown as CarValidationService; + + const walletSdkService = { + getProviderInfo: vi.fn(() => ({ pdp: { serviceURL: "https://sp.test/" } })), + } as unknown as WalletSdkService; + + const metrics = { + observeFirstByteMs: vi.fn(), + observeLastByteMs: vi.fn(), + observeThroughput: vi.fn(), + observeCheckDuration: vi.fn(), + recordStatus: vi.fn(), + recordHttpResponseCode: vi.fn(), + recordCarParseStatus: vi.fn(), + recordIpniStatus: vi.fn(), + recordBlockFetchStatus: vi.fn(), + } as unknown as AnonRetrievalCheckMetrics; + + const service = new AnonRetrievalService( + anonPieceSelector, + pieceRetrievalService, + carValidationService, + walletSdkService, + metrics, + anonRetrievalRepository, + spRepository, + ); + + return { service, saveSpy, fetchSpy }; +} + +describe("AnonRetrievalService", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("persists partial metrics when fetchPiece returns aborted=true", async () => { + const partial: PieceRetrievalResult = { + success: false, + pieceCid: PIECE.pieceCid, + bytesReceived: 524288, + pieceBytes: null, + latencyMs: 42000, + ttfbMs: 150, + throughputBps: 12500, + statusCode: 200, + commPValid: false, + errorMessage: "Anon retrieval job timeout (60s) for sp1", + aborted: true, + }; + + const { service, saveSpy } = makeService({ pieceResult: partial }); + + await service.performForProvider(SP_ADDRESS); + + expect(saveSpy).toHaveBeenCalledTimes(1); + const saved = saveSpy.mock.calls[0][0] as Partial; + expect(saved.status).toBe(RetrievalStatus.FAILED); + expect(saved.bytesRetrieved).toBe(524288); + expect(saved.ttfbMs).toBe(150); + expect(saved.latencyMs).toBe(42000); + expect(saved.throughputBps).toBe(12500); + expect(saved.responseCode).toBe(200); + expect(saved.errorMessage).toContain("Anon retrieval job timeout"); + }); + + it("still saves a row when the signal aborts before fetchPiece runs", async () => { + const ac = new AbortController(); + ac.abort(new Error("Anon retrieval job timeout (60s) for sp1")); + + const never: PieceRetrievalResult = { + success: false, + pieceCid: PIECE.pieceCid, + bytesReceived: 0, + pieceBytes: null, + latencyMs: 0, + ttfbMs: 0, + throughputBps: 0, + statusCode: 0, + commPValid: false, + }; + + const { service, saveSpy, fetchSpy } = makeService({ pieceResult: never }); + + await service.performForProvider(SP_ADDRESS, ac.signal); + + expect(fetchSpy).not.toHaveBeenCalled(); + expect(saveSpy).toHaveBeenCalledTimes(1); + const saved = saveSpy.mock.calls[0][0] as Partial; + expect(saved.status).toBe(RetrievalStatus.FAILED); + expect(saved.errorMessage).toContain("Anon retrieval job timeout"); + expect(saved.bytesRetrieved).toBeNull(); + expect(saved.ttfbMs).toBeNull(); + }); + + it("still saves a row when fetchPiece throws unexpectedly", async () => { + const never: PieceRetrievalResult = { + success: false, + pieceCid: PIECE.pieceCid, + bytesReceived: 0, + pieceBytes: null, + latencyMs: 0, + ttfbMs: 0, + throughputBps: 0, + statusCode: 0, + commPValid: false, + }; + + const { service, saveSpy } = makeService({ + pieceResult: never, + fetchPieceImpl: async () => { + throw new Error("network down"); + }, + }); + + await expect(service.performForProvider(SP_ADDRESS)).rejects.toThrow("network down"); + + expect(saveSpy).toHaveBeenCalledTimes(1); + const saved = saveSpy.mock.calls[0][0] as Partial; + expect(saved.status).toBe(RetrievalStatus.FAILED); + }); +}); diff --git a/apps/backend/src/retrieval-anon/anon-retrieval.service.ts b/apps/backend/src/retrieval-anon/anon-retrieval.service.ts new file mode 100644 index 00000000..d40fe315 --- /dev/null +++ b/apps/backend/src/retrieval-anon/anon-retrieval.service.ts @@ -0,0 +1,244 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import type { Repository } from "typeorm"; +import { type ProviderJobContext, toStructuredError } from "../common/logging.js"; +import { AnonRetrieval } from "../database/entities/anon-retrieval.entity.js"; +import { StorageProvider } from "../database/entities/storage-provider.entity.js"; +import { RetrievalStatus, ServiceType } from "../database/types.js"; +import { buildCheckMetricLabels } from "../metrics-prometheus/check-metric-labels.js"; +import { AnonRetrievalCheckMetrics } from "../metrics-prometheus/check-metrics.service.js"; +import { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; +import { AnonPieceSelectorService } from "./anon-piece-selector.service.js"; +import { CarValidationService } from "./car-validation.service.js"; +import { PieceRetrievalService } from "./piece-retrieval.service.js"; +import type { CarValidationResult, PieceRetrievalResult } from "./types.js"; + +@Injectable() +export class AnonRetrievalService { + private readonly logger = new Logger(AnonRetrievalService.name); + + constructor( + private readonly anonPieceSelectorService: AnonPieceSelectorService, + private readonly pieceRetrievalService: PieceRetrievalService, + private readonly carValidationService: CarValidationService, + private readonly walletSdkService: WalletSdkService, + private readonly metrics: AnonRetrievalCheckMetrics, + @InjectRepository(AnonRetrieval) + private readonly anonRetrievalRepository: Repository, + @InjectRepository(StorageProvider) + private readonly spRepository: Repository, + ) {} + + async performForProvider( + spAddress: string, + signal?: AbortSignal, + logContext?: ProviderJobContext, + ): Promise { + // Build metric labels + const provider = await this.spRepository.findOne({ where: { address: spAddress } }); + const labels = buildCheckMetricLabels({ + checkType: "anon_retrieval", + providerId: provider?.providerId, + providerName: provider?.name, + providerIsApproved: provider?.isApproved, + }); + + // 1. Select an anonymous piece + const piece = await this.anonPieceSelectorService.selectPieceForProvider(spAddress); + if (!piece) { + this.logger.warn({ + ...logContext, + event: "anon_retrieval_no_piece", + message: "No anonymous piece found for SP", + spAddress, + }); + this.metrics.recordStatus(labels, "failure.no_piece"); + return null; + } + + this.logger.log({ + ...logContext, + event: "anon_retrieval_started", + message: "Starting anonymous retrieval test", + pieceCid: piece.pieceCid, + dataSetId: piece.dataSetId, + pieceId: piece.pieceId, + withIPFSIndexing: piece.withIPFSIndexing, + spAddress, + }); + + const checkStart = Date.now(); + const startedAt = new Date(); + + let pieceResult: PieceRetrievalResult | null = null; + let carResult: CarValidationResult | null = null; + let saved: AnonRetrieval | null = null; + + try { + // 2. Fetch the piece. fetchPiece never throws on abort — it returns a + // result with partial metrics so we can persist what we have. + if (signal?.aborted) { + pieceResult = buildAbortedPlaceholder(piece.pieceCid, signal.reason); + } else { + pieceResult = await this.pieceRetrievalService.fetchPiece(spAddress, piece.pieceCid, signal); + } + + // Emit piece retrieval metrics + this.metrics.observeFirstByteMs(labels, pieceResult.ttfbMs); + this.metrics.observeLastByteMs(labels, pieceResult.latencyMs); + this.metrics.observeThroughput(labels, pieceResult.throughputBps); + this.metrics.recordHttpResponseCode(labels, pieceResult.statusCode); + + // 3. CAR validation (only if piece was successfully retrieved and has IPFS indexing) + if ( + pieceResult.success && + piece.withIPFSIndexing && + piece.ipfsRootCid && + pieceResult.pieceBytes && + provider && + !signal?.aborted + ) { + try { + carResult = await this.carValidationService.validateCarPiece( + pieceResult.pieceBytes, + provider, + piece.ipfsRootCid, + signal, + ); + } catch (error) { + this.logger.warn({ + ...logContext, + event: "anon_retrieval_car_validation_failed", + message: "CAR validation threw an error", + pieceCid: piece.pieceCid, + spAddress, + error: toStructuredError(error), + }); + } + } + + // Emit CAR validation metrics + if (carResult) { + this.metrics.recordCarParseStatus(labels, carResult.carParseable); + this.metrics.recordIpniStatus( + labels, + carResult.ipniValid === null ? "skipped" : carResult.ipniValid ? "valid" : "invalid", + ); + this.metrics.recordBlockFetchStatus( + labels, + carResult.blockFetchValid === null ? "skipped" : carResult.blockFetchValid ? "valid" : "invalid", + ); + } else if (!pieceResult.success) { + // Piece retrieval failed — IPNI and block fetch were skipped + this.metrics.recordIpniStatus(labels, "skipped"); + this.metrics.recordBlockFetchStatus(labels, "skipped"); + } + + // Overall check duration and status + this.metrics.observeCheckDuration(labels, Date.now() - checkStart); + this.metrics.recordStatus( + labels, + pieceResult.success ? "success" : pieceResult.aborted ? "failure.aborted" : "failure.http", + ); + } finally { + // Always save a record — even on abort or unexpected error — so we never + // lose the evidence (ttfb, bytes, response code) we already collected. + pieceResult ??= buildAbortedPlaceholder(piece.pieceCid, signal?.reason); + saved = await this.saveRetrievalRecord(spAddress, piece, pieceResult, carResult, startedAt, logContext); + } + + return saved; + } + + private async saveRetrievalRecord( + spAddress: string, + piece: { + pieceCid: string; + dataSetId: string; + pieceId: string; + rawSize: string; + withIPFSIndexing: boolean; + ipfsRootCid: string | null; + }, + pieceResult: PieceRetrievalResult, + carResult: CarValidationResult | null, + startedAt: Date, + logContext?: ProviderJobContext, + ): Promise { + const providerInfo = this.walletSdkService.getProviderInfo(spAddress); + const spBaseUrl = providerInfo?.pdp.serviceURL.replace(/\/$/, "") ?? spAddress; + + const retrieval = this.anonRetrievalRepository.create({ + spAddress, + pieceCid: piece.pieceCid, + dataSetId: BigInt(piece.dataSetId), + pieceId: BigInt(piece.pieceId), + rawSize: BigInt(piece.rawSize), + withIpfsIndexing: piece.withIPFSIndexing, + ipfsRootCid: piece.ipfsRootCid, + serviceType: ServiceType.DIRECT_SP, + retrievalEndpoint: `${spBaseUrl}/piece/${piece.pieceCid}`, + status: pieceResult.success ? RetrievalStatus.SUCCESS : RetrievalStatus.FAILED, + startedAt, + completedAt: new Date(), + latencyMs: pieceResult.latencyMs > 0 ? Math.round(pieceResult.latencyMs) : null, + ttfbMs: pieceResult.ttfbMs > 0 ? Math.round(pieceResult.ttfbMs) : null, + throughputBps: pieceResult.throughputBps > 0 ? Math.round(pieceResult.throughputBps) : null, + bytesRetrieved: pieceResult.bytesReceived > 0 ? pieceResult.bytesReceived : null, + responseCode: pieceResult.statusCode > 0 ? pieceResult.statusCode : null, + errorMessage: pieceResult.errorMessage ?? null, + commpValid: pieceResult.success ? pieceResult.commPValid : null, + carValid: carResult ? carResult.ipniValid !== false && carResult.blockFetchValid !== false : null, + }); + + try { + await this.anonRetrievalRepository.save(retrieval); + } catch (error) { + this.logger.warn({ + ...logContext, + event: "anon_retrieval_save_failed", + message: "Failed to save anonymous retrieval record", + pieceCid: piece.pieceCid, + spAddress, + error: toStructuredError(error), + }); + return null; + } + + this.logger.log({ + ...logContext, + event: "anon_retrieval_completed", + message: "Anonymous retrieval test completed", + pieceCid: piece.pieceCid, + spAddress, + success: pieceResult.success, + aborted: pieceResult.aborted === true, + latencyMs: pieceResult.latencyMs, + ttfbMs: pieceResult.ttfbMs, + bytesRetrieved: pieceResult.bytesReceived, + carParseable: carResult?.carParseable, + ipniValid: carResult?.ipniValid, + blockFetchValid: carResult?.blockFetchValid, + }); + + return retrieval; + } +} + +function buildAbortedPlaceholder(pieceCid: string, reason: unknown): PieceRetrievalResult { + const message = + reason instanceof Error && reason.message ? reason.message : typeof reason === "string" ? reason : "aborted"; + return { + success: false, + pieceCid, + bytesReceived: 0, + pieceBytes: null, + latencyMs: 0, + ttfbMs: 0, + throughputBps: 0, + statusCode: 0, + commPValid: false, + errorMessage: message, + aborted: true, + }; +} diff --git a/apps/backend/src/retrieval-anon/car-validation.service.ts b/apps/backend/src/retrieval-anon/car-validation.service.ts new file mode 100644 index 00000000..8019b8df --- /dev/null +++ b/apps/backend/src/retrieval-anon/car-validation.service.ts @@ -0,0 +1,223 @@ +import { CarReader } from "@ipld/car"; +import * as dagPB from "@ipld/dag-pb"; +import { Injectable, Logger } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { create as createBlock } from "multiformats/block"; +import { CID } from "multiformats/cid"; +import * as raw from "multiformats/codecs/raw"; +import { sha256 } from "multiformats/hashes/sha2"; +import { toStructuredError } from "../common/logging.js"; +import type { IConfig } from "../config/app.config.js"; +import type { StorageProvider } from "../database/entities/storage-provider.entity.js"; +import { HttpClientService } from "../http-client/http-client.service.js"; +import { IpniVerificationService } from "../ipni/ipni-verification.service.js"; +import { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; +import type { CarValidationResult } from "./types.js"; + +// UnixFS DAGs use only dag-pb (interior nodes) and raw (leaf data) codecs +const unixfsCodecs: Record unknown }> = { + [dagPB.code]: dagPB, + [raw.code]: raw, +}; + +@Injectable() +export class CarValidationService { + private readonly logger = new Logger(CarValidationService.name); + + constructor( + private readonly configService: ConfigService, + private readonly httpClientService: HttpClientService, + private readonly walletSdkService: WalletSdkService, + private readonly ipniVerificationService: IpniVerificationService, + ) {} + + /** + * Validate an anonymous piece retrieved as a CAR: + * 1. parse the CAR, + * 2. sample random blocks, + * 3. confirm the SP is advertised for the root + sampled CIDs via IPNI, + * 4. fetch each sampled block from the SP and hash-verify it. + * + * CAR parse failure is attributed to the client (bad upload), not the SP. + */ + async validateCarPiece( + pieceBytes: Buffer, + provider: StorageProvider, + ipfsRootCid: string, + signal?: AbortSignal, + ): Promise { + const blocks = await this.parseCar(pieceBytes, provider.address, ipfsRootCid); + if (blocks === null) { + return { carParseable: false, blockCount: 0, sampledCidCount: 0, ipniValid: null, blockFetchValid: null }; + } + if (blocks.length === 0) { + return { + carParseable: true, + blockCount: 0, + sampledCidCount: 0, + ipniValid: null, + blockFetchValid: null, + errorMessage: "CAR contained no blocks", + }; + } + + const sampleCount = this.configService.get("retrieval", { infer: true }).anonBlockSampleCount; + const shuffled = [...blocks].sort(() => Math.random() - 0.5); + const sampledBlocks = shuffled.slice(0, sampleCount); + + const ipniValid = await this.checkIpni(provider, ipfsRootCid, sampledBlocks, signal); + const blockFetchResult = await this.checkBlockFetch(sampledBlocks, provider.address, signal); + + return { + carParseable: true, + blockCount: blocks.length, + sampledCidCount: sampledBlocks.length, + ipniValid, + blockFetchValid: blockFetchResult.valid, + errorMessage: blockFetchResult.errorMessage, + }; + } + + private async parseCar( + pieceBytes: Buffer, + spAddress: string, + ipfsRootCid: string, + ): Promise<{ cid: CID; bytes: Uint8Array }[] | null> { + try { + const reader = await CarReader.fromBytes(new Uint8Array(pieceBytes)); + const blocks: { cid: CID; bytes: Uint8Array }[] = []; + for await (const block of reader.blocks()) { + blocks.push({ cid: block.cid, bytes: block.bytes }); + } + return blocks; + } catch (error) { + this.logger.debug({ + event: "car_parse_failed", + message: "Failed to parse piece bytes as CAR - client fault, not SP", + spAddress, + ipfsRootCid, + error: toStructuredError(error), + }); + return null; + } + } + + /** + * Verify via IPNI that the SP is advertised for the root CID and each sampled child CID. + * Delegates to the shared IpniVerificationService which uses filecoin-pin's provider-scoped check. + */ + private async checkIpni( + provider: StorageProvider, + ipfsRootCid: string, + sampledBlocks: ReadonlyArray<{ cid: CID }>, + signal?: AbortSignal, + ): Promise { + const timeouts = this.configService.get("timeouts", { infer: true }); + let rootCid: CID; + try { + rootCid = CID.parse(ipfsRootCid); + } catch (error) { + this.logger.warn({ + event: "ipni_root_cid_invalid", + message: "Failed to parse ipfsRootCID", + ipfsRootCid, + providerAddress: provider.address, + error: toStructuredError(error), + }); + return false; + } + + const result = await this.ipniVerificationService.verify({ + rootCid, + blockCids: sampledBlocks.map((b) => b.cid), + storageProvider: provider, + timeoutMs: timeouts.ipniVerificationTimeoutMs, + pollIntervalMs: timeouts.ipniVerificationPollingMs, + signal, + }); + + return result.rootCIDVerified; + } + + /** + * Fetch each sampled block from the SP endpoint and hash-verify the response + * against the declared CID. Mirrors IpfsBlockRetrievalStrategy's per-block + * verification for the sampled subset (no DAG traversal). + */ + private async checkBlockFetch( + sampledBlocks: ReadonlyArray<{ cid: CID; bytes: Uint8Array }>, + spAddress: string, + signal?: AbortSignal, + ): Promise<{ valid: boolean | null; errorMessage?: string }> { + const providerInfo = this.walletSdkService.getProviderInfo(spAddress); + if (!providerInfo) { + return { valid: null, errorMessage: `Provider info not found for ${spAddress}` }; + } + + const spBaseUrl = providerInfo.pdp.serviceURL.replace(/\/$/, ""); + let allValid = true; + + for (const block of sampledBlocks) { + signal?.throwIfAborted(); + const cidStr = block.cid.toString(); + const blockUrl = `${spBaseUrl}/ipfs/${cidStr}?format=raw`; + + try { + const resp = await this.httpClientService.requestWithMetrics(blockUrl, { + headers: { Accept: "application/vnd.ipld.raw" }, + httpVersion: "2", + signal, + }); + + if (resp.metrics.statusCode < 200 || resp.metrics.statusCode >= 300) { + allValid = false; + this.logger.warn({ + event: "block_fetch_non_2xx", + message: "Block fetch returned non-2xx status", + cid: cidStr, + spAddress, + statusCode: resp.metrics.statusCode, + }); + continue; + } + + if (block.cid.multihash.code !== sha256.code) { + this.logger.warn({ + event: "block_unsupported_hash", + message: `Unsupported hash algorithm 0x${block.cid.multihash.code.toString(16)}`, + cid: cidStr, + spAddress, + }); + allValid = false; + continue; + } + + const codec = unixfsCodecs[block.cid.code]; + if (!codec) { + this.logger.warn({ + event: "block_unsupported_codec", + message: `Unsupported codec 0x${block.cid.code.toString(16)}`, + cid: cidStr, + spAddress, + }); + allValid = false; + continue; + } + + // Hash-verifies and decodes; throws on mismatch + await createBlock({ bytes: resp.data, cid: block.cid, hasher: sha256, codec }); + } catch (error) { + allValid = false; + this.logger.warn({ + event: "block_fetch_failed", + message: "Block fetch or hash verification failed", + cid: cidStr, + spAddress, + error: toStructuredError(error), + }); + } + } + + return { valid: allValid }; + } +} diff --git a/apps/backend/src/retrieval-anon/piece-retrieval.service.ts b/apps/backend/src/retrieval-anon/piece-retrieval.service.ts new file mode 100644 index 00000000..51150661 --- /dev/null +++ b/apps/backend/src/retrieval-anon/piece-retrieval.service.ts @@ -0,0 +1,195 @@ +import { asPieceCID, calculate as calculatePieceCid } from "@filoz/synapse-core/piece"; +import { Injectable, Logger } from "@nestjs/common"; +import { toStructuredError } from "../common/logging.js"; +import { HttpClientService } from "../http-client/http-client.service.js"; +import { WalletSdkService } from "../wallet-sdk/wallet-sdk.service.js"; +import type { PieceRetrievalResult } from "./types.js"; + +@Injectable() +export class PieceRetrievalService { + private readonly logger = new Logger(PieceRetrievalService.name); + + constructor( + private readonly walletSdkService: WalletSdkService, + private readonly httpClientService: HttpClientService, + ) {} + + async fetchPiece(spAddress: string, pieceCid: string, signal?: AbortSignal): Promise { + const providerInfo = this.walletSdkService.getProviderInfo(spAddress); + + if (!providerInfo) { + this.logger.warn({ + event: "provider_info_not_found", + message: "Cannot fetch piece: provider info not found", + spAddress, + pieceCid, + }); + + return { + success: false, + pieceCid, + bytesReceived: 0, + pieceBytes: null, + latencyMs: 0, + ttfbMs: 0, + throughputBps: 0, + statusCode: 0, + commPValid: false, + errorMessage: `Provider info not found for ${spAddress}`, + }; + } + + const baseUrl = providerInfo.pdp.serviceURL.replace(/\/$/, ""); + const url = `${baseUrl}/piece/${pieceCid}`; + + try { + const result = await this.httpClientService.requestWithMetrics(url, { + httpVersion: "2", + signal, + }); + + const { metrics } = result; + const isSuccess = metrics.statusCode >= 200 && metrics.statusCode < 300; + const throughputBps = metrics.totalTime > 0 ? metrics.responseSize / (metrics.totalTime / 1000) : 0; + + if (result.aborted) { + this.logger.warn({ + event: "piece_fetch_aborted", + message: "Piece fetch aborted mid-download; returning partial metrics", + url, + pieceCid, + spAddress, + bytesReceived: metrics.responseSize, + ttfbMs: metrics.ttfb, + abortReason: result.abortReason, + }); + + return { + success: false, + pieceCid, + bytesReceived: metrics.responseSize, + pieceBytes: null, + latencyMs: metrics.totalTime, + ttfbMs: metrics.ttfb, + throughputBps, + statusCode: metrics.statusCode, + commPValid: false, + errorMessage: result.abortReason ?? "aborted", + aborted: true, + }; + } + + if (!isSuccess) { + this.logger.warn({ + event: "piece_fetch_non_2xx", + message: "Piece fetch returned non-2xx status", + url, + statusCode: metrics.statusCode, + pieceCid, + spAddress, + }); + + return { + success: false, + pieceCid, + bytesReceived: metrics.responseSize, + pieceBytes: null, + latencyMs: metrics.totalTime, + ttfbMs: metrics.ttfb, + throughputBps, + statusCode: metrics.statusCode, + commPValid: false, + errorMessage: `HTTP ${metrics.statusCode}`, + }; + } + + const pieceBytes = Buffer.isBuffer(result.data) ? result.data : Buffer.from(result.data); + const commPValid = await this.validateCommP(pieceBytes, pieceCid); + + this.logger.debug({ + event: "piece_fetch_success", + message: "Piece fetched successfully", + pieceCid, + spAddress, + bytesReceived: metrics.responseSize, + latencyMs: metrics.totalTime, + ttfbMs: metrics.ttfb, + }); + + return { + success: true, + pieceCid, + bytesReceived: metrics.responseSize, + pieceBytes, + latencyMs: metrics.totalTime, + ttfbMs: metrics.ttfb, + throughputBps, + statusCode: metrics.statusCode, + commPValid, + }; + } catch (error) { + const aborted = signal?.aborted === true; + this.logger.warn({ + event: "piece_fetch_failed", + message: "Piece fetch threw an error", + url, + pieceCid, + spAddress, + aborted, + error: toStructuredError(error), + }); + + return { + success: false, + pieceCid, + bytesReceived: 0, + pieceBytes: null, + latencyMs: 0, + ttfbMs: 0, + throughputBps: 0, + statusCode: 0, + commPValid: false, + errorMessage: error instanceof Error ? error.message : String(error), + aborted, + }; + } + } + + /** + * Compute the piece CID (sha2-256-trunc254-padded) of the retrieved bytes and compare + * against the expected CID. Returns false on parse failure, computation failure, or mismatch. + */ + private async validateCommP(bytes: Buffer, pieceCid: string): Promise { + const expected = asPieceCID(pieceCid); + if (!expected) { + this.logger.warn({ + event: "commp_invalid_piece_cid", + message: "Cannot parse expected piece CID for CommP validation", + pieceCid, + }); + return false; + } + + try { + const computed = calculatePieceCid(bytes); + const matches = computed.toString() === expected.toString(); + if (!matches) { + this.logger.warn({ + event: "commp_mismatch", + message: "Piece CID mismatch: SP-returned bytes hash to a different CID", + expected: expected.toString(), + computed: computed.toString(), + }); + } + return matches; + } catch (error) { + this.logger.warn({ + event: "commp_validation_error", + message: "CommP computation threw an error", + pieceCid, + error: toStructuredError(error), + }); + return false; + } + } +} diff --git a/apps/backend/src/retrieval-anon/retrieval-anon.module.ts b/apps/backend/src/retrieval-anon/retrieval-anon.module.ts new file mode 100644 index 00000000..4e9e38df --- /dev/null +++ b/apps/backend/src/retrieval-anon/retrieval-anon.module.ts @@ -0,0 +1,27 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { AnonRetrieval } from "../database/entities/anon-retrieval.entity.js"; +import { StorageProvider } from "../database/entities/storage-provider.entity.js"; +import { HttpClientModule } from "../http-client/http-client.module.js"; +import { IpniModule } from "../ipni/ipni.module.js"; +import { SubgraphModule } from "../subgraph/subgraph.module.js"; +import { WalletSdkModule } from "../wallet-sdk/wallet-sdk.module.js"; +import { AnonPieceSelectorService } from "./anon-piece-selector.service.js"; +import { AnonRetrievalService } from "./anon-retrieval.service.js"; +import { CarValidationService } from "./car-validation.service.js"; +import { PieceRetrievalService } from "./piece-retrieval.service.js"; + +@Module({ + imports: [ + ConfigModule, + TypeOrmModule.forFeature([AnonRetrieval, StorageProvider]), + SubgraphModule, + WalletSdkModule, + HttpClientModule, + IpniModule, + ], + providers: [AnonPieceSelectorService, PieceRetrievalService, CarValidationService, AnonRetrievalService], + exports: [AnonRetrievalService], +}) +export class RetrievalAnonModule {} diff --git a/apps/backend/src/retrieval-anon/types.ts b/apps/backend/src/retrieval-anon/types.ts new file mode 100644 index 00000000..2c3384d5 --- /dev/null +++ b/apps/backend/src/retrieval-anon/types.ts @@ -0,0 +1,35 @@ +/** The result of anonymous piece selection. */ +export type AnonPiece = { + pieceCid: string; + dataSetId: string; + pieceId: string; + serviceProvider: string; + withIPFSIndexing: boolean; + ipfsRootCid: string | null; + rawSize: string; +}; + +/** Result of piece retrieval. */ +export type PieceRetrievalResult = { + success: boolean; + pieceCid: string; + bytesReceived: number; + pieceBytes: Buffer | null; + latencyMs: number; + ttfbMs: number; + throughputBps: number; + statusCode: number; + commPValid: boolean; + errorMessage?: string; + aborted?: boolean; +}; + +/** Result of CAR validation. */ +export type CarValidationResult = { + carParseable: boolean; + blockCount: number; + sampledCidCount: number; + ipniValid: boolean | null; + blockFetchValid: boolean | null; + errorMessage?: string; +}; diff --git a/apps/backend/src/subgraph/queries.ts b/apps/backend/src/subgraph/queries.ts new file mode 100644 index 00000000..74802ddf --- /dev/null +++ b/apps/backend/src/subgraph/queries.ts @@ -0,0 +1,78 @@ +export const Queries = { + GET_PROVIDERS_WITH_DATASETS: ` + query GetProvidersWithDataSet($addresses: [Bytes!], $blockNumber: BigInt!) { + providers(where: {address_in: $addresses}) { + address + totalFaultedPeriods + totalProvingPeriods + proofSets (where: {nextDeadline_lt: $blockNumber, status: PROVING}) { + nextDeadline + maxProvingPeriod + } + } + } + `, + GET_SUBGRAPH_META: ` + query GetSubgraphMeta { + _meta { + block { + number + } + } + } + `, +} as const; + +/** + * Build a sampleAnonPiece query scoped to the requested pool. The single + * piece of query shape that differs is whether the proofSet filter pins + * `withIPFSIndexing: true`; assembling the fragment here keeps the rest + * of the query and the returned selection set shared. + */ +export function buildSampleAnonPieceQuery(pool: "indexed" | "any"): string { + const indexingFilter = pool === "indexed" ? "withIPFSIndexing: true" : ""; + return ` + query SampleAnonPiece( + $serviceProvider: Bytes! + $payer: Bytes! + $sampleKey: Bytes! + $minSize: BigInt! + $maxSize: BigInt! + ) { + _meta { + block { + number + } + } + roots( + first: 1 + orderBy: sampleKey + orderDirection: asc + where: { + sampleKey_gte: $sampleKey + removed: false + rawSize_gte: $minSize + rawSize_lte: $maxSize + proofSet_: { + fwssServiceProvider: $serviceProvider + fwssPayer_not: $payer + isActive: true + ${indexingFilter} + } + } + subgraphError: allow + ) { + rootId + cid + rawSize + ipfsRootCID + proofSet { + setId + withIPFSIndexing + fwssPayer + pdpPaymentEndEpoch + } + } + } + `; +} diff --git a/apps/backend/src/subgraph/subgraph.module.ts b/apps/backend/src/subgraph/subgraph.module.ts new file mode 100644 index 00000000..7834c39b --- /dev/null +++ b/apps/backend/src/subgraph/subgraph.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { SubgraphService } from "./subgraph.service.js"; + +@Module({ + providers: [SubgraphService], + exports: [SubgraphService], +}) +export class SubgraphModule {} diff --git a/apps/backend/src/pdp-subgraph/pdp-subgraph.service.spec.ts b/apps/backend/src/subgraph/subgraph.service.spec.ts similarity index 79% rename from apps/backend/src/pdp-subgraph/pdp-subgraph.service.spec.ts rename to apps/backend/src/subgraph/subgraph.service.spec.ts index cd3a1ea8..4dc2cd5e 100644 --- a/apps/backend/src/pdp-subgraph/pdp-subgraph.service.spec.ts +++ b/apps/backend/src/subgraph/subgraph.service.spec.ts @@ -1,7 +1,8 @@ import type { ConfigService } from "@nestjs/config"; +import { CID } from "multiformats/cid"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { IConfig } from "../config/app.config.js"; -import { PDPSubgraphService } from "./pdp-subgraph.service.js"; +import { SubgraphService } from "./subgraph.service.js"; const VALID_ADDRESS = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" as const; const SUBGRAPH_ENDPOINT = "https://api.thegraph.com/subgraphs/filecoin/pdp" as const; @@ -35,21 +36,57 @@ const makeSubgraphMetaResponse = (blockNumber = 12345) => ({ }, }); -describe("PDPSubgraphService", () => { - let service: PDPSubgraphService; +const FWSS_SP_ADDRESS = "0xAaaaAAaaaaAAaaaAaAaAaaAaaaAaAaAaaAaaa111"; +const FWSS_PAYER = "0xBBbbBBbbBBbBBbBbbBBbbBBbbbbBbBBbbBBbb222"; +const EXAMPLE_PIECE_CID = "baga6ea4seaqpzwrimvoc4jp4l7mk6knsknf6owsc2ev4krrs2peenl5qelh6u4y"; +const pieceCidHex = `0x${Buffer.from(CID.parse(EXAMPLE_PIECE_CID).bytes).toString("hex")}`; + +const makeSampleRoot = (overrides: Record = {}) => ({ + rootId: "1", + cid: pieceCidHex, + rawSize: "1048576", + ipfsRootCID: "bafyroot", + proofSet: { + setId: "42", + withIPFSIndexing: true, + fwssPayer: FWSS_PAYER.toLowerCase(), + pdpPaymentEndEpoch: null, + }, + ...overrides, +}); + +const makeSampleResponse = (roots: Record[] = [], blockNumber = 12345) => ({ + data: { + _meta: { block: { number: blockNumber } }, + roots, + }, +}); + +const SAMPLE_KEY = "0x0000000000000000000000000000000000000000000000000000000000000001"; +const defaultSampleParams = { + serviceProvider: FWSS_SP_ADDRESS, + payer: FWSS_PAYER, + sampleKey: SAMPLE_KEY, + minSize: "0", + maxSize: "1000000000000", + pool: "indexed" as const, +}; + +describe("SubgraphService", () => { + let service: SubgraphService; let fetchMock: ReturnType; beforeEach(() => { const configService = { get: vi.fn((key: keyof IConfig) => { if (key === "blockchain") { - return { pdpSubgraphEndpoint: SUBGRAPH_ENDPOINT }; + return { subgraphEndpoint: SUBGRAPH_ENDPOINT }; } return undefined; }), } as unknown as ConfigService; - service = new PDPSubgraphService(configService); + service = new SubgraphService(configService); fetchMock = vi.fn(); vi.stubGlobal("fetch", fetchMock); @@ -362,10 +399,10 @@ describe("PDPSubgraphService", () => { it("throws when PDP subgraph endpoint is not configured", async () => { const configService = { - get: vi.fn(() => ({ pdpSubgraphEndpoint: "" })), + get: vi.fn(() => ({ subgraphEndpoint: "" })), } as unknown as ConfigService; - const serviceWithoutEndpoint = new PDPSubgraphService(configService); + const serviceWithoutEndpoint = new SubgraphService(configService); await expect(serviceWithoutEndpoint.fetchSubgraphMeta()).rejects.toThrow("No PDP subgraph endpoint configured"); }); @@ -691,4 +728,120 @@ describe("PDPSubgraphService", () => { expect(timestamps.length).toBe(1); }); }); + + describe("sampleAnonPiece", () => { + it("returns null when endpoint is not configured", async () => { + const noEndpointConfig = { + get: vi.fn(() => ({ subgraphEndpoint: "" })), + } as unknown as ConfigService; + const noEndpointService = new SubgraphService(noEndpointConfig); + + const piece = await noEndpointService.sampleAnonPiece(defaultSampleParams); + expect(piece).toBeNull(); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it("returns null when the subgraph yields no matching root", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => makeSampleResponse([]), + }); + + const piece = await service.sampleAnonPiece(defaultSampleParams); + expect(piece).toBeNull(); + }); + + it("parses the sampled root into a decoded candidate piece", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => makeSampleResponse([makeSampleRoot()]), + }); + + const piece = await service.sampleAnonPiece(defaultSampleParams); + + expect(piece).toMatchObject({ + pieceCid: EXAMPLE_PIECE_CID, + pieceId: "1", + dataSetId: "42", + rawSize: "1048576", + withIPFSIndexing: true, + ipfsRootCid: "bafyroot", + pdpPaymentEndEpoch: null, + indexedAtBlock: 12345, + }); + }); + + it("returns pdpPaymentEndEpoch as bigint when the dataset is terminating", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => + makeSampleResponse([ + makeSampleRoot({ + proofSet: { + setId: "42", + withIPFSIndexing: true, + fwssPayer: FWSS_PAYER.toLowerCase(), + pdpPaymentEndEpoch: "5000", + }, + }), + ]), + }); + + const piece = await service.sampleAnonPiece(defaultSampleParams); + expect(piece?.pdpPaymentEndEpoch).toBe(5000n); + }); + + it("lowercases SP and payer addresses before querying", async () => { + fetchMock.mockResolvedValueOnce({ ok: true, json: async () => makeSampleResponse([]) }); + + await service.sampleAnonPiece(defaultSampleParams); + + const [, opts] = fetchMock.mock.calls[0]; + const body = JSON.parse(opts.body as string); + expect(body.variables.serviceProvider).toBe(FWSS_SP_ADDRESS.toLowerCase()); + expect(body.variables.payer).toBe(FWSS_PAYER.toLowerCase()); + expect(body.query).toContain("withIPFSIndexing: true"); + }); + + it("uses the any-pool query when pool is 'any'", async () => { + fetchMock.mockResolvedValueOnce({ ok: true, json: async () => makeSampleResponse([]) }); + + await service.sampleAnonPiece({ ...defaultSampleParams, pool: "any" }); + + const [, opts] = fetchMock.mock.calls[0]; + const body = JSON.parse(opts.body as string); + expect(body.query).not.toContain("withIPFSIndexing: true"); + }); + + it("returns null when the sampled root has an undecodable CID", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => makeSampleResponse([makeSampleRoot({ cid: "0xdeadbeef" })]), + }); + + const piece = await service.sampleAnonPiece(defaultSampleParams); + expect(piece).toBeNull(); + }); + + it("throws after max retries on repeated HTTP errors", async () => { + fetchMock.mockResolvedValue({ ok: false, status: 500, statusText: "Internal Server Error" }); + + const promise = service.sampleAnonPiece(defaultSampleParams); + promise.catch(() => {}); + await vi.runAllTimersAsync(); + + await expect(promise).rejects.toThrow("Failed to fetch subgraph sample_anon_piece_indexed after 3 attempts"); + expect(fetchMock).toHaveBeenCalledTimes(3); + }); + + it("does not retry on schema validation failure", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { _meta: { block: { number: 1 } } } }), // missing roots + }); + + await expect(service.sampleAnonPiece(defaultSampleParams)).rejects.toThrow(/validation failed/i); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/apps/backend/src/pdp-subgraph/pdp-subgraph.service.ts b/apps/backend/src/subgraph/subgraph.service.ts similarity index 52% rename from apps/backend/src/pdp-subgraph/pdp-subgraph.service.ts rename to apps/backend/src/subgraph/subgraph.service.ts index aedd8bce..55359179 100644 --- a/apps/backend/src/pdp-subgraph/pdp-subgraph.service.ts +++ b/apps/backend/src/subgraph/subgraph.service.ts @@ -2,9 +2,40 @@ import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { toStructuredError } from "../common/logging.js"; import type { IBlockchainConfig, IConfig } from "../config/app.config.js"; -import { Queries } from "./queries.js"; -import type { GraphQLResponse, ProviderDataSetResponse, ProvidersWithDataSetsOptions, SubgraphMeta } from "./types.js"; -import { validateProviderDataSetResponse, validateSubgraphMetaResponse } from "./types.js"; +import { buildSampleAnonPieceQuery, Queries } from "./queries.js"; +import type { + AnonCandidatePiece, + GraphQLResponse, + ProviderDataSetResponse, + ProvidersWithDataSetsOptions, + RawSampleAnonPieceResponse, + SubgraphMeta, +} from "./types.js"; +import { + decodePieceCid, + validateProviderDataSetResponse, + validateSampleAnonPieceResponse, + validateSubgraphMetaResponse, +} from "./types.js"; + +/** Pool of pieces to sample from. */ +export type AnonPiecePool = "indexed" | "any"; + +/** Inputs for a single anonymous piece sample query. */ +export type SampleAnonPieceParams = { + /** Service provider address (lowercase hex). */ + serviceProvider: string; + /** Dealbot's own payer address (excluded to keep the sample non-dealbot). */ + payer: string; + /** Uniform-random 32-byte sort key as `0x`-prefixed hex. */ + sampleKey: string; + /** Inclusive lower bound on raw piece size in bytes (decimal string). */ + minSize: string; + /** Inclusive upper bound on raw piece size in bytes (decimal string). */ + maxSize: string; + /** Which pool to sample from. */ + pool: AnonPiecePool; +}; /** * Error thrown when data validation fails. @@ -21,8 +52,8 @@ class ValidationError extends Error { } @Injectable() -export class PDPSubgraphService { - private readonly logger: Logger = new Logger(PDPSubgraphService.name); +export class SubgraphService { + private readonly logger: Logger = new Logger(SubgraphService.name); private readonly blockchainConfig: IBlockchainConfig; private static readonly MAX_PROVIDERS_PER_QUERY = 100; @@ -45,14 +76,14 @@ export class PDPSubgraphService { * @throws Error if endpoint is not configured or after MAX_RETRIES attempts */ async fetchSubgraphMeta(attempt: number = 1): Promise { - if (!this.blockchainConfig.pdpSubgraphEndpoint) { + if (!this.blockchainConfig.subgraphEndpoint) { throw new Error("No PDP subgraph endpoint configured"); } try { await this.enforceRateLimit(); - const response = await fetch(this.blockchainConfig.pdpSubgraphEndpoint, { + const response = await fetch(this.blockchainConfig.subgraphEndpoint, { method: "POST", headers: { "Content-Type": "application/json", @@ -95,13 +126,13 @@ export class PDPSubgraphService { } // Retry on network/HTTP errors - if (attempt < PDPSubgraphService.MAX_RETRIES) { - const delay = PDPSubgraphService.INITIAL_RETRY_DELAY_MS * (1 << (attempt - 1)); + if (attempt < SubgraphService.MAX_RETRIES) { + const delay = SubgraphService.INITIAL_RETRY_DELAY_MS * (1 << (attempt - 1)); this.logger.warn({ event: "subgraph_meta_request_retry", message: "Subgraph meta request failed. Retrying...", attempt, - maxRetries: PDPSubgraphService.MAX_RETRIES, + maxRetries: SubgraphService.MAX_RETRIES, retryDelayMs: delay, error: toStructuredError(error), }); @@ -112,11 +143,11 @@ export class PDPSubgraphService { this.logger.error({ event: "subgraph_meta_request_failed", message: "Subgraph meta request failed after maximum retries", - maxRetries: PDPSubgraphService.MAX_RETRIES, + maxRetries: SubgraphService.MAX_RETRIES, error: toStructuredError(error), }); throw new Error( - `Failed to fetch subgraph metadata after ${PDPSubgraphService.MAX_RETRIES} attempts: ${errorMessage}`, + `Failed to fetch subgraph metadata after ${SubgraphService.MAX_RETRIES} attempts: ${errorMessage}`, ); } } @@ -136,13 +167,154 @@ export class PDPSubgraphService { return []; } - if (addresses.length <= PDPSubgraphService.MAX_PROVIDERS_PER_QUERY) { + if (addresses.length <= SubgraphService.MAX_PROVIDERS_PER_QUERY) { return this.fetchWithRetry(blockNumber, addresses); } return this.fetchMultipleBatchesWithRateLimit(blockNumber, addresses); } + /** + * Draw a single random anonymous piece for retrieval testing. + * + * Uses the Root.sampleKey (keccak256 of the entity id) to pick the + * smallest key ≥ `params.sampleKey` that matches the filters — a uniform + * random pick when `sampleKey` is generated uniformly. Server-side filters + * cover SP, payer-exclusion, active status, size range, and optionally + * `withIPFSIndexing`. Returns null when no piece matches (callers should + * retry with a fresh sampleKey or relax the pool/bucket). + * + * `pdpPaymentEndEpoch` is returned to the caller for a cheap client-side + * epoch comparison — GraphQL filters on nullable BigInts are awkward. + */ + async sampleAnonPiece(params: SampleAnonPieceParams): Promise { + if (!this.blockchainConfig.subgraphEndpoint) { + return null; + } + + const query = buildSampleAnonPieceQuery(params.pool); + const variables = { + serviceProvider: params.serviceProvider.toLowerCase(), + payer: params.payer.toLowerCase(), + sampleKey: params.sampleKey, + minSize: params.minSize, + maxSize: params.maxSize, + }; + + const validated = await this.executeQuery( + `sample_anon_piece_${params.pool}`, + query, + variables, + validateSampleAnonPieceResponse, + ); + + const root = validated.roots[0]; + if (!root) { + return null; + } + + try { + return { + pieceCid: decodePieceCid(root.cid), + pieceId: root.rootId, + dataSetId: root.proofSet.setId, + rawSize: root.rawSize, + withIPFSIndexing: root.proofSet.withIPFSIndexing, + ipfsRootCid: root.ipfsRootCID ?? null, + indexedAtBlock: validated._meta.block.number, + pdpPaymentEndEpoch: root.proofSet.pdpPaymentEndEpoch != null ? BigInt(root.proofSet.pdpPaymentEndEpoch) : null, + }; + } catch (error) { + this.logger.warn({ + event: "anon_piece_cid_decode_failed", + message: "Failed to decode piece CID from subgraph data", + dataSetId: root.proofSet.setId, + pieceId: root.rootId, + error: toStructuredError(error), + }); + return null; + } + } + + /** + * Generic single-query helper with retry and rate limiting. Used by queries that + * don't fit the batched provider-fetch shape. + */ + private async executeQuery( + operationName: string, + query: string, + variables: Record, + transform: (data: unknown) => T, + attempt: number = 1, + ): Promise { + if (!this.blockchainConfig.subgraphEndpoint) { + throw new Error("No PDP subgraph endpoint configured"); + } + + try { + await this.enforceRateLimit(); + + const response = await fetch(this.blockchainConfig.subgraphEndpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query, variables }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const result = (await response.json()) as GraphQLResponse; + + if (result.errors) { + const errorMessage = result.errors?.[0]?.message || "Unknown GraphQL error"; + throw new Error(`GraphQL error: ${errorMessage}`); + } + + try { + return transform(result.data); + } catch (validationError) { + const errorMessage = validationError instanceof Error ? validationError.message : "Unknown validation error"; + throw new ValidationError(`Data validation failed: ${errorMessage}`); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + + if (error instanceof ValidationError) { + this.logger.error({ + event: `subgraph_${operationName}_validation_failed`, + message: `Subgraph ${operationName} validation failed`, + error: toStructuredError(error), + }); + throw error; + } + + if (attempt < SubgraphService.MAX_RETRIES) { + const delay = SubgraphService.INITIAL_RETRY_DELAY_MS * (1 << (attempt - 1)); + this.logger.warn({ + event: `subgraph_${operationName}_request_retry`, + message: `Subgraph ${operationName} request failed. Retrying...`, + attempt, + maxRetries: SubgraphService.MAX_RETRIES, + retryDelayMs: delay, + error: toStructuredError(error), + }); + await new Promise((resolve) => setTimeout(resolve, delay)); + return this.executeQuery(operationName, query, variables, transform, attempt + 1); + } + + this.logger.error({ + event: `subgraph_${operationName}_request_failed`, + message: `Subgraph ${operationName} request failed after maximum retries`, + maxRetries: SubgraphService.MAX_RETRIES, + error: toStructuredError(error), + }); + throw new Error( + `Failed to fetch subgraph ${operationName} after ${SubgraphService.MAX_RETRIES} attempts: ${errorMessage}`, + ); + } + } + /** * Fetch multiple batches with rate limiting and concurrency control */ @@ -151,15 +323,15 @@ export class PDPSubgraphService { addresses: string[], ): Promise { const batches: string[][] = []; - for (let i = 0; i < addresses.length; i += PDPSubgraphService.MAX_PROVIDERS_PER_QUERY) { - const addressesLimit = Math.min(addresses.length, i + PDPSubgraphService.MAX_PROVIDERS_PER_QUERY); + for (let i = 0; i < addresses.length; i += SubgraphService.MAX_PROVIDERS_PER_QUERY) { + const addressesLimit = Math.min(addresses.length, i + SubgraphService.MAX_PROVIDERS_PER_QUERY); batches.push(addresses.slice(i, addressesLimit)); } const allProviders: ProviderDataSetResponse["providers"] = []; - for (let i = 0; i < batches.length; i += PDPSubgraphService.MAX_CONCURRENT_REQUESTS) { - const batchGroup = batches.slice(i, i + PDPSubgraphService.MAX_CONCURRENT_REQUESTS); + for (let i = 0; i < batches.length; i += SubgraphService.MAX_CONCURRENT_REQUESTS) { + const batchGroup = batches.slice(i, i + SubgraphService.MAX_CONCURRENT_REQUESTS); const results = await Promise.all(batchGroup.map((batch) => this.fetchWithRetry(blockNumber, batch))); @@ -178,7 +350,7 @@ export class PDPSubgraphService { addresses: string[], attempt: number = 1, ): Promise { - if (!this.blockchainConfig.pdpSubgraphEndpoint) { + if (!this.blockchainConfig.subgraphEndpoint) { throw new Error("No PDP subgraph endpoint configured"); } @@ -190,7 +362,7 @@ export class PDPSubgraphService { try { await this.enforceRateLimit(); - const response = await fetch(this.blockchainConfig.pdpSubgraphEndpoint, { + const response = await fetch(this.blockchainConfig.subgraphEndpoint, { method: "POST", headers: { "Content-Type": "application/json", @@ -235,13 +407,13 @@ export class PDPSubgraphService { } // Retry on network/HTTP errors - if (attempt < PDPSubgraphService.MAX_RETRIES) { - const delay = PDPSubgraphService.INITIAL_RETRY_DELAY_MS * (1 << (attempt - 1)); + if (attempt < SubgraphService.MAX_RETRIES) { + const delay = SubgraphService.INITIAL_RETRY_DELAY_MS * (1 << (attempt - 1)); this.logger.warn({ event: "subgraph_provider_request_retry", message: "Subgraph provider request failed. Retrying...", attempt, - maxRetries: PDPSubgraphService.MAX_RETRIES, + maxRetries: SubgraphService.MAX_RETRIES, retryDelayMs: delay, addressCount: addresses.length, error: toStructuredError(error), @@ -253,14 +425,12 @@ export class PDPSubgraphService { this.logger.error({ event: "subgraph_provider_request_failed", message: "Subgraph provider request failed after maximum retries", - maxRetries: PDPSubgraphService.MAX_RETRIES, + maxRetries: SubgraphService.MAX_RETRIES, blockNumber, addressCount: addresses.length, error: toStructuredError(error), }); - throw new Error( - `Failed to fetch provider data after ${PDPSubgraphService.MAX_RETRIES} attempts: ${errorMessage}`, - ); + throw new Error(`Failed to fetch provider data after ${SubgraphService.MAX_RETRIES} attempts: ${errorMessage}`); } } @@ -270,18 +440,18 @@ export class PDPSubgraphService { * Read more here: https://docs.goldsky.com/subgraphs/graphql-endpoints#public-endpoints */ private async enforceRateLimit(requestCount: number = 1): Promise { - if (requestCount > PDPSubgraphService.MAX_CONCURRENT_REQUESTS) { + if (requestCount > SubgraphService.MAX_CONCURRENT_REQUESTS) { throw new Error( - `Cannot request ${requestCount} items; exceeds rate limit window of ${PDPSubgraphService.MAX_CONCURRENT_REQUESTS}`, + `Cannot request ${requestCount} items; exceeds rate limit window of ${SubgraphService.MAX_CONCURRENT_REQUESTS}`, ); } const now = Date.now(); - const windowStart = now - PDPSubgraphService.RATE_LIMIT_WINDOW_MS; + const windowStart = now - SubgraphService.RATE_LIMIT_WINDOW_MS; this.requestTimestamps = this.requestTimestamps.filter((timestamp) => timestamp > windowStart); - const availableSlots = PDPSubgraphService.MAX_CONCURRENT_REQUESTS - this.requestTimestamps.length; + const availableSlots = SubgraphService.MAX_CONCURRENT_REQUESTS - this.requestTimestamps.length; if (requestCount > availableSlots) { const requiredSlots = requestCount - availableSlots; @@ -290,7 +460,7 @@ export class PDPSubgraphService { const oldestTimestamp = this.requestTimestamps[index] || now; // wait time with 10ms buffer - const waitTime = oldestTimestamp + PDPSubgraphService.RATE_LIMIT_WINDOW_MS - now + 10; + const waitTime = oldestTimestamp + SubgraphService.RATE_LIMIT_WINDOW_MS - now + 10; if (waitTime > 0) { await new Promise((resolve) => setTimeout(resolve, waitTime)); diff --git a/apps/backend/src/pdp-subgraph/types.spec.ts b/apps/backend/src/subgraph/types.spec.ts similarity index 100% rename from apps/backend/src/pdp-subgraph/types.spec.ts rename to apps/backend/src/subgraph/types.spec.ts diff --git a/apps/backend/src/pdp-subgraph/types.ts b/apps/backend/src/subgraph/types.ts similarity index 58% rename from apps/backend/src/pdp-subgraph/types.ts rename to apps/backend/src/subgraph/types.ts index ad8dcdc4..3a89f360 100644 --- a/apps/backend/src/pdp-subgraph/types.ts +++ b/apps/backend/src/subgraph/types.ts @@ -1,4 +1,5 @@ import Joi from "joi"; +import { CID } from "multiformats/cid"; import { Hex, isAddress } from "viem"; // ----------------------------------------- @@ -54,6 +55,58 @@ export type ProviderDataSetResponse = { }[]; }; +/** A piece eligible for anonymous retrieval. */ +export type AnonCandidatePiece = { + /** Decoded piece CID string (e.g. "bafk..."). */ + pieceCid: string; + /** On-chain piece ID (rootId) as a decimal string. */ + pieceId: string; + /** On-chain dataset ID (setId) as a decimal string. */ + dataSetId: string; + /** Raw piece size in bytes, as a decimal string. */ + rawSize: string; + /** True iff the parent dataset declared withIPFSIndexing metadata. */ + withIPFSIndexing: boolean; + /** IPFS root CID declared by the client when uploading, or null. */ + ipfsRootCid: string | null; + /** Subgraph-indexed block number at query time. */ + indexedAtBlock: number; + /** pdpPaymentEndEpoch from the parent dataset, or null. */ + pdpPaymentEndEpoch: bigint | null; +}; + +/** + * Validated raw shape of the anonymous piece sampling subgraph response. + * At most one root is returned (`first: 1`). + */ +export type RawSampleAnonPieceResponse = { + _meta: { block: { number: number } }; + roots: Array<{ + rootId: string; + cid: string; + rawSize: string; + ipfsRootCID: string | null; + proofSet: { + setId: string; + withIPFSIndexing: boolean; + fwssPayer: string | null; + pdpPaymentEndEpoch: string | null; + }; + }>; +}; + +// ----------------------------------------- +// Helpers +// ----------------------------------------- + +/** + * Decodes a hex-encoded CID (0x...) into its string representation. + */ +export function decodePieceCid(hexData: string): string { + const bytes = Buffer.from(hexData.slice(2), "hex"); + return CID.decode(new Uint8Array(bytes)).toString(); +} + // ----------------------------------------- // Joi Custom Schema Converters // ----------------------------------------- @@ -117,6 +170,41 @@ const providerDataSetResponseSchema = Joi.object({ .unknown(true) .required(); +const sampleRootProofSetSchema = Joi.object({ + setId: Joi.string().pattern(/^\d+$/).required(), + withIPFSIndexing: Joi.boolean().required(), + fwssPayer: Joi.string() + .pattern(/^0x[0-9a-fA-F]{40}$/) + .allow(null) + .optional(), + pdpPaymentEndEpoch: Joi.string().pattern(/^\d+$/).allow(null).optional(), +}).unknown(true); + +const sampleRootSchema = Joi.object({ + rootId: Joi.string().pattern(/^\d+$/).required(), + cid: Joi.string() + .pattern(/^0x[0-9a-fA-F]+$/) + .required(), + rawSize: Joi.string().pattern(/^\d+$/).required(), + ipfsRootCID: Joi.string().allow(null).optional(), + proofSet: sampleRootProofSetSchema.required(), +}).unknown(true); + +const sampleAnonPieceResponseSchema = Joi.object({ + _meta: Joi.object({ + block: Joi.object({ + number: Joi.number().integer().positive().required(), + }) + .unknown(true) + .required(), + }) + .unknown(true) + .required(), + roots: Joi.array().items(sampleRootSchema).max(1).required(), +}) + .unknown(true) + .required(); + // ----------------------------------------- // Validator Functions // ----------------------------------------- @@ -149,3 +237,16 @@ export function validateProviderDataSetResponse(value: unknown): ProviderDataSet } return validated as ProviderDataSetResponse; } + +/** + * Validates the raw sampleAnonPiece response from the subgraph. + * + * @throws Error if validation fails + */ +export function validateSampleAnonPieceResponse(value: unknown): RawSampleAnonPieceResponse { + const { error, value: validated } = sampleAnonPieceResponseSchema.validate(value, { abortEarly: false }); + if (error) { + throw new Error(`Invalid sampleAnonPiece response format: ${error.message}`); + } + return validated as RawSampleAnonPieceResponse; +} diff --git a/apps/backend/src/wallet-sdk/wallet-sdk.service.spec.ts b/apps/backend/src/wallet-sdk/wallet-sdk.service.spec.ts index 9b0a7070..75078eee 100644 --- a/apps/backend/src/wallet-sdk/wallet-sdk.service.spec.ts +++ b/apps/backend/src/wallet-sdk/wallet-sdk.service.spec.ts @@ -17,7 +17,7 @@ const baseConfig: IBlockchainConfig = { checkDatasetCreationFees: false, useOnlyApprovedProviders: false, minNumDataSetsForChecks: 1, - pdpSubgraphEndpoint: "https://api.thegraph.com/subgraphs/filecoin/pdp", + subgraphEndpoint: "https://api.thegraph.com/subgraphs/filecoin/pdp", }; const makeProvider = (overrides: Partial): PDPProviderEx => diff --git a/apps/subgraph/.gitignore b/apps/subgraph/.gitignore new file mode 100644 index 00000000..931a409a --- /dev/null +++ b/apps/subgraph/.gitignore @@ -0,0 +1,17 @@ +# graph-cli outputs +build/ +generated/ + +# Node dependencies +node_modules/ + +# Goldsky deploy artifacts +.goldsky/ + +# Test outputs +coverage/ +tests/.bin/ +tests/.latest.json + +# Editor / OS +.DS_Store diff --git a/apps/subgraph/README.md b/apps/subgraph/README.md new file mode 100644 index 00000000..2d893838 --- /dev/null +++ b/apps/subgraph/README.md @@ -0,0 +1,76 @@ +# @dealbot/subgraph + +A dealbot-owned Graph Protocol subgraph indexing the Filecoin PDP contracts. Deployed to Goldsky and consumed exclusively by `apps/backend` via the `SUBGRAPH_ENDPOINT` env var. + +## What it indexes + +- **PDPVerifier** — dataset lifecycle, piece add/remove, proving periods. +- **FilecoinWarmStorageService (FWSS)** — payer/service-provider metadata, `withIPFSIndexing` flag, `ipfsRootCID` per piece, service/payment termination. + +## Why it exists + +The dealbot backend needs three queries (see `apps/backend/src/subgraph/queries.ts`): + +1. `GET_SUBGRAPH_META` — latest indexed block. +2. `GET_PROVIDERS_WITH_DATASETS` — overdue proving-period detection. +3. `GET_FWSS_CANDIDATE_PIECES` — anonymous-retrieval piece selection (motivated by [FilOzone/dealbot#427](https://github.com/FilOzone/dealbot/issues/427)). + +The code originated as a fork of [FilOzone/pdp-explorer#100](https://github.com/FilOzone/pdp-explorer/pull/100). Forking lets us trim the schema and handlers to exactly what dealbot queries, and deploy on our own cadence. + +## Why this package is an outlier + +Subgraph mappings compile to WASM via AssemblyScript. Despite the `.ts` extension, AssemblyScript is **not** TypeScript: + +- No Biome/Prettier — the parser trips on AssemblyScript primitives (`u8`, `u32`, `i32`). +- Tests use `matchstick-as`, not Vitest. +- `tsconfig.json` extends `@graphprotocol/graph-ts`'s base config, not the monorepo's. +- Build is `graph codegen && graph build`, not `tsc` or `vite build`. + +The package is intentionally isolated from the root `pnpm test` / `pnpm build` scripts — its lifecycle is "rebuild and redeploy to Goldsky when mappings change", not "build on every PR". + +## Contract addresses + +| Network | Contract | Address | Start block | +|---|---|---|---| +| mainnet (`filecoin`) | PDPVerifier | `0xBADd0B92C1c71d02E7d520f64c0876538fa2557F` | 5441432 | +| mainnet (`filecoin`) | FilecoinWarmStorageService | `0x8408502033C418E1bbC97cE9ac48E5528F371A9f` | 5459617 | +| calibration (`filecoin-testnet`) | PDPVerifier | `0x85e366Cf9DD2c0aE37E963d9556F5f4718d6417C` | 3140755 | +| calibration (`filecoin-testnet`) | FilecoinWarmStorageService | `0x02925630df557F957f70E112bA06e50965417CA0` | 3141276 | + +Maintained in `networks.json`. Editing `subgraph.yaml` manually is usually wrong — run `pnpm build:mainnet` or `pnpm build:calibration` which applies `networks.json` via `graph build --network `. + +Note: `graph build --network X` rewrites `subgraph.yaml` **in place** with the chosen network's values. The committed version is mainnet-default — after a `build:calibration`, re-run `build:mainnet` before committing to avoid leaking calibration values into the mainnet manifest. + +## Local commands + +```bash +# Typegen only (no WASM build) +pnpm --filter @dealbot/subgraph codegen + +# Full build for one network +pnpm --filter @dealbot/subgraph build:mainnet +pnpm --filter @dealbot/subgraph build:calibration + +# Run matchstick tests +pnpm --filter @dealbot/subgraph test +``` + +## Deploy + +Requires `goldsky` CLI authenticated via `GOLDSKY_API_KEY`. + +```bash +export VERSION=0.1.0 +pnpm --filter @dealbot/subgraph build:calibration +pnpm --filter @dealbot/subgraph deploy:calibration + +pnpm --filter @dealbot/subgraph build:mainnet +pnpm --filter @dealbot/subgraph deploy:mainnet +``` + +Goldsky slots (slugs TBD): + +- `dealbot-mainnet/` — mainnet +- `dealbot-calibration/` — calibration + +After deploy, update `SUBGRAPH_ENDPOINT` in the backend env to the new `/gn` URL. diff --git a/apps/subgraph/abis/FilecoinWarmStorageService.json b/apps/subgraph/abis/FilecoinWarmStorageService.json new file mode 100644 index 00000000..cedddc45 --- /dev/null +++ b/apps/subgraph/abis/FilecoinWarmStorageService.json @@ -0,0 +1,2389 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_pdpVerifierAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "_paymentsContractAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "_usdfc", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "_filBeamBeneficiaryAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "_serviceProviderRegistry", + "type": "address", + "internalType": "contract ServiceProviderRegistry" + }, + { + "name": "_sessionKeyRegistry", + "type": "address", + "internalType": "contract SessionKeyRegistry" + }, + { + "name": "_reinitializer_version", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addApprovedProvider", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "announcePlannedUpgrade", + "inputs": [ + { + "name": "plannedUpgrade", + "type": "tuple", + "internalType": "struct FilecoinWarmStorageService.PlannedUpgrade", + "components": [ + { + "name": "nextImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "afterEpoch", + "type": "uint96", + "internalType": "uint96" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "calculateRatePerEpoch", + "inputs": [ + { + "name": "totalBytes", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "storageRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "configureProvingPeriod", + "inputs": [ + { + "name": "_maxProvingPeriod", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "_challengeWindowSize", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "dataSetCreated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "serviceProvider", + "type": "address", + "internalType": "address" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "dataSetDeleted", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "extsload", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "extsloadStruct", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "size", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32[]", + "internalType": "bytes32[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "filBeamBeneficiaryAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getEffectiveRates", + "inputs": [], + "outputs": [ + { + "name": "serviceFee", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "spPayment", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getProvingPeriodForEpoch", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "epoch", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getServicePrice", + "inputs": [], + "outputs": [ + { + "name": "pricing", + "type": "tuple", + "internalType": "struct FilecoinWarmStorageService.ServicePricing", + "components": [ + { + "name": "pricePerTiBPerMonthNoCDN", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pricePerTiBCdnEgress", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pricePerTiBCacheMissEgress", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "tokenAddress", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "epochsPerMonth", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumPricePerMonth", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_maxProvingPeriod", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "_challengeWindowSize", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_filBeamControllerAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "_name", + "type": "string", + "internalType": "string" + }, + { + "name": "_description", + "type": "string", + "internalType": "string" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "migrate", + "inputs": [ + { + "name": "_viewContract", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nextProvingPeriod", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "challengeEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "leafCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "paymentsContractAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pdpVerifierAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "piecesAdded", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "firstAdded", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceData", + "type": "tuple[]", + "internalType": "struct Cids.Cid[]", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "piecesScheduledRemove", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceIds", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "possessionProven", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "challengeCount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "railTerminated", + "inputs": [ + { + "name": "railId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "terminator", + "type": "address", + "internalType": "address" + }, + { + "name": "endEpoch", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeApprovedProvider", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "serviceProviderRegistry", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract ServiceProviderRegistry" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "sessionKeyRegistry", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract SessionKeyRegistry" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setViewContract", + "inputs": [ + { + "name": "_viewContract", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "settleFilBeamPaymentRails", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cdnAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cacheMissAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "storageProviderChanged", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "terminateCDNService", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "terminateService", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "topUpCDNPaymentRails", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cdnAmountToAdd", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "cacheMissAmountToAdd", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFilBeamController", + "inputs": [ + { + "name": "newController", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updatePricing", + "inputs": [ + { + "name": "newStoragePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "newMinimumRate", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateServiceCommission", + "inputs": [ + { + "name": "newCommissionBps", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "usdfcTokenAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC20Metadata" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "validatePayment", + "inputs": [ + { + "name": "railId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "proposedAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fromEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "toEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "result", + "type": "tuple", + "internalType": "struct IValidator.ValidationResult", + "components": [ + { + "name": "modifiedAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "settleUpto", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "note", + "type": "string", + "internalType": "string" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "viewContractAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "CDNPaymentRailsToppedUp", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "cdnAmountAdded", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "totalCdnLockup", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cacheMissAmountAdded", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "totalCacheMissLockup", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CDNPaymentTerminated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "endEpoch", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cacheMissRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cdnRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CDNServiceTerminated", + "inputs": [ + { + "name": "caller", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "cacheMissRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cdnRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ContractUpgraded", + "inputs": [ + { + "name": "version", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "implementation", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DataSetCreated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "providerId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pdpRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cacheMissRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cdnRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "payer", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "serviceProvider", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "payee", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "metadataKeys", + "type": "string[]", + "indexed": false, + "internalType": "string[]" + }, + { + "name": "metadataValues", + "type": "string[]", + "indexed": false, + "internalType": "string[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DataSetServiceProviderChanged", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "oldServiceProvider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newServiceProvider", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "FaultRecord", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "periodsFaulted", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FilBeamControllerChanged", + "inputs": [ + { + "name": "oldController", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newController", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FilecoinServiceDeployed", + "inputs": [ + { + "name": "name", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "description", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PDPPaymentTerminated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "endEpoch", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "pdpRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PieceAdded", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pieceId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pieceCid", + "type": "tuple", + "indexed": false, + "internalType": "struct Cids.Cid", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "keys", + "type": "string[]", + "indexed": false, + "internalType": "string[]" + }, + { + "name": "values", + "type": "string[]", + "indexed": false, + "internalType": "string[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PricingUpdated", + "inputs": [ + { + "name": "storagePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "minimumRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProviderApproved", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProviderUnapproved", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RailRateUpdated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "railId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ServiceTerminated", + "inputs": [ + { + "name": "caller", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "dataSetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pdpRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cacheMissRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "cdnRailId", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UpgradeAnnounced", + "inputs": [ + { + "name": "plannedUpgrade", + "type": "tuple", + "indexed": false, + "internalType": "struct FilecoinWarmStorageService.PlannedUpgrade", + "components": [ + { + "name": "nextImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "afterEpoch", + "type": "uint96", + "internalType": "uint96" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ViewContractSet", + "inputs": [ + { + "name": "viewContract", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AtLeastOnePriceMustBeNonZero", + "inputs": [] + }, + { + "type": "error", + "name": "CDNPaymentAlreadyTerminated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "CacheMissPaymentAlreadyTerminated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "CallerNotPayer", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expectedPayer", + "type": "address", + "internalType": "address" + }, + { + "name": "caller", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "CallerNotPayerOrPayee", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expectedPayer", + "type": "address", + "internalType": "address" + }, + { + "name": "expectedPayee", + "type": "address", + "internalType": "address" + }, + { + "name": "caller", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "CallerNotPayments", + "inputs": [ + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "actual", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ChallengeWindowTooEarly", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "windowStart", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "nowBlock", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ClientDataSetAlreadyRegistered", + "inputs": [ + { + "name": "clientDataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "CommissionExceedsMaximum", + "inputs": [ + { + "name": "commissionType", + "type": "uint8", + "internalType": "enum Errors.CommissionType" + }, + { + "name": "max", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "DataSetNotFoundForRail", + "inputs": [ + { + "name": "railId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "DataSetNotRegistered", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "DataSetPaymentAlreadyTerminated", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "DataSetPaymentBeyondEndEpoch", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pdpEndEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "currentBlock", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "DivisionByZero", + "inputs": [] + }, + { + "type": "error", + "name": "DuplicateMetadataKey", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "key", + "type": "string", + "internalType": "string" + } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "ExtraDataRequired", + "inputs": [] + }, + { + "type": "error", + "name": "ExtraDataTooLarge", + "inputs": [ + { + "name": "actualSize", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAllowedSize", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "FilBeamServiceNotConfigured", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InsufficientLockupAllowance", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "internalType": "address" + }, + { + "name": "lockupAllowance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "lockupUsage", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumLockupRequired", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InsufficientLockupFunds", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "minimumRequired", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "available", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InsufficientMaxLockupPeriod", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "internalType": "address" + }, + { + "name": "maxLockupPeriod", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "requiredLockupPeriod", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InsufficientRateAllowance", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "internalType": "address" + }, + { + "name": "rateAllowance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "rateUsage", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumRateRequired", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidChallengeCount", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minExpected", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidChallengeEpoch", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidChallengeWindowSize", + "inputs": [ + { + "name": "maxProvingPeriod", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "challengeWindowSize", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidDataSetId", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidEpochRange", + "inputs": [ + { + "name": "fromEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "toEpoch", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidServiceDescriptionLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidServiceNameLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidTopUpAmount", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "MaxProvingPeriodZero", + "inputs": [] + }, + { + "type": "error", + "name": "MetadataArrayCountMismatch", + "inputs": [ + { + "name": "metadataArrayCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceCount", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "MetadataKeyAndValueLengthMismatch", + "inputs": [ + { + "name": "keysLength", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "valuesLength", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "MetadataKeyExceedsMaxLength", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "MetadataValueExceedsMaxLength", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NextProvingPeriodAlreadyCalled", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "periodDeadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "nowBlock", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NoPDPPaymentRail", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyFilBeamControllerAllowed", + "inputs": [ + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "actual", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OnlyPDPVerifierAllowed", + "inputs": [ + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "actual", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OperatorNotApproved", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "PaymentRailsNotFinalized", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pdpEndEpoch", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "PriceExceedsMaximum", + "inputs": [ + { + "name": "priceType", + "type": "uint8", + "internalType": "enum Errors.PriceType" + }, + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actual", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProofAlreadySubmitted", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProviderAlreadyApproved", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProviderIdMismatchAtIndex", + "inputs": [ + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProviderNotInApprovedList", + "inputs": [ + { + "name": "providerId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProviderNotRegistered", + "inputs": [ + { + "name": "provider", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ProvingNotStarted", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ProvingPeriodPassed", + "inputs": [ + { + "name": "dataSetId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "nowBlock", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "RailNotAssociated", + "inputs": [ + { + "name": "railId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "RailNotFullySettled", + "inputs": [ + { + "name": "railId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "settledUpTo", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "endEpoch", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ServiceContractMustTerminateRail", + "inputs": [] + }, + { + "type": "error", + "name": "StorageProviderChangesNotSupported", + "inputs": [] + }, + { + "type": "error", + "name": "TooManyMetadataKeys", + "inputs": [ + { + "name": "maxAllowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "keysLength", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ZeroAddress", + "inputs": [ + { + "name": "field", + "type": "uint8", + "internalType": "enum Errors.AddressField" + } + ] + } +] \ No newline at end of file diff --git a/apps/subgraph/abis/PDPVerifier.json b/apps/subgraph/abis/PDPVerifier.json new file mode 100644 index 00000000..6f7fb361 --- /dev/null +++ b/apps/subgraph/abis/PDPVerifier.json @@ -0,0 +1,1266 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "MAX_ENQUEUED_REMOVALS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MAX_PIECE_SIZE_LOG2", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "NO_CHALLENGE_SCHEDULED", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "NO_PROVEN_EPOCH", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addPieces", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "listenerAddr", + "type": "address", + "internalType": "address" + }, + { + "name": "pieceData", + "type": "tuple[]", + "internalType": "struct Cids.Cid[]", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "calculateProofFee", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "calculateProofFeeForSize", + "inputs": [ + { + "name": "proofSize", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claimDataSetStorageProvider", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "createDataSet", + "inputs": [ + { + "name": "listenerAddr", + "type": "address", + "internalType": "address" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "dataSetLive", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "deleteDataSet", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "feeEffectiveTime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "feePerTiB", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint96", + "internalType": "uint96" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "findPieceIds", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "leafIndexs", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct IPDPTypes.PieceIdAndOffset[]", + "components": [ + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getActivePieceCount", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "activeCount", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getActivePieces", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limit", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "pieces", + "type": "tuple[]", + "internalType": "struct Cids.Cid[]", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "pieceIds", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "hasMore", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getChallengeFinality", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getChallengeRange", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDataSetLastProvenEpoch", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDataSetLeafCount", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDataSetListener", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDataSetStorageProvider", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getNextChallengeEpoch", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getNextDataSetId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getNextPieceId", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPieceCid", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Cids.Cid", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPieceLeafCount", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRandomness", + "inputs": [ + { + "name": "epoch", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getScheduledRemovals", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_challengeFinality", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "migrate", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nextProvingPeriod", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "challengeEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pieceChallengable", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pieceLive", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proposeDataSetStorageProvider", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "newStorageProvider", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proposedFeePerTiB", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint96", + "internalType": "uint96" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "provePossession", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "proofs", + "type": "tuple[]", + "internalType": "struct IPDPTypes.Proof[]", + "components": [ + { + "name": "leaf", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "proof", + "type": "bytes32[]", + "internalType": "bytes32[]" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "schedulePieceDeletions", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "pieceIds", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "extraData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateProofFee", + "inputs": [ + { + "name": "newFeePerTiB", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "ContractUpgraded", + "inputs": [ + { + "name": "version", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "implementation", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DataSetCreated", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "storageProvider", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DataSetDeleted", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "deletedLeafCount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DataSetEmpty", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeeUpdateProposed", + "inputs": [ + { + "name": "currentFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "effectiveTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "NextProvingPeriod", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "challengeEpoch", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "leafCount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PiecesAdded", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pieceIds", + "type": "uint256[]", + "indexed": false, + "internalType": "uint256[]" + }, + { + "name": "pieceCids", + "type": "tuple[]", + "indexed": false, + "internalType": "struct Cids.Cid[]", + "components": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PiecesRemoved", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "pieceIds", + "type": "uint256[]", + "indexed": false, + "internalType": "uint256[]" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PossessionProven", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "challenges", + "type": "tuple[]", + "indexed": false, + "internalType": "struct IPDPTypes.PieceIdAndOffset[]", + "components": [ + { + "name": "pieceId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProofFeePaid", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "StorageProviderChanged", + "inputs": [ + { + "name": "setId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "oldStorageProvider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newStorageProvider", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "IndexedError", + "inputs": [ + { + "name": "idx", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "msg", + "type": "string", + "internalType": "string" + } + ] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } +] diff --git a/apps/subgraph/networks.json b/apps/subgraph/networks.json new file mode 100644 index 00000000..93d77b0b --- /dev/null +++ b/apps/subgraph/networks.json @@ -0,0 +1,22 @@ +{ + "filecoin": { + "PDPVerifier": { + "address": "0xBADd0B92C1c71d02E7d520f64c0876538fa2557F", + "startBlock": 5441432 + }, + "FilecoinWarmStorageService": { + "address": "0x8408502033C418E1bbC97cE9ac48E5528F371A9f", + "startBlock": 5459617 + } + }, + "filecoin-testnet": { + "PDPVerifier": { + "address": "0x85e366Cf9DD2c0aE37E963d9556F5f4718d6417C", + "startBlock": 3140755 + }, + "FilecoinWarmStorageService": { + "address": "0x02925630df557F957f70E112bA06e50965417CA0", + "startBlock": 3141276 + } + } +} diff --git a/apps/subgraph/package.json b/apps/subgraph/package.json new file mode 100644 index 00000000..90e70cc7 --- /dev/null +++ b/apps/subgraph/package.json @@ -0,0 +1,22 @@ +{ + "name": "@dealbot/subgraph", + "private": true, + "license": "(MIT OR Apache-2.0)", + "scripts": { + "codegen": "graph codegen", + "build": "pnpm run build:mainnet", + "build:mainnet": "graph codegen && graph build --network filecoin", + "build:calibration": "graph codegen && graph build --network filecoin-testnet", + "deploy:mainnet": "goldsky subgraph deploy dealbot-mainnet/$VERSION --path ./build", + "deploy:calibration": "goldsky subgraph deploy dealbot-calibration/$VERSION --path ./build", + "test": "graph test" + }, + "dependencies": { + "@graphprotocol/graph-cli": "0.98.1", + "@graphprotocol/graph-ts": "0.38.2" + }, + "devDependencies": { + "assemblyscript": "0.19.23", + "matchstick-as": "0.6.0" + } +} diff --git a/apps/subgraph/schema.graphql b/apps/subgraph/schema.graphql new file mode 100644 index 00000000..91eadf36 --- /dev/null +++ b/apps/subgraph/schema.graphql @@ -0,0 +1,82 @@ +enum DataSetStatus { + EMPTY # Newly created dataset, no roots yet + READY # Dataset has roots and is ready for proving + PROVING # Proofs are currently expected for this dataset + DELETED # Dataset has been deleted on-chain +} + +type Provider @entity(immutable: false) { + id: Bytes! # address + address: Bytes! + # Proving-period tracking is strictly bound to the FWSS contract. + totalFaultedPeriods: BigInt! + totalProvingPeriods: BigInt! + # Derived relationship + proofSets: [DataSet!]! @derivedFrom(field: "owner") +} + +type DataSet @entity(immutable: false) { + id: Bytes! # setId + setId: BigInt! + owner: Provider! + # True iff the dataset has not been deleted (PDPVerifier.DataSetDeleted) + # and the FWSS service has not been terminated (FWSS.ServiceTerminated). + # Note: PDPPaymentTerminated does NOT affect this flag; clients compare + # pdpPaymentEndEpoch to current epoch themselves. + isActive: Boolean! + status: DataSetStatus! + # Block number of next deadline, 0 if not set. Dealbot filters on this + # to detect overdue proving periods. + nextDeadline: BigInt! + # Multiplier for proving period frequency, 0 if not set. + maxProvingPeriod: BigInt! + # Internal: flipped true by PossessionProven, reset false on each + # NextProvingPeriod. Drives the faultedPeriods counter on Provider; + # not directly queried by dealbot. + provenThisPeriod: Boolean! + + # ---- FWSS fields (null / false for non-FWSS datasets) ---- + # Populated by FWSS.DataSetCreated handler. + fwssPayer: Bytes # address of the payer + fwssServiceProvider: Bytes # address — may diverge from owner after transfers + + # Derived from FWSS.DataSetCreated metadataKeys. + withIPFSIndexing: Boolean! + + # Populated by FWSS.PDPPaymentTerminated handler. + # May be in the past (already terminated) or future (terminating). + # Does NOT flip isActive — clients compare to current epoch. + pdpPaymentEndEpoch: BigInt + + # Block timestamp at which this DataSet was first created. Set once on + # the creating event (whichever of FWSS.DataSetCreated or + # PDPVerifier.DataSetCreated fires first) and never updated. + createdAt: BigInt! + + # Derived relationship + roots: [Root!]! @derivedFrom(field: "proofSet") +} + +type Root @entity(immutable: false) { + id: Bytes! # setId-rootId + setId: BigInt! + rootId: BigInt! + rawSize: BigInt! + cid: Bytes! + removed: Boolean! + + # Populated by FWSS.PieceAdded handler (null for non-FWSS pieces). + ipfsRootCID: String + + # Block timestamp at which this Root was first added via PiecesAdded. + # Set once on creation and never updated. + createdAt: BigInt! + + # keccak256(id) computed once at insert time. Used by dealbot's anonymous + # retrieval check to draw a uniform-random piece via + # `orderBy: sampleKey, where: { sampleKey_gte: }, first: 1` + # — independent of creation order, dataset age, and corpus size. + sampleKey: Bytes! + + proofSet: DataSet! +} diff --git a/apps/subgraph/src/fwss.ts b/apps/subgraph/src/fwss.ts new file mode 100644 index 00000000..2e739b36 --- /dev/null +++ b/apps/subgraph/src/fwss.ts @@ -0,0 +1,91 @@ +import { BigInt, log } from "@graphprotocol/graph-ts"; +import { + DataSetCreated as DataSetCreatedEvent, + DataSetServiceProviderChanged as DataSetServiceProviderChangedEvent, + PDPPaymentTerminated as PDPPaymentTerminatedEvent, + PieceAdded as PieceAddedEvent, + ServiceTerminated as ServiceTerminatedEvent, +} from "../generated/FilecoinWarmStorageService/FilecoinWarmStorageService"; +import { DataSet, Root } from "../generated/schema"; +import { arrayContains, extractMetadataValue, getProofSetEntityId, getRootEntityId } from "./helpers"; +import { DataSetStatus } from "./types"; + +// ---- Handlers ------------------------------------------------------------- + +export function handleFwssDataSetCreated(event: DataSetCreatedEvent): void { + const id = getProofSetEntityId(event.params.dataSetId); + // FWSS.DataSetCreated fires BEFORE PDPVerifier's own DataSetCreated event + // (see PDPVerifier._createDataSet: listener callback runs first, THEN + // `emit DataSetCreated`). If no entity exists yet, create a stub with + // required defaults; pdp-verifier.handleDataSetCreated will run later in + // the same block and fill in PDPVerifier-level fields. Since handlers run + // sequentially and atomically within a block, no GraphQL query can observe + // that intermediate state. + let ds = DataSet.load(id); + if (ds == null) { + ds = new DataSet(id); + ds.setId = event.params.dataSetId; + // PDPVerifier-level non-null defaults; handleDataSetCreated will overwrite. + ds.owner = event.params.serviceProvider; + ds.isActive = true; + ds.status = DataSetStatus.EMPTY; + ds.nextDeadline = BigInt.zero(); + ds.maxProvingPeriod = BigInt.zero(); + ds.provenThisPeriod = false; + ds.createdAt = event.block.timestamp; + } + + ds.fwssPayer = event.params.payer; + ds.fwssServiceProvider = event.params.serviceProvider; + ds.withIPFSIndexing = arrayContains(event.params.metadataKeys, "withIPFSIndexing"); + ds.save(); +} + +export function handleFwssPieceAdded(event: PieceAddedEvent): void { + const root = Root.load(getRootEntityId(event.params.dataSetId, event.params.pieceId)); + if (root == null) { + log.warning("FWSS PieceAdded for unknown root {}-{}", [ + event.params.dataSetId.toString(), + event.params.pieceId.toString(), + ]); + return; + } + + root.ipfsRootCID = extractMetadataValue(event.params.keys, event.params.values, "ipfsRootCID"); + root.save(); +} + +export function handleFwssServiceTerminated(event: ServiceTerminatedEvent): void { + const ds = DataSet.load(getProofSetEntityId(event.params.dataSetId)); + if (ds == null) { + log.warning("FWSS ServiceTerminated for unknown dataSet {}", [event.params.dataSetId.toString()]); + return; + } + + ds.isActive = false; + ds.save(); +} + +export function handleFwssPdpPaymentTerminated(event: PDPPaymentTerminatedEvent): void { + const ds = DataSet.load(getProofSetEntityId(event.params.dataSetId)); + if (ds == null) { + log.warning("FWSS PDPPaymentTerminated for unknown dataSet {}", [event.params.dataSetId.toString()]); + return; + } + + ds.pdpPaymentEndEpoch = event.params.endEpoch; + ds.save(); +} + +export function handleFwssDataSetServiceProviderChanged(event: DataSetServiceProviderChangedEvent): void { + const ds = DataSet.load(getProofSetEntityId(event.params.dataSetId)); + if (ds == null) { + log.warning("FWSS DataSetServiceProviderChanged for unknown dataSet {}", [ + event.params.dataSetId.toString(), + ]); + return; + } + + ds.fwssServiceProvider = event.params.newServiceProvider; + ds.save(); +} diff --git a/apps/subgraph/src/helpers.ts b/apps/subgraph/src/helpers.ts new file mode 100644 index 00000000..e3350bdb --- /dev/null +++ b/apps/subgraph/src/helpers.ts @@ -0,0 +1,175 @@ +import { Address, BigInt, Bytes, crypto } from "@graphprotocol/graph-ts"; + +// ---- Entity ID helpers ---------------------------------------------------- + +export function getProofSetEntityId(setId: BigInt): Bytes { + return Bytes.fromByteArray(Bytes.fromBigInt(setId)); +} + +export function getRootEntityId(setId: BigInt, rootId: BigInt): Bytes { + return Bytes.fromUTF8(setId.toString() + "-" + rootId.toString()); +} + +// Uniform pseudorandom sort key for Root entities. Used by dealbot to draw +// random pieces fairly via `orderBy: sampleKey, where: { sampleKey_gte: X }`, +// which needs a key distributed independently of setId/rootId. +export function getRootSampleKey(rootEntityId: Bytes): Bytes { + return Bytes.fromByteArray(crypto.keccak256(rootEntityId)); +} + +// ---- FWSS metadata helpers ------------------------------------------------ + +export function arrayContains(arr: string[], needle: string): boolean { + for (let i = 0; i < arr.length; i++) { + if (arr[i] == needle) return true; + } + return false; +} + +export function extractMetadataValue(keys: string[], values: string[], needle: string): string | null { + for (let i = 0; i < keys.length; i++) { + if (keys[i] == needle) { + return i < values.length ? values[i] : null; + } + } + return null; +} + +// ---- Per-network proving period ------------------------------------------ +// +// NextProvingPeriod is emitted by the PDPVerifier, so event.address on that +// handler is the PDPVerifier contract. Each subgraph build targets a single +// network, so only the matching branch is live for a given deployment — the +// others are dead code on that build, kept explicit here so the mapping is +// discoverable in one place rather than hidden behind a build-time constant. +// mainnet: MaxProvingPeriod = 2880 +// calibration: MaxProvingPeriod = 240 + +const MAINNET_PDP_VERIFIER = Address.fromString("0xBADd0B92C1c71d02E7d520f64c0876538fa2557F"); +const CALIBRATION_PDP_VERIFIER = Address.fromString("0x85e366Cf9DD2c0aE37E963d9556F5f4718d6417C"); + +export function maxProvingPeriodFor(pdpVerifier: Address): i32 { + if (pdpVerifier.equals(MAINNET_PDP_VERIFIER)) return 2880; + if (pdpVerifier.equals(CALIBRATION_PDP_VERIFIER)) return 240; + // Conservative fallback for unknown deployments (matches calibration). + return 240; +} + +// ---- CommP v2 CID decoding ------------------------------------------------ + +export const COMMP_V2_PREFIX: u8[] = [0x01, 0x55, 0x91, 0x20]; + +export class CommPv2ValidationResult { + constructor( + public isValid: boolean, + public padding: BigInt = BigInt.zero(), + public height: u8 = 0, + public digestOffset: BigInt = BigInt.zero(), + ) {} +} + +export class UvarintResult { + constructor( + public isValid: boolean, + public value: BigInt = BigInt.zero(), + public offset: BigInt = BigInt.zero(), + ) {} +} + +export function readUvarint(data: Bytes, offset: BigInt): UvarintResult { + let offsetU32 = offset.toU32(); + + if (offsetU32 >= u32(data.length)) { + return new UvarintResult(false); + } + + let i: u32 = 0; + let value: u64 = u64(data[offsetU32] & 0x7f); + + while (data[offsetU32 + i] >= 0x80) { + i++; + + if (offsetU32 + i >= u32(data.length)) { + return new UvarintResult(false); + } + + if (i >= 10) { + return new UvarintResult(false); + } + + let nextByte = u64(data[offsetU32 + i] & 0x7f); + value = value | (nextByte << (i * 7)); + } + + i++; + return new UvarintResult(true, BigInt.fromU64(value), BigInt.fromU32(offsetU32 + i)); +} + +export function validateCommPv2(cidData: Bytes): CommPv2ValidationResult { + if (cidData.length < 4) { + return new CommPv2ValidationResult(false); + } + + for (let i: i32 = 0; i < 4; i++) { + if (cidData[i] != COMMP_V2_PREFIX[i]) { + return new CommPv2ValidationResult(false); + } + } + + let offset = BigInt.fromU32(4); + + if (offset.toU32() >= u32(cidData.length)) { + return new CommPv2ValidationResult(false); + } + + let mhLengthResult = readUvarint(cidData, offset); + if (!mhLengthResult.isValid) { + return new CommPv2ValidationResult(false); + } + + let mhLength = mhLengthResult.value; + offset = mhLengthResult.offset; + + if (mhLength.lt(BigInt.fromU32(34))) { + return new CommPv2ValidationResult(false); + } + + if (mhLength.plus(offset).notEqual(BigInt.fromU32(cidData.length))) { + return new CommPv2ValidationResult(false); + } + + if (offset.toU32() >= u32(cidData.length)) { + return new CommPv2ValidationResult(false); + } + + let paddingResult = readUvarint(cidData, offset); + if (!paddingResult.isValid) { + return new CommPv2ValidationResult(false); + } + + let padding = paddingResult.value; + offset = paddingResult.offset; + + if (offset.toU32() >= u32(cidData.length)) { + return new CommPv2ValidationResult(false); + } + + let height = cidData[offset.toU32()]; + offset = offset.plus(BigInt.fromU32(1)); + + return new CommPv2ValidationResult(true, padding, height, offset); +} + +export function unpaddedSize(padding: BigInt, height: u8): BigInt { + if (height > 58) { + return BigInt.zero(); + } + + const baseSize = BigInt.fromU32(127).leftShift(height - 2); + + if (padding.gt(baseSize)) { + return BigInt.zero(); + } + + return baseSize.minus(padding); +} diff --git a/apps/subgraph/src/pdp-verifier.ts b/apps/subgraph/src/pdp-verifier.ts new file mode 100644 index 00000000..fd99ed9e --- /dev/null +++ b/apps/subgraph/src/pdp-verifier.ts @@ -0,0 +1,232 @@ +import { BigInt, log } from "@graphprotocol/graph-ts"; +import { + DataSetCreated as DataSetCreatedEvent, + DataSetDeleted as DataSetDeletedEvent, + DataSetEmpty as DataSetEmptyEvent, + NextProvingPeriod as NextProvingPeriodEvent, + PiecesAdded as PiecesAddedEvent, + PiecesRemoved as PiecesRemovedEvent, + PossessionProven as PossessionProvenEvent, + StorageProviderChanged as StorageProviderChangedEvent, +} from "../generated/PDPVerifier/PDPVerifier"; +import { DataSet, Provider, Root } from "../generated/schema"; +import { + getProofSetEntityId, + getRootEntityId, + getRootSampleKey, + maxProvingPeriodFor, + unpaddedSize, + validateCommPv2, +} from "./helpers"; +import { DataSetStatus } from "./types"; + +// ---- Handlers ------------------------------------------------------------- + +export function handleDataSetCreated(event: DataSetCreatedEvent): void { + const proofSetEntityId = getProofSetEntityId(event.params.setId); + const providerEntityId = event.params.storageProvider; + + // FWSS.dataSetCreated fires BEFORE PDPVerifier's own DataSetCreated event + // (see PDPVerifier._createDataSet: listener callback runs, THEN `emit + // DataSetCreated`). If the listener is FWSS, handleFwssDataSetCreated has + // already created a stub entity with FWSS-layer fields populated. Load to + // preserve those fields. + let proofSet = DataSet.load(proofSetEntityId); + if (proofSet == null) { + proofSet = new DataSet(proofSetEntityId); + proofSet.withIPFSIndexing = false; + proofSet.createdAt = event.block.timestamp; + // fwssPayer, fwssServiceProvider, pdpPaymentEndEpoch are nullable. + } + proofSet.setId = event.params.setId; + proofSet.owner = providerEntityId; + proofSet.isActive = true; + proofSet.status = DataSetStatus.EMPTY; + proofSet.nextDeadline = BigInt.zero(); + proofSet.maxProvingPeriod = BigInt.zero(); + proofSet.provenThisPeriod = false; + proofSet.save(); + + let provider = Provider.load(providerEntityId); + if (provider == null) { + provider = new Provider(providerEntityId); + provider.address = event.params.storageProvider; + provider.totalFaultedPeriods = BigInt.zero(); + provider.totalProvingPeriods = BigInt.zero(); + provider.save(); + } +} + +export function handleDataSetDeleted(event: DataSetDeletedEvent): void { + const proofSet = DataSet.load(getProofSetEntityId(event.params.setId)); + if (proofSet == null) { + log.warning("DataSetDeleted: DataSet {} not found", [event.params.setId.toString()]); + return; + } + + proofSet.isActive = false; + proofSet.status = DataSetStatus.DELETED; + proofSet.nextDeadline = BigInt.zero(); + proofSet.save(); +} + +export function handleStorageProviderChanged(event: StorageProviderChangedEvent): void { + const proofSet = DataSet.load(getProofSetEntityId(event.params.setId)); + if (proofSet == null) { + log.warning("StorageProviderChanged: DataSet {} not found", [event.params.setId.toString()]); + return; + } + + const newProviderId = event.params.newStorageProvider; + let newProvider = Provider.load(newProviderId); + if (newProvider == null) { + newProvider = new Provider(newProviderId); + newProvider.address = newProviderId; + newProvider.totalFaultedPeriods = BigInt.zero(); + newProvider.totalProvingPeriods = BigInt.zero(); + newProvider.save(); + } + + proofSet.owner = newProviderId; + proofSet.save(); +} + +export function handleDataSetEmpty(event: DataSetEmptyEvent): void { + const proofSet = DataSet.load(getProofSetEntityId(event.params.setId)); + if (proofSet == null) { + log.warning("DataSetEmpty: DataSet {} not found", [event.params.setId.toString()]); + return; + } + + proofSet.status = DataSetStatus.EMPTY; + // Zero nextDeadline so the next PiecesAdded + NextProvingPeriod round + // re-enters the first-init branch and promotes to PROVING again. + proofSet.nextDeadline = BigInt.zero(); + proofSet.maxProvingPeriod = BigInt.zero(); + proofSet.provenThisPeriod = false; + proofSet.save(); +} + +export function handlePossessionProven(event: PossessionProvenEvent): void { + const proofSet = DataSet.load(getProofSetEntityId(event.params.setId)); + if (proofSet == null) { + log.warning("PossessionProven: DataSet {} not found", [event.params.setId.toString()]); + return; + } + + // Flip the flag so the next NextProvingPeriod classifies this period as + // proven rather than faulted. + proofSet.provenThisPeriod = true; + proofSet.save(); +} + +export function handleNextProvingPeriod(event: NextProvingPeriodEvent): void { + const setId = event.params.setId; + const currentBlockNumber = event.block.number; + + const proofSet = DataSet.load(getProofSetEntityId(setId)); + if (proofSet == null) { + log.warning("NextProvingPeriod: DataSet {} not found", [setId.toString()]); + return; + } + + let periodsSkipped: BigInt = BigInt.zero(); + let faultedPeriods: BigInt = BigInt.zero(); + let nextDeadline: BigInt; + + if (proofSet.nextDeadline.equals(BigInt.zero())) { + // First-init: promote to PROVING, seed maxProvingPeriod. + proofSet.status = DataSetStatus.PROVING; + proofSet.maxProvingPeriod = BigInt.fromI32(maxProvingPeriodFor(event.address)); + nextDeadline = currentBlockNumber.plus(proofSet.maxProvingPeriod); + } else { + if (currentBlockNumber.gt(proofSet.nextDeadline)) { + periodsSkipped = currentBlockNumber + .minus(proofSet.nextDeadline.plus(BigInt.fromI32(1))) + .div(proofSet.maxProvingPeriod); + } + nextDeadline = proofSet.nextDeadline.plus( + proofSet.maxProvingPeriod.times(periodsSkipped.plus(BigInt.fromI32(1))), + ); + faultedPeriods = proofSet.provenThisPeriod ? periodsSkipped : periodsSkipped.plus(BigInt.fromI32(1)); + } + + proofSet.nextDeadline = nextDeadline; + proofSet.provenThisPeriod = false; + proofSet.save(); + + const provider = Provider.load(proofSet.owner); + if (provider != null) { + provider.totalFaultedPeriods = provider.totalFaultedPeriods.plus(faultedPeriods); + provider.totalProvingPeriods = provider.totalProvingPeriods.plus(periodsSkipped.plus(BigInt.fromI32(1))); + provider.save(); + } +} + +export function handlePiecesAdded(event: PiecesAddedEvent): void { + const setId = event.params.setId; + const rootIdsFromEvent = event.params.pieceIds; + const pieceCidsFromEvent = event.params.pieceCids; + + const proofSet = DataSet.load(getProofSetEntityId(setId)); + if (proofSet == null) { + log.warning("handlePiecesAdded: DataSet {} not found", [setId.toString()]); + return; + } + + let addedAny = false; + + for (let i = 0; i < rootIdsFromEvent.length; i++) { + const rootId = rootIdsFromEvent[i]; + const pieceCid = pieceCidsFromEvent[i]; + + const pieceBytes = pieceCid.data; + const commPData = validateCommPv2(pieceBytes); + const rawSize = commPData.isValid ? unpaddedSize(commPData.padding, commPData.height) : BigInt.zero(); + + const rootEntityId = getRootEntityId(setId, rootId); + if (Root.load(rootEntityId) != null) { + log.warning("handlePiecesAdded: Root {} for Set {} already exists; skipping", [ + rootId.toString(), + setId.toString(), + ]); + continue; + } + + const root = new Root(rootEntityId); + root.setId = setId; + root.rootId = rootId; + root.rawSize = rawSize; + root.cid = pieceBytes; + root.removed = false; + root.createdAt = event.block.timestamp; + root.proofSet = getProofSetEntityId(setId); + root.sampleKey = getRootSampleKey(rootEntityId); + // ipfsRootCID: patched in FWSS handler if applicable. + root.save(); + + addedAny = true; + } + + // First non-empty add transitions the DataSet to READY. NextProvingPeriod + // will then promote it to PROVING. + if (addedAny && proofSet.status == DataSetStatus.EMPTY) { + proofSet.status = DataSetStatus.READY; + proofSet.save(); + } +} + +export function handlePiecesRemoved(event: PiecesRemovedEvent): void { + const setId = event.params.setId; + const rootIds = event.params.pieceIds; + + for (let i = 0; i < rootIds.length; i++) { + const root = Root.load(getRootEntityId(setId, rootIds[i])); + if (root == null) { + log.warning("handlePiecesRemoved: Root {} for Set {} not found", [rootIds[i].toString(), setId.toString()]); + continue; + } + root.removed = true; + root.save(); + } +} diff --git a/apps/subgraph/src/types.ts b/apps/subgraph/src/types.ts new file mode 100644 index 00000000..935ffdea --- /dev/null +++ b/apps/subgraph/src/types.ts @@ -0,0 +1,6 @@ +export class DataSetStatus { + static readonly EMPTY: string = "EMPTY"; + static readonly READY: string = "READY"; + static readonly PROVING: string = "PROVING"; + static readonly DELETED: string = "DELETED"; +} diff --git a/apps/subgraph/subgraph.yaml b/apps/subgraph/subgraph.yaml new file mode 100644 index 00000000..6f36ecdb --- /dev/null +++ b/apps/subgraph/subgraph.yaml @@ -0,0 +1,74 @@ +specVersion: 1.3.0 +indexerHints: + prune: auto +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: PDPVerifier + network: filecoin + source: + abi: PDPVerifier + address: "0xBADd0B92C1c71d02E7d520f64c0876538fa2557F" + startBlock: 5441432 + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + file: ./src/pdp-verifier.ts + entities: + - DataSet + - Provider + - Root + abis: + - name: PDPVerifier + file: ./abis/PDPVerifier.json + eventHandlers: + - event: DataSetCreated(indexed uint256,indexed address) + handler: handleDataSetCreated + - event: StorageProviderChanged(indexed uint256,indexed address,indexed address) + handler: handleStorageProviderChanged + - event: DataSetDeleted(indexed uint256,uint256) + handler: handleDataSetDeleted + - event: PiecesAdded(indexed uint256,uint256[],(bytes)[]) + handler: handlePiecesAdded + - event: PiecesRemoved(indexed uint256,uint256[]) + handler: handlePiecesRemoved + - event: DataSetEmpty(indexed uint256) + handler: handleDataSetEmpty + - event: PossessionProven(indexed uint256,(uint256,uint256)[]) + handler: handlePossessionProven + - event: NextProvingPeriod(indexed uint256,uint256,uint256) + handler: handleNextProvingPeriod + - kind: ethereum + name: FilecoinWarmStorageService + network: filecoin + source: + abi: FilecoinWarmStorageService + address: "0x8408502033C418E1bbC97cE9ac48E5528F371A9f" + startBlock: 5459617 + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + file: ./src/fwss.ts + entities: + - DataSet + - Root + abis: + - name: FilecoinWarmStorageService + file: ./abis/FilecoinWarmStorageService.json + eventHandlers: + - event: DataSetCreated(indexed uint256,indexed + uint256,uint256,uint256,uint256,address,address,address,string[],string[]) + handler: handleFwssDataSetCreated + - event: PieceAdded(indexed uint256,indexed uint256,(bytes),string[],string[]) + handler: handleFwssPieceAdded + - event: ServiceTerminated(indexed address,indexed + uint256,uint256,uint256,uint256) + handler: handleFwssServiceTerminated + - event: PDPPaymentTerminated(indexed uint256,uint256,uint256) + handler: handleFwssPdpPaymentTerminated + - event: DataSetServiceProviderChanged(indexed uint256,indexed address,indexed + address) + handler: handleFwssDataSetServiceProviderChanged diff --git a/apps/subgraph/tests/dataset-status.test.ts b/apps/subgraph/tests/dataset-status.test.ts new file mode 100644 index 00000000..9dea8c4b --- /dev/null +++ b/apps/subgraph/tests/dataset-status.test.ts @@ -0,0 +1,204 @@ +import { afterEach, assert, clearStore, describe, test } from "matchstick-as/assembly/index"; +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { + handleDataSetCreated, + handleDataSetDeleted, + handleDataSetEmpty, + handleNextProvingPeriod, + handlePiecesAdded, +} from "../src/pdp-verifier"; +import { + createDataSetCreatedEvent, + createDataSetDeletedEvent, + createDataSetEmptyEvent, + createNextProvingPeriodEvent, + createRootsAddedEvent, + generateTxHash, +} from "./pdp-verifier-utils"; + +const SET_ID = BigInt.fromI32(1); +const ROOT_ID_1 = BigInt.fromI32(101); +const SENDER_ADDRESS = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a"); +const CONTRACT_ADDRESS = Address.fromString("0xb16081f360e3847006db660bae1c6d1b2e17ec2b"); +const PROOF_SET_ID_BYTES = Bytes.fromBigInt(SET_ID); + +function createAndSubmitDataSet(txId: i32): void { + const event = createDataSetCreatedEvent( + SET_ID, + SENDER_ADDRESS, + Bytes.fromI32(123), + CONTRACT_ADDRESS, + BigInt.fromI32(100), + BigInt.fromI32(1678886400), + generateTxHash(txId), + BigInt.fromI32(0), + ); + handleDataSetCreated(event); +} + +function addRoots(txId: i32, blockNumber: i32): void { + const rootsEvent = createRootsAddedEvent(SET_ID, [ROOT_ID_1], SENDER_ADDRESS, CONTRACT_ADDRESS); + rootsEvent.block.timestamp = BigInt.fromI32(1678886500); + rootsEvent.block.number = BigInt.fromI32(blockNumber); + rootsEvent.logIndex = BigInt.fromI32(1); + rootsEvent.transaction.hash = generateTxHash(txId); + handlePiecesAdded(rootsEvent); +} + +function nextProvingPeriod(txId: i32, blockNumber: i32): void { + const event = createNextProvingPeriodEvent( + SET_ID, + BigInt.fromI32(blockNumber), + BigInt.fromI32(32), + CONTRACT_ADDRESS, + BigInt.fromI32(blockNumber), + BigInt.fromI32(1678886600), + generateTxHash(txId), + BigInt.fromI32(0), + ); + handleNextProvingPeriod(event); +} + +describe("DataSetStatus Lifecycle Tests", () => { + afterEach(() => { + clearStore(); + }); + + test("handleDataSetCreated sets status to EMPTY", () => { + createAndSubmitDataSet(1); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "EMPTY"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "true"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "0"); + }); + + test("handlePiecesAdded transitions status from EMPTY to READY", () => { + createAndSubmitDataSet(10); + addRoots(11, 150); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "READY"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "true"); + }); + + test("handleNextProvingPeriod transitions status from READY to PROVING", () => { + createAndSubmitDataSet(20); + addRoots(21, 150); + nextProvingPeriod(22, 200); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "PROVING"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "true"); + assert.fieldEquals("DataSet", dataSetId, "maxProvingPeriod", "240"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "440"); // 200 + 240 + }); + + test("handleDataSetDeleted transitions status to DELETED", () => { + createAndSubmitDataSet(30); + addRoots(31, 150); + + const dataSetDeletedEvent = createDataSetDeletedEvent( + SET_ID, + BigInt.fromI32(32), + CONTRACT_ADDRESS, + BigInt.fromI32(200), + BigInt.fromI32(1678886700), + generateTxHash(32), + BigInt.fromI32(0), + ); + handleDataSetDeleted(dataSetDeletedEvent); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "DELETED"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "false"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "0"); + }); + + test("handleDataSetEmpty transitions status to EMPTY", () => { + createAndSubmitDataSet(40); + addRoots(41, 150); + + const dataSetEmptyEvent = createDataSetEmptyEvent( + SET_ID, + CONTRACT_ADDRESS, + BigInt.fromI32(200), + BigInt.fromI32(1678886700), + generateTxHash(42), + BigInt.fromI32(0), + ); + handleDataSetEmpty(dataSetEmptyEvent); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "EMPTY"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "0"); + assert.fieldEquals("DataSet", dataSetId, "maxProvingPeriod", "0"); + }); + + test("handleDataSetDeleted from PROVING status transitions to DELETED", () => { + createAndSubmitDataSet(50); + addRoots(51, 150); + nextProvingPeriod(52, 200); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "PROVING"); + + const dataSetDeletedEvent = createDataSetDeletedEvent( + SET_ID, + BigInt.fromI32(32), + CONTRACT_ADDRESS, + BigInt.fromI32(250), + BigInt.fromI32(1678886800), + generateTxHash(53), + BigInt.fromI32(0), + ); + handleDataSetDeleted(dataSetDeletedEvent); + + assert.fieldEquals("DataSet", dataSetId, "status", "DELETED"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "false"); + }); + + test("Lifecycle: EMPTY → READY → PROVING → EMPTY → READY → PROVING", () => { + // 1. Create (EMPTY) + createAndSubmitDataSet(90); + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "status", "EMPTY"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "0"); + + // 2. Add roots (READY) + addRoots(91, 150); + assert.fieldEquals("DataSet", dataSetId, "status", "READY"); + + // 3. NextProvingPeriod (PROVING) + nextProvingPeriod(92, 200); + assert.fieldEquals("DataSet", dataSetId, "status", "PROVING"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "440"); + + // 4. DataSetEmpty (EMPTY) — resets deadline to 0 so next NPP re-seeds. + const dataSetEmptyEvent = createDataSetEmptyEvent( + SET_ID, + CONTRACT_ADDRESS, + BigInt.fromI32(250), + BigInt.fromI32(1678886700), + generateTxHash(93), + BigInt.fromI32(0), + ); + handleDataSetEmpty(dataSetEmptyEvent); + assert.fieldEquals("DataSet", dataSetId, "status", "EMPTY"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "0"); + + // 5. Add roots again (READY) + const rootsEvent = createRootsAddedEvent(SET_ID, [BigInt.fromI32(201)], SENDER_ADDRESS, CONTRACT_ADDRESS); + rootsEvent.block.number = BigInt.fromI32(300); + rootsEvent.logIndex = BigInt.fromI32(1); + rootsEvent.transaction.hash = generateTxHash(94); + handlePiecesAdded(rootsEvent); + assert.fieldEquals("DataSet", dataSetId, "status", "READY"); + + // 6. NextProvingPeriod (PROVING) — first-init branch runs again since nextDeadline was zeroed. + nextProvingPeriod(95, 350); + assert.fieldEquals("DataSet", dataSetId, "status", "PROVING"); + assert.fieldEquals("DataSet", dataSetId, "nextDeadline", "590"); // 350 + 240 + assert.fieldEquals("DataSet", dataSetId, "maxProvingPeriod", "240"); + }); +}); diff --git a/apps/subgraph/tests/fwss-utils.ts b/apps/subgraph/tests/fwss-utils.ts new file mode 100644 index 00000000..e5db3137 --- /dev/null +++ b/apps/subgraph/tests/fwss-utils.ts @@ -0,0 +1,246 @@ +import { newMockEvent } from "matchstick-as"; +import { ethereum, BigInt, Address, Bytes } from "@graphprotocol/graph-ts"; +import { + DataSetCreated as FwssDataSetCreated, + PieceAdded as FwssPieceAdded, + ServiceTerminated as FwssServiceTerminated, + PDPPaymentTerminated as FwssPdpPaymentTerminated, + DataSetServiceProviderChanged as FwssDataSetServiceProviderChanged, +} from "../generated/FilecoinWarmStorageService/FilecoinWarmStorageService"; + +// FWSS DataSetCreated: +// dataSetId, providerId, pdpRailId, cacheMissRailId, cdnRailId, +// payer, serviceProvider, payee, metadataKeys[], metadataValues[] +export function createFwssDataSetCreatedEvent( + dataSetId: BigInt, + providerId: BigInt, + pdpRailId: BigInt, + payer: Address, + serviceProvider: Address, + metadataKeys: string[], + metadataValues: string[], + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1) +): FwssDataSetCreated { + let ev = changetype(newMockEvent()); + ev.parameters = new Array(); + + ev.parameters.push( + new ethereum.EventParam( + "dataSetId", + ethereum.Value.fromUnsignedBigInt(dataSetId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "providerId", + ethereum.Value.fromUnsignedBigInt(providerId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "pdpRailId", + ethereum.Value.fromUnsignedBigInt(pdpRailId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "cacheMissRailId", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(0)) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "cdnRailId", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(0)) + ) + ); + ev.parameters.push( + new ethereum.EventParam("payer", ethereum.Value.fromAddress(payer)) + ); + ev.parameters.push( + new ethereum.EventParam( + "serviceProvider", + ethereum.Value.fromAddress(serviceProvider) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "payee", + ethereum.Value.fromAddress(serviceProvider) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "metadataKeys", + ethereum.Value.fromStringArray(metadataKeys) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "metadataValues", + ethereum.Value.fromStringArray(metadataValues) + ) + ); + + ev.block.number = blockNumber; + ev.block.timestamp = timestamp; + return ev; +} + +// FWSS PieceAdded: dataSetId, pieceId, Cids.Cid (tuple(bytes)), keys[], values[] +export function createFwssPieceAddedEvent( + dataSetId: BigInt, + pieceId: BigInt, + pieceCidBytes: Bytes, + keys: string[], + values: string[], + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1) +): FwssPieceAdded { + let ev = changetype(newMockEvent()); + ev.parameters = new Array(); + + ev.parameters.push( + new ethereum.EventParam( + "dataSetId", + ethereum.Value.fromUnsignedBigInt(dataSetId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "pieceId", + ethereum.Value.fromUnsignedBigInt(pieceId) + ) + ); + + let cidTuple = new ethereum.Tuple(); + cidTuple.push(ethereum.Value.fromBytes(pieceCidBytes)); + ev.parameters.push( + new ethereum.EventParam("pieceCid", ethereum.Value.fromTuple(cidTuple)) + ); + + ev.parameters.push( + new ethereum.EventParam("keys", ethereum.Value.fromStringArray(keys)) + ); + ev.parameters.push( + new ethereum.EventParam("values", ethereum.Value.fromStringArray(values)) + ); + + ev.block.number = blockNumber; + ev.block.timestamp = timestamp; + return ev; +} + +// FWSS ServiceTerminated: caller, dataSetId, pdpRailId, cacheMissRailId, cdnRailId +export function createFwssServiceTerminatedEvent( + dataSetId: BigInt, + caller: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1) +): FwssServiceTerminated { + let ev = changetype(newMockEvent()); + ev.parameters = new Array(); + + ev.parameters.push( + new ethereum.EventParam("caller", ethereum.Value.fromAddress(caller)) + ); + ev.parameters.push( + new ethereum.EventParam( + "dataSetId", + ethereum.Value.fromUnsignedBigInt(dataSetId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "pdpRailId", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(0)) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "cacheMissRailId", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(0)) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "cdnRailId", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(0)) + ) + ); + + ev.block.number = blockNumber; + ev.block.timestamp = timestamp; + return ev; +} + +// FWSS PDPPaymentTerminated: dataSetId, endEpoch, pdpRailId +export function createFwssPdpPaymentTerminatedEvent( + dataSetId: BigInt, + endEpoch: BigInt, + pdpRailId: BigInt, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1) +): FwssPdpPaymentTerminated { + let ev = changetype(newMockEvent()); + ev.parameters = new Array(); + + ev.parameters.push( + new ethereum.EventParam( + "dataSetId", + ethereum.Value.fromUnsignedBigInt(dataSetId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "endEpoch", + ethereum.Value.fromUnsignedBigInt(endEpoch) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "pdpRailId", + ethereum.Value.fromUnsignedBigInt(pdpRailId) + ) + ); + + ev.block.number = blockNumber; + ev.block.timestamp = timestamp; + return ev; +} + +// FWSS DataSetServiceProviderChanged: dataSetId, oldServiceProvider, newServiceProvider +export function createFwssDataSetServiceProviderChangedEvent( + dataSetId: BigInt, + oldServiceProvider: Address, + newServiceProvider: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1) +): FwssDataSetServiceProviderChanged { + let ev = changetype(newMockEvent()); + ev.parameters = new Array(); + + ev.parameters.push( + new ethereum.EventParam( + "dataSetId", + ethereum.Value.fromUnsignedBigInt(dataSetId) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "oldServiceProvider", + ethereum.Value.fromAddress(oldServiceProvider) + ) + ); + ev.parameters.push( + new ethereum.EventParam( + "newServiceProvider", + ethereum.Value.fromAddress(newServiceProvider) + ) + ); + + ev.block.number = blockNumber; + ev.block.timestamp = timestamp; + return ev; +} diff --git a/apps/subgraph/tests/fwss.test.ts b/apps/subgraph/tests/fwss.test.ts new file mode 100644 index 00000000..d63d7db4 --- /dev/null +++ b/apps/subgraph/tests/fwss.test.ts @@ -0,0 +1,282 @@ +import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index"; +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { + handleFwssDataSetCreated, + handleFwssDataSetServiceProviderChanged, + handleFwssPdpPaymentTerminated, + handleFwssPieceAdded, + handleFwssServiceTerminated, +} from "../src/fwss"; +import { getRootEntityId } from "../src/helpers"; +import { handleDataSetCreated, handlePiecesAdded } from "../src/pdp-verifier"; +import { createDataSetCreatedEvent, createRootsAddedEvent } from "./pdp-verifier-utils"; +import { + createFwssDataSetCreatedEvent, + createFwssDataSetServiceProviderChangedEvent, + createFwssPdpPaymentTerminatedEvent, + createFwssPieceAddedEvent, + createFwssServiceTerminatedEvent, +} from "./fwss-utils"; + +const SET_ID = BigInt.fromI32(1); +const PROVIDER_ID = BigInt.fromI32(42); +const PDP_RAIL_ID = BigInt.fromI32(99); +const ROOT_ID = BigInt.fromI32(101); +const PROVIDER_ADDRESS = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a"); +const PAYER_ADDRESS = Address.fromString("0xb16081f360e3847006db660bae1c6d1b2e17ec2b"); +const NEW_PROVIDER_ADDRESS = Address.fromString("0xc16081f360e3847006db660bae1c6d1b2e17ec2c"); +const CONTRACT_ADDRESS = Address.fromString("0xd16081f360e3847006db660bae1c6d1b2e17ec2d"); + +const PROOF_SET_ENTITY_ID = Bytes.fromByteArray(Bytes.fromBigInt(SET_ID)); + +function seedDataSet(): void { + const ev = createDataSetCreatedEvent(SET_ID, PROVIDER_ADDRESS, Bytes.fromI32(0), CONTRACT_ADDRESS); + handleDataSetCreated(ev); +} + +function seedRoot(): void { + const ev = createRootsAddedEvent(SET_ID, [ROOT_ID], PROVIDER_ADDRESS, CONTRACT_ADDRESS); + handlePiecesAdded(ev); +} + +describe("FWSS handlers", () => { + beforeEach(() => { + clearStore(); + }); + + // -- handleFwssDataSetCreated ------------------------------------------- + + test("PDPVerifier-created DataSet has withIPFSIndexing = false by default", () => { + seedDataSet(); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "withIPFSIndexing", "false"); + }); + + test("handleFwssDataSetCreated populates FWSS fields and derives withIPFSIndexing", () => { + seedDataSet(); + const ev = createFwssDataSetCreatedEvent( + SET_ID, + PROVIDER_ID, + PDP_RAIL_ID, + PAYER_ADDRESS, + PROVIDER_ADDRESS, + ["source", "withIPFSIndexing", "withCDN"], + ["filecoin-pin", "", "true"], + ); + handleFwssDataSetCreated(ev); + + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "fwssPayer", PAYER_ADDRESS.toHexString()); + assert.fieldEquals( + "DataSet", + PROOF_SET_ENTITY_ID.toHexString(), + "fwssServiceProvider", + PROVIDER_ADDRESS.toHexString(), + ); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "withIPFSIndexing", "true"); + }); + + test("handleFwssDataSetCreated leaves withIPFSIndexing false when key absent", () => { + seedDataSet(); + const ev = createFwssDataSetCreatedEvent( + SET_ID, + PROVIDER_ID, + PDP_RAIL_ID, + PAYER_ADDRESS, + PROVIDER_ADDRESS, + ["source"], + ["filecoin-pin"], + ); + handleFwssDataSetCreated(ev); + + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "withIPFSIndexing", "false"); + }); + + test("handleFwssDataSetCreated creates a stub when DataSet doesn't exist yet", () => { + // FWSS.DataSetCreated fires BEFORE PDPVerifier.DataSetCreated in the same + // tx (see PDPVerifier._createDataSet). When our handler runs first, it + // must create a stub with FWSS fields set so the later PDPVerifier handler + // can load it instead of overwriting. + const UNSEEN_SET_ID = BigInt.fromI32(999); + const unseenEntityId = Bytes.fromByteArray(Bytes.fromBigInt(UNSEEN_SET_ID)).toHexString(); + + const ev = createFwssDataSetCreatedEvent( + UNSEEN_SET_ID, + PROVIDER_ID, + PDP_RAIL_ID, + PAYER_ADDRESS, + PROVIDER_ADDRESS, + ["withIPFSIndexing"], + [""], + ); + handleFwssDataSetCreated(ev); + + assert.fieldEquals("DataSet", unseenEntityId, "setId", "999"); + assert.fieldEquals("DataSet", unseenEntityId, "fwssPayer", PAYER_ADDRESS.toHexString()); + assert.fieldEquals("DataSet", unseenEntityId, "withIPFSIndexing", "true"); + // Placeholder owner set by the FWSS stub (pdp-verifier overwrites later in the same block). + assert.fieldEquals("DataSet", unseenEntityId, "owner", PROVIDER_ADDRESS.toHexString()); + }); + + test("FWSS-then-PDPVerifier ordering preserves both field groups", () => { + // Simulates real on-chain ordering: FWSS.DataSetCreated fires before + // PDPVerifier.DataSetCreated. After both handlers run, FWSS and + // PDPVerifier fields must both be populated correctly. + const fwssEv = createFwssDataSetCreatedEvent( + SET_ID, + PROVIDER_ID, + PDP_RAIL_ID, + PAYER_ADDRESS, + PROVIDER_ADDRESS, + ["withIPFSIndexing"], + [""], + ); + handleFwssDataSetCreated(fwssEv); + + const pdpEv = createDataSetCreatedEvent(SET_ID, PROVIDER_ADDRESS, Bytes.fromI32(0), CONTRACT_ADDRESS); + handleDataSetCreated(pdpEv); + + // FWSS fields preserved. + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "withIPFSIndexing", "true"); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "fwssPayer", PAYER_ADDRESS.toHexString()); + // PDPVerifier fields set. + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "setId", SET_ID.toString()); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "isActive", "true"); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "status", "EMPTY"); + }); + + // -- handleFwssPieceAdded ----------------------------------------------- + + test("handleFwssPieceAdded extracts ipfsRootCID", () => { + seedDataSet(); + seedRoot(); + const ev = createFwssPieceAddedEvent( + SET_ID, + ROOT_ID, + Bytes.fromHexString("0xdeadbeef"), + ["ipfsRootCID"], + ["bafybeiexamplecid"], + ); + handleFwssPieceAdded(ev); + + const rootId = getRootEntityId(SET_ID, ROOT_ID).toHexString(); + assert.fieldEquals("Root", rootId, "ipfsRootCID", "bafybeiexamplecid"); + }); + + test("handleFwssPieceAdded no-ops for unknown pieceId", () => { + seedDataSet(); + // no seedRoot — root doesn't exist. + const ev = createFwssPieceAddedEvent( + SET_ID, + BigInt.fromI32(999), + Bytes.fromHexString("0xdeadbeef"), + ["ipfsRootCID"], + ["bafybeinope"], + ); + handleFwssPieceAdded(ev); + + const rootId = getRootEntityId(SET_ID, BigInt.fromI32(999)).toHexString(); + assert.notInStore("Root", rootId); + }); + + // -- handleFwssServiceTerminated ---------------------------------------- + + test("handleFwssServiceTerminated flips isActive to false", () => { + seedDataSet(); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "isActive", "true"); + + const ev = createFwssServiceTerminatedEvent(SET_ID, PROVIDER_ADDRESS); + handleFwssServiceTerminated(ev); + + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "isActive", "false"); + }); + + test("handleFwssServiceTerminated no-ops for unknown dataSetId", () => { + const ev = createFwssServiceTerminatedEvent(BigInt.fromI32(999), PROVIDER_ADDRESS); + handleFwssServiceTerminated(ev); + assert.notInStore("DataSet", Bytes.fromByteArray(Bytes.fromBigInt(BigInt.fromI32(999))).toHexString()); + }); + + // -- handleFwssPdpPaymentTerminated ------------------------------------- + + test("handleFwssPdpPaymentTerminated stores endEpoch and leaves isActive alone", () => { + seedDataSet(); + const ev = createFwssPdpPaymentTerminatedEvent(SET_ID, BigInt.fromI32(12345), PDP_RAIL_ID); + handleFwssPdpPaymentTerminated(ev); + + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "pdpPaymentEndEpoch", "12345"); + assert.fieldEquals("DataSet", PROOF_SET_ENTITY_ID.toHexString(), "isActive", "true"); + }); + + test("handleFwssPdpPaymentTerminated no-ops for unknown dataSetId", () => { + const ev = createFwssPdpPaymentTerminatedEvent(BigInt.fromI32(999), BigInt.fromI32(12345), PDP_RAIL_ID); + handleFwssPdpPaymentTerminated(ev); + assert.notInStore("DataSet", Bytes.fromByteArray(Bytes.fromBigInt(BigInt.fromI32(999))).toHexString()); + }); + + // -- handleFwssDataSetServiceProviderChanged ---------------------------- + + test("handleFwssDataSetServiceProviderChanged updates fwssServiceProvider", () => { + seedDataSet(); + handleFwssDataSetCreated( + createFwssDataSetCreatedEvent(SET_ID, PROVIDER_ID, PDP_RAIL_ID, PAYER_ADDRESS, PROVIDER_ADDRESS, [], []), + ); + + const ev = createFwssDataSetServiceProviderChangedEvent(SET_ID, PROVIDER_ADDRESS, NEW_PROVIDER_ADDRESS); + handleFwssDataSetServiceProviderChanged(ev); + + assert.fieldEquals( + "DataSet", + PROOF_SET_ENTITY_ID.toHexString(), + "fwssServiceProvider", + NEW_PROVIDER_ADDRESS.toHexString(), + ); + }); + + test("handleFwssDataSetServiceProviderChanged no-ops for unknown dataSetId", () => { + const ev = createFwssDataSetServiceProviderChangedEvent( + BigInt.fromI32(999), + PROVIDER_ADDRESS, + NEW_PROVIDER_ADDRESS, + ); + handleFwssDataSetServiceProviderChanged(ev); + assert.notInStore("DataSet", Bytes.fromByteArray(Bytes.fromBigInt(BigInt.fromI32(999))).toHexString()); + }); + + // -- End-to-end: backs GET_FWSS_CANDIDATE_PIECES query ------------------ + + test("GET_FWSS_CANDIDATE_PIECES: DataSet + Root populated with filterable fields", () => { + // FWSS stub first (matches on-chain ordering). + const fwssDsEv = createFwssDataSetCreatedEvent( + SET_ID, + PROVIDER_ID, + PDP_RAIL_ID, + PAYER_ADDRESS, + PROVIDER_ADDRESS, + ["withIPFSIndexing"], + [""], + ); + handleFwssDataSetCreated(fwssDsEv); + + // PDPVerifier DataSetCreated fills in PDPVerifier-layer fields. + handleDataSetCreated(createDataSetCreatedEvent(SET_ID, PROVIDER_ADDRESS, Bytes.fromI32(0), CONTRACT_ADDRESS)); + + // PiecesAdded creates Root. + handlePiecesAdded(createRootsAddedEvent(SET_ID, [ROOT_ID], PROVIDER_ADDRESS, CONTRACT_ADDRESS)); + + // FWSS.PieceAdded adds ipfsRootCID. + handleFwssPieceAdded( + createFwssPieceAddedEvent(SET_ID, ROOT_ID, Bytes.fromHexString("0xdeadbeef"), ["ipfsRootCID"], [ + "bafybeiexamplecid", + ]), + ); + + const dsId = PROOF_SET_ENTITY_ID.toHexString(); + assert.fieldEquals("DataSet", dsId, "isActive", "true"); + assert.fieldEquals("DataSet", dsId, "withIPFSIndexing", "true"); + assert.fieldEquals("DataSet", dsId, "fwssPayer", PAYER_ADDRESS.toHexString()); + assert.fieldEquals("DataSet", dsId, "fwssServiceProvider", PROVIDER_ADDRESS.toHexString()); + + const rootId = getRootEntityId(SET_ID, ROOT_ID).toHexString(); + assert.fieldEquals("Root", rootId, "removed", "false"); + assert.fieldEquals("Root", rootId, "ipfsRootCID", "bafybeiexamplecid"); + }); +}); diff --git a/apps/subgraph/tests/pdp-verifier-utils.ts b/apps/subgraph/tests/pdp-verifier-utils.ts new file mode 100644 index 00000000..d3e37a29 --- /dev/null +++ b/apps/subgraph/tests/pdp-verifier-utils.ts @@ -0,0 +1,273 @@ +import { newMockEvent } from "matchstick-as"; +import { ethereum, BigInt, Address, Bytes } from "@graphprotocol/graph-ts"; +import { + DataSetCreated, + PiecesAdded, + NextProvingPeriod, + PossessionProven, + DataSetDeleted, + DataSetEmpty, +} from "../generated/PDPVerifier/PDPVerifier"; + +// Helper to generate unique transaction hash from a counter +export function generateTxHash(counter: i32): Bytes { + const hexCounter = counter.toString(16).padStart(64, "0"); + return Bytes.fromHexString("0x" + hexCounter); +} + +// Mocks the DataSetCreated event +// event DataSetCreated(uint256 indexed setId, address indexed provider, bytes32 root); +export function createDataSetCreatedEvent( + setId: BigInt, + provider: Address, + root: Bytes, // Although root is part of the event, handleDataSetCreated might not use it directly + contractAddress: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(1), + logIndex: BigInt = BigInt.fromI32(0) +): DataSetCreated { + let DataSetCreatedEvent = changetype(newMockEvent()); + + DataSetCreatedEvent.parameters = new Array(); + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + let providerParam = new ethereum.EventParam( + "provider", + ethereum.Value.fromAddress(provider) + ); + let rootParam = new ethereum.EventParam( + "root", + ethereum.Value.fromFixedBytes(root) + ); + + DataSetCreatedEvent.parameters.push(setIdParam); + DataSetCreatedEvent.parameters.push(providerParam); + DataSetCreatedEvent.parameters.push(rootParam); + + DataSetCreatedEvent.address = contractAddress; + DataSetCreatedEvent.block.number = blockNumber; + DataSetCreatedEvent.block.timestamp = timestamp; + DataSetCreatedEvent.transaction.hash = txHash; + DataSetCreatedEvent.logIndex = logIndex; + + // Transaction input is not strictly needed if the handler only uses event.params + // DataSetCreatedEvent.transaction.input = Bytes.fromI32(0); + + return DataSetCreatedEvent; +} + +export function createRootsAddedEvent( + setId: BigInt, + pieceIds: BigInt[], + sender: Address, + contractAddress: Address +): PiecesAdded { + let rootsAddedEvent = changetype(newMockEvent()); + + rootsAddedEvent.parameters = new Array(); + rootsAddedEvent.address = contractAddress; + rootsAddedEvent.transaction.from = sender; + rootsAddedEvent.transaction.to = contractAddress; + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + let rootIdsParam = new ethereum.EventParam( + "pieceIds", + ethereum.Value.fromUnsignedBigIntArray(pieceIds) + ); + + let pieceCids: Array = []; + for (let i = 0; i < pieceIds.length; i++) { + let cidTuple = new ethereum.Tuple(); + let cidData = Bytes.fromHexString( + "0x01559120258ff7f7021387dcea7164b7d1c4a98bd6f8d3c187e3114795efa391df307c8aa9d5d5cbac03" + ); + cidTuple.push(ethereum.Value.fromBytes(cidData)); + pieceCids.push(cidTuple); + } + + let pieceCidsParam = new ethereum.EventParam( + "pieceCids", + ethereum.Value.fromTupleArray(pieceCids) + ); + + rootsAddedEvent.parameters.push(setIdParam); + rootsAddedEvent.parameters.push(rootIdsParam); + rootsAddedEvent.parameters.push(pieceCidsParam); + + let txInputHex = + "0x9afd37f20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002a01559120258ff7f7021387dcea7164b7d1c4a98bd6f8d3c187e3114795efa391df307c8aa9d5d5cbac030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a05f13cbf0c320f1092664967af5de13e4abe964d4f755c0d4cffe18a146f395030000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b69706673526f6f744349440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b626166796265696537696d32766e766870347a726d6778776c6b336d6133736f736f6e743765367776726f63336134756261707a7a7a3368796a75000000000000000000000000000000000000000000000000000000000000000000000000411b4c7e389fe7383d20d251599c194c9ddb3e71d79c2c1b44fe15b0f505aea92e525239a7e91647c64370054fe8a779486342fafb8971a7eb69101c97368c4bf61b00000000000000000000000000000000000000000000000000000000000000"; + let txInput = Bytes.fromHexString(txInputHex); + rootsAddedEvent.transaction.input = txInput; + + rootsAddedEvent.block.number = BigInt.fromI32(1); + rootsAddedEvent.block.timestamp = BigInt.fromI32(1); + + return rootsAddedEvent; +} + +export function createNextProvingPeriodEvent( + setId: BigInt, + challengeEpoch: BigInt, + leafCount: BigInt, + contractAddress: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(2), + logIndex: BigInt = BigInt.fromI32(0) +): NextProvingPeriod { + let nextProvingPeriodEvent = changetype(newMockEvent()); + + nextProvingPeriodEvent.parameters = new Array(); + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + let challengeEpochParam = new ethereum.EventParam( + "challengeEpoch", + ethereum.Value.fromUnsignedBigInt(challengeEpoch) + ); + let leafCountParam = new ethereum.EventParam( + "leafCount", + ethereum.Value.fromUnsignedBigInt(leafCount) + ); + + nextProvingPeriodEvent.parameters.push(setIdParam); + nextProvingPeriodEvent.parameters.push(challengeEpochParam); + nextProvingPeriodEvent.parameters.push(leafCountParam); + + nextProvingPeriodEvent.address = contractAddress; + nextProvingPeriodEvent.block.number = blockNumber; + nextProvingPeriodEvent.block.timestamp = timestamp; + nextProvingPeriodEvent.transaction.hash = txHash; + nextProvingPeriodEvent.logIndex = logIndex; + + return nextProvingPeriodEvent; +} + +export function createPossessionProvenEvent( + setId: BigInt, + pieceIds: BigInt[], + offsets: BigInt[], + contractAddress: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(3), + logIndex: BigInt = BigInt.fromI32(0) +): PossessionProven { + if (pieceIds.length !== offsets.length) { + throw new Error( + `createPossessionProvenEvent: pieceIds.length (${pieceIds.length}) must equal offsets.length (${offsets.length})` + ); + } + + let possessionProvenEvent = changetype(newMockEvent()); + + possessionProvenEvent.parameters = new Array(); + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + + let challenges: Array = []; + for (let i = 0; i < pieceIds.length; i++) { + let challenge = new ethereum.Tuple(); + challenge.push(ethereum.Value.fromUnsignedBigInt(pieceIds[i])); + challenge.push(ethereum.Value.fromUnsignedBigInt(offsets[i])); + challenges.push(challenge); + } + + let challengesParam = new ethereum.EventParam( + "challenges", + ethereum.Value.fromTupleArray(challenges) + ); + + possessionProvenEvent.parameters.push(setIdParam); + possessionProvenEvent.parameters.push(challengesParam); + + possessionProvenEvent.address = contractAddress; + possessionProvenEvent.block.number = blockNumber; + possessionProvenEvent.block.timestamp = timestamp; + possessionProvenEvent.transaction.hash = txHash; + possessionProvenEvent.logIndex = logIndex; + + return possessionProvenEvent; +} + +export function createDataSetDeletedEvent( + setId: BigInt, + deletedLeafCount: BigInt, + contractAddress: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(4), + logIndex: BigInt = BigInt.fromI32(0) +): DataSetDeleted { + let dataSetDeletedEvent = changetype(newMockEvent()); + + dataSetDeletedEvent.parameters = new Array(); + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + let deletedLeafCountParam = new ethereum.EventParam( + "deletedLeafCount", + ethereum.Value.fromUnsignedBigInt(deletedLeafCount) + ); + + dataSetDeletedEvent.parameters.push(setIdParam); + dataSetDeletedEvent.parameters.push(deletedLeafCountParam); + + dataSetDeletedEvent.address = contractAddress; + dataSetDeletedEvent.block.number = blockNumber; + dataSetDeletedEvent.block.timestamp = timestamp; + dataSetDeletedEvent.transaction.hash = txHash; + dataSetDeletedEvent.logIndex = logIndex; + dataSetDeletedEvent.transaction.from = Address.fromString( + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a" + ); + dataSetDeletedEvent.transaction.to = contractAddress; + + return dataSetDeletedEvent; +} + +export function createDataSetEmptyEvent( + setId: BigInt, + contractAddress: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(5), + logIndex: BigInt = BigInt.fromI32(0) +): DataSetEmpty { + let dataSetEmptyEvent = changetype(newMockEvent()); + + dataSetEmptyEvent.parameters = new Array(); + + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + + dataSetEmptyEvent.parameters.push(setIdParam); + + dataSetEmptyEvent.address = contractAddress; + dataSetEmptyEvent.block.number = blockNumber; + dataSetEmptyEvent.block.timestamp = timestamp; + dataSetEmptyEvent.transaction.hash = txHash; + dataSetEmptyEvent.logIndex = logIndex; + dataSetEmptyEvent.transaction.from = Address.fromString( + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a" + ); + dataSetEmptyEvent.transaction.to = contractAddress; + + return dataSetEmptyEvent; +} diff --git a/apps/subgraph/tests/pdp-verifier.test.ts b/apps/subgraph/tests/pdp-verifier.test.ts new file mode 100644 index 00000000..1c0ece7a --- /dev/null +++ b/apps/subgraph/tests/pdp-verifier.test.ts @@ -0,0 +1,66 @@ +import { afterAll, assert, beforeAll, clearStore, describe, test } from "matchstick-as/assembly/index"; +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { getRootEntityId, getRootSampleKey } from "../src/helpers"; +import { handleDataSetCreated, handlePiecesAdded } from "../src/pdp-verifier"; +import { createDataSetCreatedEvent, createRootsAddedEvent } from "./pdp-verifier-utils"; + +const SET_ID = BigInt.fromI32(1); +const ROOT_ID_1 = BigInt.fromI32(101); +const RAW_SIZE_1 = BigInt.fromI32(10486897); +const ROOT_CID_1_STR = + "0x01559120258ff7f7021387dcea7164b7d1c4a98bd6f8d3c187e3114795efa391df307c8aa9d5d5cbac03"; +const SENDER_ADDRESS = Address.fromString("0xa16081f360e3847006db660bae1c6d1b2e17ec2a"); +const CONTRACT_ADDRESS = Address.fromString("0xb16081f360e3847006db660bae1c6d1b2e17ec2b"); +const PROOF_SET_ID_BYTES = Bytes.fromBigInt(SET_ID); + +describe("handlePiecesAdded Tests", () => { + beforeAll(() => { + const mockDataSetCreatedEvent = createDataSetCreatedEvent( + SET_ID, + SENDER_ADDRESS, + Bytes.fromI32(123), + CONTRACT_ADDRESS, + BigInt.fromI32(50), + BigInt.fromI32(1678886400), + ); + handleDataSetCreated(mockDataSetCreatedEvent); + + const rootsAddedEvent = createRootsAddedEvent(SET_ID, [ROOT_ID_1], SENDER_ADDRESS, CONTRACT_ADDRESS); + rootsAddedEvent.block.timestamp = BigInt.fromI32(100); + rootsAddedEvent.block.number = BigInt.fromI32(50); + rootsAddedEvent.logIndex = BigInt.fromI32(1); + rootsAddedEvent.transaction.hash = Bytes.fromHexString("0x" + "c".repeat(64)); + + handlePiecesAdded(rootsAddedEvent); + }); + + afterAll(() => { + clearStore(); + }); + + test("DataSet, Provider, and Root are created with the expected fields", () => { + assert.entityCount("DataSet", 1); + assert.entityCount("Root", 1); + assert.entityCount("Provider", 1); + + const dataSetId = PROOF_SET_ID_BYTES.toHex(); + assert.fieldEquals("DataSet", dataSetId, "setId", SET_ID.toString()); + assert.fieldEquals("DataSet", dataSetId, "status", "READY"); + assert.fieldEquals("DataSet", dataSetId, "isActive", "true"); + assert.fieldEquals("DataSet", dataSetId, "owner", SENDER_ADDRESS.toHex()); + + const rootEntityIdBytes = getRootEntityId(SET_ID, ROOT_ID_1); + const rootEntityId = rootEntityIdBytes.toHex(); + assert.fieldEquals("Root", rootEntityId, "rootId", ROOT_ID_1.toString()); + assert.fieldEquals("Root", rootEntityId, "setId", SET_ID.toString()); + assert.fieldEquals("Root", rootEntityId, "cid", ROOT_CID_1_STR); + assert.fieldEquals("Root", rootEntityId, "rawSize", RAW_SIZE_1.toString()); + assert.fieldEquals("Root", rootEntityId, "removed", "false"); + assert.fieldEquals("Root", rootEntityId, "sampleKey", getRootSampleKey(rootEntityIdBytes).toHex()); + + const providerId = SENDER_ADDRESS.toHex(); + assert.fieldEquals("Provider", providerId, "address", providerId); + assert.fieldEquals("Provider", providerId, "totalFaultedPeriods", "0"); + assert.fieldEquals("Provider", providerId, "totalProvingPeriods", "0"); + }); +}); diff --git a/apps/subgraph/tsconfig.json b/apps/subgraph/tsconfig.json new file mode 100644 index 00000000..4e866720 --- /dev/null +++ b/apps/subgraph/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", + "include": ["src", "tests"] +} diff --git a/docs/checks/data-retention.md b/docs/checks/data-retention.md index 804190c6..e87fbb99 100644 --- a/docs/checks/data-retention.md +++ b/docs/checks/data-retention.md @@ -25,7 +25,7 @@ Dealbot polls The Graph API endpoint for PDP (Proof of Data Possession) data at **Subgraph repository**: [FilOzone/pdp-explorer](https://github.com/FilOzone/pdp-explorer/blob/main/subgraph/src/pdp-verifier.ts) -**Subgraph endpoint**: Configured via `PDP_SUBGRAPH_ENDPOINT` environment variable (see [environment-variables.md](../environment-variables.md#pdp_subgraph_endpoint)) +**Subgraph endpoint**: Configured via `SUBGRAPH_ENDPOINT` environment variable (see [environment-variables.md](../environment-variables.md#subgraph_endpoint)) > **Note**: The production subgraph URL is currently being finalized [here](https://github.com/FilOzone/pdp-explorer/pull/86). @@ -46,7 +46,7 @@ From `GET_PROVIDERS_WITH_DATASETS` query for each provider: > **Note**: The subgraph query uses the field name `proofSets`, but this refers to "dataSets" in the current codebase. The terminology was updated from "proof set" to "data set" but the subgraph schema retains the old naming. -Source: [`pdp-subgraph.service.ts` (`fetchSubgraphMeta`, `fetchProvidersWithDatasets`)](../../apps/backend/src/pdp-subgraph/pdp-subgraph.service.ts) +Source: [`subgraph.service.ts` (`fetchSubgraphMeta`, `fetchProvidersWithDatasets`)](../../apps/backend/src/subgraph/subgraph.service.ts) ### 2. Compute Challenge Totals and Overdue Estimates @@ -164,7 +164,7 @@ The PDP subgraph service enforces Goldsky's public endpoint rate limits: Rate limiting is enforced client-side to prevent 429 errors. -Source: [`pdp-subgraph.service.ts` (`enforceRateLimit`)](../../apps/backend/src/pdp-subgraph/pdp-subgraph.service.ts) +Source: [`subgraph.service.ts` (`enforceRateLimit`)](../../apps/backend/src/subgraph/subgraph.service.ts) ## Metrics Recorded @@ -195,11 +195,11 @@ Key environment variables that control data retention check behavior: | Variable | Required | Default | Description | | ----------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------ | -| `PDP_SUBGRAPH_ENDPOINT` | No | Empty string | The Graph API endpoint for PDP subgraph queries. When empty, data retention checks are disabled. | +| `SUBGRAPH_ENDPOINT` | No | Empty string | The Graph API endpoint for PDP subgraph queries. When empty, data retention checks are disabled. | Source: [`app.config.ts`](../../apps/backend/src/config/app.config.ts) -See also: [`environment-variables.md`](../environment-variables.md#pdp_subgraph_endpoint) for the full configuration reference. +See also: [`environment-variables.md`](../environment-variables.md#subgraph_endpoint) for the full configuration reference. ## Error Handling diff --git a/docs/checks/production-configuration-and-approval-methodology.md b/docs/checks/production-configuration-and-approval-methodology.md index 6da7c92f..8c033f0d 100644 --- a/docs/checks/production-configuration-and-approval-methodology.md +++ b/docs/checks/production-configuration-and-approval-methodology.md @@ -40,7 +40,7 @@ Relevant parameters include: | Parameter | Value | Notes | |-----------|-------|-------| -| [`PDP_SUBGRAPH_ENDPOINT`](../environment-variables.md#pdp_subgraph_endpoint) | TODO: fill this in | Uses the subgraph from [pdp-explorer](https://github.com/FilOzone/pdp-explorer). | +| [`SUBGRAPH_ENDPOINT`](../environment-variables.md#subgraph_endpoint) | TODO: fill this in | Uses the subgraph from [pdp-explorer](https://github.com/FilOzone/pdp-explorer). | | [`MIN_NUM_DATASETS_FOR_CHECKS`](../environment-variables.md#dataset-configuration) | 15 | Ensure there are enough datasets with pieces being added so that statistical significance for [Data Retention Fault Rate](#data-retention-fault-rate) can be achieved quicker. Note that on mainnet each dataset incurs 5 challenges[^1] per daily proof[^2]. With this many datasets, an SP can be approved for data retention after a faultless ~7 days even if the SP doesn't have other datasets. | See [How are data retention statistics/thresholds calculated?](#how-are-data-retention-statisticsthresholds-calculated) for more details. diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 9aff8c1e..a09d879c 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -8,10 +8,10 @@ This document provides a comprehensive guide to all environment variables used b | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Application](#application-configuration) | `NODE_ENV`, `DEALBOT_PORT`, `DEALBOT_HOST`, `DEALBOT_RUN_MODE`, `DEALBOT_METRICS_PORT`, `DEALBOT_METRICS_HOST`, `DEALBOT_ALLOWED_ORIGINS`, `ENABLE_DEV_MODE` | | [Database](#database-configuration) | `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_POOL_MAX`, `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_NAME` | -| [Blockchain](#blockchain-configuration) | `NETWORK`, `RPC_URL`, `WALLET_ADDRESS`, `WALLET_PRIVATE_KEY`, `SESSION_KEY_PRIVATE_KEY`, `CHECK_DATASET_CREATION_FEES`, `USE_ONLY_APPROVED_PROVIDERS`, `PDP_SUBGRAPH_ENDPOINT` | +| [Blockchain](#blockchain-configuration) | `NETWORK`, `RPC_URL`, `WALLET_ADDRESS`, `WALLET_PRIVATE_KEY`, `SESSION_KEY_PRIVATE_KEY`, `CHECK_DATASET_CREATION_FEES`, `USE_ONLY_APPROVED_PROVIDERS`, `SUBGRAPH_ENDPOINT` | | [Dataset Versioning](#dataset-versioning) | `DEALBOT_DATASET_VERSION` | | [Scheduling](#scheduling-configuration) | `PROVIDERS_REFRESH_INTERVAL_SECONDS`, `DATA_RETENTION_POLL_INTERVAL_SECONDS`, `DEALBOT_MAINTENANCE_WINDOWS_UTC`, `DEALBOT_MAINTENANCE_WINDOW_MINUTES` | -| [Jobs (pg-boss)](#jobs-pg-boss) | `DEALBOT_PGBOSS_SCHEDULER_ENABLED`, `DEALBOT_PGBOSS_POOL_MAX`, `DEALS_PER_SP_PER_HOUR`, `DATASET_CREATIONS_PER_SP_PER_HOUR`, `RETRIEVALS_PER_SP_PER_HOUR`, `JOB_SCHEDULER_POLL_SECONDS`, `JOB_WORKER_POLL_SECONDS`, `PG_BOSS_LOCAL_CONCURRENCY`, `JOB_CATCHUP_MAX_ENQUEUE`, `JOB_SCHEDULE_PHASE_SECONDS`, `JOB_ENQUEUE_JITTER_SECONDS`, `DEAL_JOB_TIMEOUT_SECONDS`, `RETRIEVAL_JOB_TIMEOUT_SECONDS`, `IPFS_BLOCK_FETCH_CONCURRENCY` | +| [Jobs (pg-boss)](#jobs-pg-boss) | `DEALBOT_PGBOSS_SCHEDULER_ENABLED`, `DEALBOT_PGBOSS_POOL_MAX`, `DEALS_PER_SP_PER_HOUR`, `DATASET_CREATIONS_PER_SP_PER_HOUR`, `RETRIEVALS_PER_SP_PER_HOUR`, `JOB_SCHEDULER_POLL_SECONDS`, `JOB_WORKER_POLL_SECONDS`, `PG_BOSS_LOCAL_CONCURRENCY`, `JOB_CATCHUP_MAX_ENQUEUE`, `JOB_SCHEDULE_PHASE_SECONDS`, `JOB_ENQUEUE_JITTER_SECONDS`, `DEAL_JOB_TIMEOUT_SECONDS`, `RETRIEVAL_JOB_TIMEOUT_SECONDS`, `ANON_RETRIEVAL_JOB_TIMEOUT_SECONDS`, `IPFS_BLOCK_FETCH_CONCURRENCY` | | [Dataset](#dataset-configuration) | `DEALBOT_LOCAL_DATASETS_PATH`, `RANDOM_PIECE_SIZES` | | [Timeouts](#timeout-configuration) | `CONNECT_TIMEOUT_MS`, `HTTP_REQUEST_TIMEOUT_MS`, `HTTP2_REQUEST_TIMEOUT_MS`, `IPNI_VERIFICATION_TIMEOUT_MS`, `IPNI_VERIFICATION_POLLING_MS` | | [Piece Cleanup](#piece-cleanup) | `MAX_DATASET_STORAGE_SIZE_BYTES`, `TARGET_DATASET_STORAGE_SIZE_BYTES`, `JOB_PIECE_CLEANUP_PER_SP_PER_HOUR`, `MAX_PIECE_CLEANUP_RUNTIME_SECONDS` | @@ -424,22 +424,25 @@ Session keys are scoped (only storage operations, not deposits or withdrawals) a --- -### `PDP_SUBGRAPH_ENDPOINT` +### `SUBGRAPH_ENDPOINT` - **Type**: `string` (URL) - **Required**: No - **Default**: Empty string (feature disabled) -**Role**: The Graph API endpoint for querying PDP (Proof of Data Possession) subgraph data. This endpoint is used to retrieve data retention info for provider data. +**Role**: The Graph API endpoint for querying PDP (Proof of Data Possession) subgraph data. Drives the overdue-periods metric and the anonymous-retrieval candidate-piece query. + +The dealbot-owned subgraph lives at `apps/subgraph/` (package `@dealbot/subgraph`) and is deployed to Goldsky. Point this variable at one of those slots; the exact slugs are documented in `apps/subgraph/README.md`. **When to update**: -- When switching between different Graph API endpoints +- When swapping between the dealbot-owned subgraph slots on Goldsky (mainnet vs calibnet). +- When deploying a new subgraph version. **Example**: ```bash -PDP_SUBGRAPH_ENDPOINT=https://api.thegraph.com/subgraphs/filecoin/pdp +SUBGRAPH_ENDPOINT=https://api.goldsky.com/api/public//subgraphs/dealbot-subgraph//gn ``` --- @@ -783,6 +786,25 @@ Use this to stagger multiple dealbot deployments that are not sharing a database **Note**: This is independent of HTTP-level timeouts. The job timeout enforces end-to-end execution time of a Retrieval Check job. +--- + +### `ANON_RETRIEVAL_JOB_TIMEOUT_SECONDS` + +- **Type**: `number` +- **Required**: No +- **Default**: `360` (6 minutes) +- **Minimum**: `60` +- **Enforced**: Yes (config validation) + +**Role**: Maximum runtime for anonymous retrieval jobs before forced abort. Anonymous retrievals fetch arbitrary pieces (up to ~70 MiB) that were not produced by the dealbot, so this is typically larger than `RETRIEVAL_JOB_TIMEOUT_SECONDS`. When the timeout trips, partial metrics (`ttfb_ms`, `bytes_retrieved`, `response_code`) are still persisted so the abort is not silently lost. + +**When to update**: + +- Increase if large pieces are consistently being cut off mid-download +- Decrease to detect and fail stuck retrievals faster + +**Note**: This is independent of HTTP-level timeouts (`CONNECT_TIMEOUT_MS`, `HTTP2_REQUEST_TIMEOUT_MS`). The job timeout covers the end-to-end execution of an Anon Retrieval Check (piece selection, download, CommP validation, CAR/IPNI validation). + --- ### `IPFS_BLOCK_FETCH_CONCURRENCY` diff --git a/kustomize/overlays/local/backend-configmap-local.yaml b/kustomize/overlays/local/backend-configmap-local.yaml index 704d444f..937b9ed6 100644 --- a/kustomize/overlays/local/backend-configmap-local.yaml +++ b/kustomize/overlays/local/backend-configmap-local.yaml @@ -26,5 +26,5 @@ data: PG_BOSS_LOCAL_CONCURRENCY: "3" JOB_WORKER_POLL_SECONDS: "60" RANDOM_PIECE_SIZES: "10485760" - PDP_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmdfaaxeuz6us01u359yjdctw/subgraphs/pdp-explorer/calibration311a/gn" + SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmdfaaxeuz6us01u359yjdctw/subgraphs/pdp-explorer/calibration311a/gn" JOB_SCHEDULER_POLL_SECONDS: "60" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 617eed44..f3a78e03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,27 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -catalogs: - default: - '@biomejs/biome': - specifier: 2.3.14 - version: 2.3.14 - '@swc/core': - specifier: 1.15.11 - version: 1.15.11 - '@vitest/coverage-v8': - specifier: 4.0.18 - version: 4.0.18 - typescript: - specifier: 5.9.3 - version: 5.9.3 - unplugin-swc: - specifier: 1.5.9 - version: 1.5.9 - vitest: - specifier: 4.0.18 - version: 4.0.18 - overrides: cron: 4.4.0 @@ -70,13 +49,13 @@ importers: version: 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) '@nestjs/schedule': specifier: ^6.1.1 - version: 6.1.1(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) + version: 6.1.1(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)) '@nestjs/swagger': specifier: ^11.2.6 - version: 11.2.6(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2) + version: 11.2.6(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2) '@nestjs/typeorm': specifier: ^11.0.0 - version: 11.0.0(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(ts-node@10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3))) + version: 11.0.0(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(ts-node@10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3))) '@willsoto/nestjs-prometheus': specifier: ^6.0.2 version: 6.0.2(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3) @@ -100,7 +79,7 @@ importers: version: 4.4.0 filecoin-pin: specifier: ^0.20.0 - version: 0.20.0(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)(zod@4.3.6) + version: 0.20.0(encoding@0.1.13)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)(zod@4.3.6) helmet: specifier: ^8.1.0 version: 8.1.0 @@ -155,7 +134,7 @@ importers: version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.1.13 - version: 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-express@11.1.13) + version: 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)) '@swc/core': specifier: 'catalog:' version: 1.15.11 @@ -193,6 +172,22 @@ importers: specifier: 'catalog:' version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(yaml@2.8.2) + apps/subgraph: + dependencies: + '@graphprotocol/graph-cli': + specifier: 0.98.1 + version: 0.98.1(@types/node@25.2.3)(typescript@5.9.3)(zod@3.25.76) + '@graphprotocol/graph-ts': + specifier: 0.38.2 + version: 0.38.2 + devDependencies: + assemblyscript: + specifier: 0.19.23 + version: 0.19.23 + matchstick-as: + specifier: 0.6.0 + version: 0.6.0 + apps/web: dependencies: '@radix-ui/react-slot': @@ -811,6 +806,9 @@ packages: '@fastify/ajv-compiler@4.0.5': resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + '@fastify/error@4.2.0': resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} @@ -841,6 +839,18 @@ packages: peerDependencies: viem: 2.x + '@float-capital/float-subgraph-uncrashable@0.0.0-internal-testing.5': + resolution: {integrity: sha512-yZ0H5e3EpAYKokX/AbtplzlvSxEJY7ZfpvQyDzyODkks0hakAAlDG6fQu1SlDJMWorY7bbq1j7fCiFeTWci6TA==} + hasBin: true + + '@graphprotocol/graph-cli@0.98.1': + resolution: {integrity: sha512-GrWFcRCBlLcRT+gIGundQl7yyrX3YWUPj66bxThKf5CJvvWXdZoNxrj27dMMqulsSwYmpCkb3YmpCiVJFGdpHw==} + engines: {node: '>=20.18.1'} + hasBin: true + + '@graphprotocol/graph-ts@0.38.2': + resolution: {integrity: sha512-87KIFSFs2+Te+mnmb7Y+M57oqzlLy20cIyPIRbn9qJfpZFSZHTKtBLT6KQmcsK0YkoWis9Ur3c3M2c9mmaaEHQ==} + '@hapi/address@5.1.1': resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} engines: {node: '>=14.0.0'} @@ -1044,18 +1054,14 @@ packages: '@ipshipyard/libp2p-auto-tls@2.0.1': resolution: {integrity: sha512-zpDXVMY1ZgB6o30zFocXUzrD9+tz1bbEdgewFoBf4olDh5/CwjDi/k9v2RrJqujWKYWyRuHRg6Q+VRpvtGrpuw==} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/ttlcache@1.4.1': resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} engines: {node: '>=12'} @@ -1156,6 +1162,9 @@ packages: '@libp2p/interface-internal@3.0.13': resolution: {integrity: sha512-qZTn1CKOro/1m8Eizb/B1pUvW/eJe5KhP/dvqKETqka26qH89eX5SlTS1OPTINXzJvfbnDFptVJOPxmpa3BfgA==} + '@libp2p/interface@2.11.0': + resolution: {integrity: sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==} + '@libp2p/interface@3.1.0': resolution: {integrity: sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==} @@ -1165,6 +1174,9 @@ packages: '@libp2p/keychain@6.0.9': resolution: {integrity: sha512-gO8krY3iPbXzc+LLA2haTEKbnINpx/p/FlXeHZsyXSD5Q31aV2zQsOrNlVaCyDhKqb1uiyon7NMMPn8UUqkJWQ==} + '@libp2p/logger@5.2.0': + resolution: {integrity: sha512-OEFS529CnIKfbWEHmuCNESw9q0D0hL8cQ8klQfjIVPur15RcgAEgc1buQ7Y6l0B6tCYg120bp55+e9tGvn8c0g==} + '@libp2p/logger@6.2.2': resolution: {integrity: sha512-XtanXDT+TuMuZoCK760HGV1AmJsZbwAw5AiRUxWDbsZPwAroYq64nb41AHRu9Gyc0TK9YD+p72+5+FIxbw0hzw==} @@ -1180,6 +1192,9 @@ packages: '@libp2p/peer-collections@7.0.13': resolution: {integrity: sha512-SwNQFT0tfSyfbdUUKZFzHv9DXxsabuT99ch/40as8qC7xgoJJfUmhoa9FSuAuABdpTVHDJmxCI2pIbcb1kBqfg==} + '@libp2p/peer-id@5.1.9': + resolution: {integrity: sha512-cVDp7lX187Epmi/zr0Qq2RsEMmueswP9eIxYSFoMcHL/qcvRFhsxOfUGB8361E26s2WJvC9sXZ0oJS9XVueJhQ==} + '@libp2p/peer-id@6.0.4': resolution: {integrity: sha512-Z3xK0lwwKn4bPg3ozEpPr1HxsRi2CxZdghOL+MXoFah/8uhJJHxHFA8A/jxtKn4BB8xkk6F8R5vKNIS05yaCYw==} @@ -1230,9 +1245,15 @@ packages: '@multiformats/multiaddr-matcher@3.0.1': resolution: {integrity: sha512-jvjwzCPysVTQ53F4KqwmcqZw73BqHMk0UUZrMP9P4OtJ/YHrfs122ikTqhVA2upe0P/Qz9l8HVlhEifVYB2q9A==} + '@multiformats/multiaddr-to-uri@11.0.2': + resolution: {integrity: sha512-SiLFD54zeOJ0qMgo9xv1Tl9O5YktDKAVDP4q4hL16mSq4O4sfFNagNADz8eAofxd6TfQUzGQ3TkRRG9IY2uHRg==} + '@multiformats/multiaddr-to-uri@12.0.0': resolution: {integrity: sha512-3uIEBCiy8tfzxYYBl81x1tISiNBQ7mHU4pGjippbJRoQYHzy/ZdZM/7JvTldr8pc/dzpkaNJxnsuxxlhsPOJsA==} + '@multiformats/multiaddr@12.5.1': + resolution: {integrity: sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==} + '@multiformats/multiaddr@13.0.1': resolution: {integrity: sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==} @@ -1377,6 +1398,9 @@ packages: resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + '@noble/curves@1.9.1': resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} @@ -1389,6 +1413,10 @@ packages: resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} engines: {node: '>= 20.19.0'} + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -1414,6 +1442,26 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true + '@oclif/core@4.10.5': + resolution: {integrity: sha512-qcdCF7NrdWPfme6Kr34wwljRCXbCVpL1WVxiNy0Ep6vbWKjxAjFQwuhqkoyL0yjI+KdwtLcOCGn5z2yzdijc8w==} + engines: {node: '>=18.0.0'} + + '@oclif/core@4.5.5': + resolution: {integrity: sha512-iQzlaJQgPeUXrtrX71OzDwxPikQ7c2FhNd8U8rBB7BCtj2XYfmzBT/Hmbc+g9OKDIG/JkbJT0fXaWMMBrhi+1A==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-autocomplete@3.2.45': + resolution: {integrity: sha512-ENrUg8rbVCjh40uvi3MC9kGbiUoEf11nyqE59RBzegeeLpRXNo/Zp27L9j1tUmPEqGgfS2/wvHPihNzkpK1FDw==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-not-found@3.2.80': + resolution: {integrity: sha512-yTLjWvR1r/Rd/cO2LxHdMCDoL5sQhBYRUcOMCmxZtWVWhx4rAZ8KVUPDVsb+SvjJDV5ADTDBgt1H52fFx7YWqg==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-warn-if-update-available@3.1.60': + resolution: {integrity: sha512-cRKBZm14IuA6G8W84dfd3iXj3BTAoxQ5o3pUE8DKEQ4n/tVha20t5nkVeD+ISC68e0Fuw5koTMvRwXb1lJSnzg==} + engines: {node: '>=18.0.0'} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -1672,6 +1720,9 @@ packages: resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} engines: {node: '>=20.0.0'} + '@pinax/graph-networks-registry@0.7.1': + resolution: {integrity: sha512-Gn2kXRiEd5COAaMY/aDCRO0V+zfb1uQKCu5HFPoWka+EsZW27AlTINA7JctYYYEMuCbjMia5FBOzskjgEvj6LA==} + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -1679,6 +1730,18 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} + engines: {node: '>=12'} + '@prisma/instrumentation@7.4.2': resolution: {integrity: sha512-r9JfchJF1Ae6yAxcaLu/V1TGqBhAuSDe3mRNOssBfx1rMzfZ4fdNvrgUBwyb/TNTGXFxlH9AZix5P257x07nrg==} peerDependencies: @@ -1861,6 +1924,9 @@ packages: react-redux: optional: true + '@rescript/std@9.0.0': + resolution: {integrity: sha512-zGzFsgtZ44mgL4Xef2gOy1hrRVdrs9mcxCOOKZrIPsmbZW14yTkaF591GXxpQvjXiHtgZ/iA9qLyWH6oSReIxQ==} + '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -1997,12 +2063,21 @@ packages: '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + '@scure/base@1.2.6': resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + '@scure/bip32@1.7.0': resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@scure/bip39@1.6.0': resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} @@ -2399,9 +2474,15 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@25.2.3': resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/pg-pool@2.0.7': resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} @@ -2455,6 +2536,9 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2553,6 +2637,22 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@whatwg-node/disposablestack@0.0.6': + resolution: {integrity: sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/fetch@0.10.13': + resolution: {integrity: sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/node-fetch@0.8.5': + resolution: {integrity: sha512-4xzCl/zphPqlp9tASLVeUhB5+WJHbuWGYpfoC2q1qh5dw0AqZBW7L27V5roxYWijPxj4sspRAAoOH3d2ztaHUQ==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/promise-helpers@1.3.2': + resolution: {integrity: sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==} + engines: {node: '>=16.0.0'} + '@willsoto/nestjs-prometheus@6.0.2': resolution: {integrity: sha512-ePyLZYdIrOOdlOWovzzMisIgviXqhPVzFpSMKNNhn6xajhRHeBsjAzSdpxZTc6pnjR9hw1lNAHyKnKl7lAPaVg==} peerDependencies: @@ -2565,6 +2665,15 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abitype@0.7.1: + resolution: {integrity: sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==} + peerDependencies: + typescript: '>=4.9.4' + zod: ^3 >=3.19.1 + peerDependenciesMeta: + zod: + optional: true + abitype@1.2.3: resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} peerDependencies: @@ -2661,6 +2770,14 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2669,6 +2786,10 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -2681,6 +2802,10 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -2693,6 +2818,12 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + apisauce@2.1.6: + resolution: {integrity: sha512-MdxR391op/FucS2YQRfB/NMRyCnHEPDd4h17LRIuVYi0BpGmMhpxc0shbOpfs5ahABuBEffNCGal5EcsydbBWg==} + + app-module-path@2.2.0: + resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + app-root-path@3.1.0: resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} engines: {node: '>= 6.0.0'} @@ -2722,6 +2853,15 @@ packages: resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} engines: {node: '>=12.0.0'} + assemblyscript@0.19.23: + resolution: {integrity: sha512-fwOQNZVTMga5KRsfY80g7cpOl4PsFQczMwHzdtgoqLXaYhkhavufKb0sB0l3T1DUxpAufA0KNhlbpuuhZUwxMA==} + hasBin: true + + assemblyscript@0.27.31: + resolution: {integrity: sha512-Ra8kiGhgJQGZcBxjtMcyVRxOEJZX64kd+XGpjWzjcjgxWJVv+CAQO0aDBk4GQVhjYbOkATarC83mHjAVGtwPBQ==} + engines: {node: '>=16', npm: '>=7'} + hasBin: true + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2729,6 +2869,9 @@ packages: ast-v8-to-istanbul@0.3.11: resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2746,6 +2889,9 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + axios@0.21.4: + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} @@ -2794,15 +2940,29 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binaryen@102.0.0-nightly.20211028: + resolution: {integrity: sha512-GCJBVB5exbxzzvyt8MGDv/MeUjs6gkXDvf4xOIItRBptYl0Tz5sm1o/uG95YK0L0VeG5ajDu3hRtkBP2kzqC5w==} + hasBin: true + + binaryen@116.0.0-nightly.20240114: + resolution: {integrity: sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==} + hasBin: true + bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + blob-to-it@2.0.12: + resolution: {integrity: sha512-0zEZt8t8/QrdH4boktG19F/9fqfPWFjuh1QlK0qTCO13oUWaBAR8kpNloQNb3OWUtaA0mu8qfPy0R3CZDC8M2g==} + blockstore-core@6.1.2: resolution: {integrity: sha512-yWU38RM8DJ6C7Y2shIeTNVgGiJX/ko2RXqDyNlxMakOc+aVS7k1SCiakMlh6ix0juRNPtj0ySMTXU8UBDXXRCQ==} @@ -2820,6 +2980,10 @@ packages: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2835,6 +2999,18 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2844,6 +3020,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2887,6 +3067,10 @@ packages: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2932,6 +3116,10 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clean-stack@3.0.1: + resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} + engines: {node: '>=10'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -2940,6 +3128,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-table3@0.6.0: + resolution: {integrity: sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==} + engines: {node: 10.* || >= 12.*} + cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} @@ -2964,13 +3156,23 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -3008,6 +3210,9 @@ packages: resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==} engines: {node: '>=20'} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -3053,6 +3258,10 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + cosmiconfig@7.0.1: + resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} + engines: {node: '>=10'} + cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -3073,6 +3282,10 @@ packages: resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} engines: {node: '>=18.x'} + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3135,6 +3348,9 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + dag-jose@5.1.1: + resolution: {integrity: sha512-9alfZ8Wh1XOOMel8bMpDqWsDT72ojFQCJPtwZSev9qh4f8GoCV9qrJW8jcOUhcstO8Kfm09FHGo//jqiZq3z9w==} + data-urls@7.0.0: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -3191,6 +3407,26 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + decompress-tar@4.1.1: + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} + + decompress-tarbz2@4.1.1: + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} + + decompress-targz@4.1.1: + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} + + decompress-unzip@4.0.1: + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} + + decompress@4.2.1: + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} + dedent@1.7.0: resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: @@ -3207,6 +3443,14 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -3214,6 +3458,14 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + delay@6.0.0: resolution: {integrity: sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==} engines: {node: '>=16'} @@ -3259,6 +3511,10 @@ packages: dnum@2.17.0: resolution: {integrity: sha512-Abo8RU2ZoABVO2R051XlJEgDIXAlA8/ZjOT2F1uAWvm6Vb8TphmN4k7qgu5nWKSv/JUGLVty6QPEeLTvaxNRYQ==} + docker-compose@1.3.0: + resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} + engines: {node: '>= 6.0.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -3291,6 +3547,20 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + ejs@3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-fetch@1.9.1: + resolution: {integrity: sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA==} + engines: {node: '>=6'} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -3308,6 +3578,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -3315,6 +3588,10 @@ packages: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} + enquirer@2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -3357,6 +3634,12 @@ packages: es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + es6-promisify@5.0.0: + resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} + esbuild@0.27.1: resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} @@ -3369,6 +3652,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -3408,6 +3695,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -3426,6 +3716,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -3441,12 +3735,19 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + eyes@0.1.8: + resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} + engines: {node: '> 0.1.90'} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -3457,6 +3758,9 @@ packages: fast-json-stringify@6.1.1: resolution: {integrity: sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==} + fast-levenshtein@3.0.0: + resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -3466,6 +3770,10 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + fastify@5.8.2: resolution: {integrity: sha512-lZmt3navvZG915IE+f7/TIVamxIwmBd+OMB+O9WBzcpIwOo6F0LTh0sluoMFk5VkrKTvvrwIaoJPkir4Z+jtAg==} @@ -3480,6 +3788,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3493,10 +3804,25 @@ packages: resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} + file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + + file-type@5.2.0: + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} + + file-type@6.2.0: + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} + filecoin-pin@0.20.0: resolution: {integrity: sha512-rWYJuW0B75LUMvUiUUR6F3iQmHdwVdV0RrmG3sljP3+gVCboYkc0xB80RWlT631xCDCncz/lmstBAQ9r+RbdKw==} hasBin: true + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3581,6 +3907,13 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + + fs-jetpack@4.3.1: + resolution: {integrity: sha512-dbeOK84F6BiQzk2yqqCVwCPWTxAvVGJ3fMQc6E2wuEohS28mR6yHngbrKuVCK1KHRx/ccByDylqu4H5PCP2urQ==} + fs-monkey@1.1.0: resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} @@ -3599,6 +3932,10 @@ packages: resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==} engines: {node: '>=14.16'} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -3611,6 +3948,9 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-iterator@1.0.2: + resolution: {integrity: sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==} + get-iterator@2.0.1: resolution: {integrity: sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==} @@ -3626,6 +3966,14 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@2.3.1: + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -3641,6 +3989,12 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} @@ -3649,13 +4003,29 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + gluegun@5.2.0: + resolution: {integrity: sha512-jSUM5xUy2ztYFQANne17OUm/oAd7qSX7EBksS9bQDt9UvLPqcEkeWUebmaposb8Tx7eTTD8uJVWGRe6PYSsYkg==} + hasBin: true + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql-import-node@0.0.5: + resolution: {integrity: sha512-OXbou9fqh9/Lm7vwXT0XoRN9J5+WCYKnbiTalgFDvkQERITRmcfncZs6aVABedd5B85yQU5EULS4a5pnbpuI0Q==} + peerDependencies: + graphql: '*' + + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + graphql@16.12.0: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -3663,6 +4033,10 @@ packages: hamt-sharding@3.0.6: resolution: {integrity: sha512-nZeamxfymIWLpVcAN0CRrb7uVq3hCOGj9IcL6NMA6VVCVWqj+h9Jo/SmaWuS92AEDf1thmHsM5D5c70hM3j2Tg==} + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3711,6 +4085,10 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-call@5.3.0: + resolution: {integrity: sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==} + engines: {node: '>=8.0.0'} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3723,6 +4101,14 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.1: resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} engines: {node: '>=0.10.0'} @@ -3744,6 +4130,9 @@ packages: immer@11.1.3: resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3776,9 +4165,15 @@ packages: interface-blockstore@6.0.1: resolution: {integrity: sha512-AVcUbMwrhiO4RqDljUitUt3aoon6MD2fblsN7vEVBDsmHFQT0LIOODVK5Qxe28h1uUvVykyZqmo09f6w55KiJg==} + interface-datastore@8.3.2: + resolution: {integrity: sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA==} + interface-datastore@9.0.2: resolution: {integrity: sha512-jebn+GV/5LTDDoyicNIB4D9O0QszpPqT09Z/MpEWvf3RekjVKpXJCDguM5Au2fwIFxFDAQMZe5bSla0jMamCNg==} + interface-store@6.0.3: + resolution: {integrity: sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==} + interface-store@7.0.1: resolution: {integrity: sha512-OPRRUO3Cs6Jr/t98BrJLQp1jUTPgrRH0PqFfuNoPAqd+J7ABN1tjFVjQdaOBiybYJTS/AyBSZnZVWLPvp3dW3w==} @@ -3807,12 +4202,19 @@ packages: ipfs-unixfs-importer@16.1.4: resolution: {integrity: sha512-apnhvrTRFZMt7YUjyHJcvaPN1SSgBQ7OQIjTb2LppRDbY20kzVO8PcROLOzpinFmeZlVfx0QONy8aNfAcT3b2w==} + ipfs-unixfs@11.2.5: + resolution: {integrity: sha512-uasYJ0GLPbViaTFsOLnL9YPjX5VmhnqtWRriogAHOe4ApmIi9VAOFBzgDHsUW2ub4pEa/EysbtWk126g2vkU/g==} + ipfs-unixfs@12.0.1: resolution: {integrity: sha512-V8o80MEq3Aehs9KSX9k/FpRmsyYrLAH6mrq7Tq13vQi8TA3onzif8z5sDsWg8AAV9aa+uuvr0HHVLtnb5rNL3A==} ipns@10.1.3: resolution: {integrity: sha512-b2Zeh8+7qOV11NjnTsYLpG8K6T13uBMndpzk9N9E2Qjz/u80qsxvKpspSP32sErOLr/GWjdFVVc02E9PMojQNA==} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -3825,6 +4227,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-electron@2.2.2: resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} @@ -3836,10 +4243,19 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -3851,6 +4267,9 @@ packages: is-loopback-addr@2.0.2: resolution: {integrity: sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==} + is-natural-number@4.0.1: + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} + is-network-error@1.3.0: resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} engines: {node: '>=16'} @@ -3872,10 +4291,26 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + is-regexp@3.1.0: resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} engines: {node: '>=12'} + is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -3888,6 +4323,13 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -3897,9 +4339,18 @@ packages: iso-kv@3.1.1: resolution: {integrity: sha512-yKTLmUCc8gl0MXJs3ZaTaNDgfG/2ROasZERKPa5aYY7Ks/eb8BvGfLHrC+t1cHxiRkogvaXulDP77ovwLKgLPg==} + iso-url@1.2.1: + resolution: {integrity: sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==} + engines: {node: '>=12'} + iso-web@2.1.1: resolution: {integrity: sha512-P3qFt9hVgJx5lgUHY6TBoI575SHT7vt6BswXbcqd3BTZkBtEH59QxP6gMCtAACHxoWezbK2lTPj4yBoTBADDxQ==} + isomorphic-ws@4.0.1: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + isows@1.0.7: resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} peerDependencies: @@ -4011,6 +4462,9 @@ packages: it-to-buffer@4.0.10: resolution: {integrity: sha512-dXNHSILSPVv+31nxav+egNxWA/RpSuAHCSurJCLxkFDpmzAyYPJwIkPfLkYiHLoJqyE6Z5nVFILp6aDvz9V5pw==} + it-to-stream@1.0.0: + resolution: {integrity: sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA==} + iterare@1.2.1: resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} engines: {node: '>=6'} @@ -4018,6 +4472,20 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jayson@4.2.0: + resolution: {integrity: sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==} + engines: {node: '>=8'} + hasBin: true + jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4076,6 +4544,10 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -4097,6 +4569,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -4112,6 +4587,9 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4123,6 +4601,9 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + kubo-rpc-client@5.4.1: + resolution: {integrity: sha512-v86bQWtyA//pXTrt9y4iEwjW6pt1gA18Z1famWXIR/HN5TFdYwQ3yHOlRE6JSWBDQ0rR6FOMyrrGy8To78mXow==} + kysely@0.28.9: resolution: {integrity: sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA==} engines: {node: '>=20.0.0'} @@ -4217,6 +4698,10 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4236,18 +4721,70 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash.lowercase@4.3.0: + resolution: {integrity: sha512-UcvP1IZYyDKyEL64mmrwoA1AbFu5ahojhTtkOUr1K9dbuxzS9ev8i4TxMMGCqRC9TE8uDaSoufNAXxRPNTseVA==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + lodash.lowerfirst@4.3.1: + resolution: {integrity: sha512-UUKX7VhP1/JL54NXg2aq/E1Sfnjjes8fNYTNkPU8ZmsaVeBvPHKdbNaN79Re5XRL01u6wbq3j0cbYZj71Fcu5w==} + + lodash.pad@4.5.1: + resolution: {integrity: sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg==} + + lodash.padend@4.6.1: + resolution: {integrity: sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==} + + lodash.padstart@4.6.1: + resolution: {integrity: sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw==} + + lodash.repeat@4.1.0: + resolution: {integrity: sha512-eWsgQW89IewS95ZOcr15HHCX6FVDxq3f2PNUIng3fyzsPev9imFQxIYdFZ6crl8L56UR6ZlGDLcEb3RZsCSSqw==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.trim@4.18.0: + resolution: {integrity: sha512-q8B9MlXzN9NaTtS2JCd7kKl3RqwrVURgKEXoHDII8A/v7y3tWOq3rLEe+vN6LNvT+EYBVKVt6roNQxMkosS2aA==} + + lodash.trimend@4.18.0: + resolution: {integrity: sha512-8w2M3nZAWLN1OX/6mTPCwRlZiD/LhVyPV9l7DEbkd9wybExvg9AcCjbD19swj6oVzX5hcMZHp3/Y1b4Sl3sHKg==} + + lodash.trimstart@4.5.1: + resolution: {integrity: sha512-b/+D6La8tU76L/61/aN0jULWHkT0EeJCmVstPBn/K9MtD2qBW83AsBNrr63dKuWYwVMO7ucv13QNO/Ek/2RKaQ==} + + lodash.uppercase@4.3.0: + resolution: {integrity: sha512-+Nbnxkj7s8K5U8z6KnEYPGUOGp3woZbB7Ecs7v3LkkjLQSm2kP9SKIILitN1ktn2mB/tmM9oSlku06I+/lH7QA==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@3.0.0: + resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} + engines: {node: '>=8'} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -4256,10 +4793,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: - resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} - engines: {node: 20 || >=22} - lru-cache@11.2.6: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} @@ -4267,6 +4800,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lucide-react@0.563.0: resolution: {integrity: sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==} peerDependencies: @@ -4292,6 +4829,10 @@ packages: main-event@1.0.1: resolution: {integrity: sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==} + make-dir@1.3.0: + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -4305,6 +4846,9 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + matchstick-as@0.6.0: + resolution: {integrity: sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4450,17 +4994,21 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} - minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -4521,6 +5069,9 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multiformats@13.1.3: + resolution: {integrity: sha512-CZPi9lFZCM/+7oRolWYsvalsyWQGFo+GpdaTmjxXXomC+nP/W1Rnxb9sUgjvmNmRZ5bOPqRAl4nuK+Ydw/4tGw==} + multiformats@13.4.2: resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} @@ -4545,6 +5096,11 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + native-fetch@4.0.2: + resolution: {integrity: sha512-4QcVlKFtv2EYVS5MBgsGX5+NWKtbDbIECdUXDBGDMAZXq3Jkv9zf+y8iS7Ub8fEdga3GpYeazp9gauNqXHJOCg==} + peerDependencies: + undici: '*' + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -4616,6 +5172,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -4653,10 +5213,18 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + open@7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} + ora@4.0.2: + resolution: {integrity: sha512-YUOZbamht5mfLxPmk4M35CD/5DuOkAacxlEUbStVXpBAt4fyhBf+vZHI/HRkI++QUp3sNoeA2Gw4C+hi4eGSig==} + engines: {node: '>=8'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -4672,6 +5240,10 @@ packages: typescript: optional: true + p-defer@3.0.0: + resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} + engines: {node: '>=8'} + p-defer@4.0.1: resolution: {integrity: sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==} engines: {node: '>=12'} @@ -4680,6 +5252,9 @@ packages: resolution: {integrity: sha512-nDq4JpyYnNPDrndgY3z9vQsB0X1bdsOcDmMFbLvewcnt38Geda9x+gULX3+RiGzwJ1QvzqTwNB4EQp+OwMOVAA==} engines: {node: '>=20'} + p-fifo@1.0.0: + resolution: {integrity: sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4731,6 +5306,13 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-duration@2.1.6: + resolution: {integrity: sha512-1/A2Exg3NcJGcYdgV/dn4frR7vO2hOW/ohQ4KIgbT4W3raVcpYSszPWiL6I6cKufi4jQM5NbGRXLBj8AoLM4iQ==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -4775,6 +5357,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + pg-boss@12.11.1: resolution: {integrity: sha512-gb7zgSac6RwpA6LQvgwY/yJtYeHrwjX7ksCK1WJs5Hi3mHx4/1eFBD+UtAMcm9JIYTMlBwttsnV2GxsArguRAg==} engines: {node: '>=22.12.0'} @@ -4829,6 +5414,22 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} @@ -4880,6 +5481,11 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -4888,6 +5494,9 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@4.0.1: resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} @@ -4901,6 +5510,10 @@ packages: progress-events@1.0.1: resolution: {integrity: sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + prom-client@15.1.3: resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} engines: {node: ^16 || ^18 || >=20} @@ -4908,6 +5521,9 @@ packages: promise@8.3.0: resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protons-runtime@5.6.0: resolution: {integrity: sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==} @@ -4994,6 +5610,9 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-native-fetch-api@3.0.0: + resolution: {integrity: sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==} + react-native-webrtc@124.0.7: resolution: {integrity: sha512-gnXPdbUS8IkKHq9WNaWptW/yy5s6nMyI6cNn90LXdobPVCgYSk6NA2uUGdT4c4J14BRgaFA95F+cR28tUPkMVA==} peerDependencies: @@ -5051,6 +5670,9 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -5093,6 +5715,10 @@ packages: regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} + engines: {node: '>=14'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5137,6 +5763,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -5151,6 +5782,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5160,9 +5795,16 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safe-regex2@5.0.0: resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} @@ -5198,10 +5840,19 @@ packages: secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + seek-bzip@1.0.6: + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.3.5: + resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -5370,6 +6021,15 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.9.1: + resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + + stream-to-it@1.0.1: + resolution: {integrity: sha512-AqHYAYPHcmvMrcLNgncE/q0Aj/ajP6A4qGhxP6EVn7K3YTNs0bJpJyk57wc2Heb7MUL64jurvmnmui8D9kjZgA==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5385,9 +6045,16 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -5400,6 +6067,13 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-dirs@2.1.0: + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -5434,6 +6108,10 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5472,6 +6150,10 @@ packages: tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-stream@1.6.2: + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -5511,6 +6193,9 @@ packages: throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} @@ -5547,6 +6232,13 @@ packages: resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} hasBin: true + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -5630,6 +6322,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@0.7.1: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} @@ -5730,9 +6426,16 @@ packages: uint8arrays@5.1.0: resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==} + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + undici@7.21.0: resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==} engines: {node: '>=20.18.1'} @@ -5770,6 +6473,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -5781,6 +6487,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -5789,6 +6498,10 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -5895,6 +6608,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wabt@1.0.24: + resolution: {integrity: sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg==} + hasBin: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -5908,6 +6625,26 @@ packages: weald@1.1.1: resolution: {integrity: sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ==} + web3-errors@1.3.1: + resolution: {integrity: sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-eth-abi@4.4.1: + resolution: {integrity: sha512-60ecEkF6kQ9zAfbTY04Nc9q4eEYM0++BySpGi8wZ2PD1tw/c0SDvsKhV6IKURxLJhsDlb08dATc3iD6IbtWJmg==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-types@1.10.0: + resolution: {integrity: sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-utils@4.3.3: + resolution: {integrity: sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-validator@2.0.6: + resolution: {integrity: sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==} + engines: {node: '>=14', npm: '>=6.12.0'} + webcrypto-core@1.8.1: resolution: {integrity: sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==} @@ -5974,6 +6711,13 @@ packages: engines: {node: '>=8'} hasBin: true + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -6029,6 +6773,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -6055,6 +6803,18 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -6068,6 +6828,9 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -6080,9 +6843,33 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} +catalogs: + default: + '@biomejs/biome': + specifier: 2.3.14 + version: 2.3.14 + '@swc/core': + specifier: 1.15.11 + version: 1.15.11 + '@vitest/coverage-v8': + specifier: 4.0.18 + version: 4.0.18 + typescript: + specifier: 5.9.3 + version: 5.9.3 + unplugin-swc: + specifier: 1.5.9 + version: 1.5.9 + vitest: + specifier: 4.0.18 + version: 4.0.18 + snapshots: '@acemir/cssom@0.9.31': {} @@ -6209,7 +6996,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6368,7 +7155,7 @@ snapshots: '@babel/parser': 7.29.0 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -6577,6 +7364,8 @@ snapshots: ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.1.0 + '@fastify/busboy@3.2.0': {} + '@fastify/error@4.2.0': {} '@fastify/fast-json-stringify-compiler@5.0.3': @@ -6626,6 +7415,55 @@ snapshots: transitivePeerDependencies: - typescript + '@float-capital/float-subgraph-uncrashable@0.0.0-internal-testing.5': + dependencies: + '@rescript/std': 9.0.0 + graphql: 16.12.0 + graphql-import-node: 0.0.5(graphql@16.12.0) + js-yaml: 4.1.1 + + '@graphprotocol/graph-cli@0.98.1(@types/node@25.2.3)(typescript@5.9.3)(zod@3.25.76)': + dependencies: + '@float-capital/float-subgraph-uncrashable': 0.0.0-internal-testing.5 + '@oclif/core': 4.5.5 + '@oclif/plugin-autocomplete': 3.2.45 + '@oclif/plugin-not-found': 3.2.80(@types/node@25.2.3) + '@oclif/plugin-warn-if-update-available': 3.1.60 + '@pinax/graph-networks-registry': 0.7.1 + '@whatwg-node/fetch': 0.10.13 + assemblyscript: 0.19.23 + chokidar: 4.0.3 + debug: 4.4.3(supports-color@8.1.1) + decompress: 4.2.1 + docker-compose: 1.3.0 + fs-extra: 11.3.2 + glob: 11.0.3 + gluegun: 5.2.0(debug@4.4.3) + graphql: 16.11.0 + immutable: 5.1.4 + jayson: 4.2.0 + js-yaml: 4.1.0 + kubo-rpc-client: 5.4.1(undici@7.16.0) + open: 10.2.0 + prettier: 3.6.2 + progress: 2.0.3 + semver: 7.7.3 + tmp-promise: 3.0.3 + undici: 7.16.0 + web3-eth-abi: 4.4.1(typescript@5.9.3)(zod@3.25.76) + yaml: 2.8.1 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - supports-color + - typescript + - utf-8-validate + - zod + + '@graphprotocol/graph-ts@0.38.2': + dependencies: + assemblyscript: 0.27.31 + '@hapi/address@5.1.1': dependencies: '@hapi/hoek': 11.0.7 @@ -6725,7 +7563,7 @@ snapshots: multiformats: 13.4.2 uint8arrays: 5.1.0 - '@helia/unixfs@7.1.0': + '@helia/unixfs@7.1.0(encoding@0.1.13)': dependencies: '@helia/interface': 6.1.1 '@ipld/dag-pb': 4.1.5 @@ -6736,7 +7574,7 @@ snapshots: interface-blockstore: 6.0.1 ipfs-unixfs: 12.0.1 ipfs-unixfs-exporter: 15.0.3 - ipfs-unixfs-importer: 16.1.4 + ipfs-unixfs-importer: 16.1.4(encoding@0.1.13) it-all: 3.0.9 it-first: 3.0.9 it-glob: 3.0.4 @@ -6963,12 +7801,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6978,6 +7810,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/ttlcache@1.4.1': {} '@istanbuljs/load-nyc-config@1.1.0': @@ -7227,6 +8061,17 @@ snapshots: '@multiformats/multiaddr': 13.0.1 progress-events: 1.0.1 + '@libp2p/interface@2.11.0': + dependencies: + '@multiformats/dns': 1.0.11 + '@multiformats/multiaddr': 12.5.1 + it-pushable: 3.2.3 + it-stream-types: 2.0.2 + main-event: 1.0.1 + multiformats: 13.4.2 + progress-events: 1.0.1 + uint8arraylist: 2.4.8 + '@libp2p/interface@3.1.0': dependencies: '@multiformats/dns': 1.0.11 @@ -7281,6 +8126,14 @@ snapshots: sanitize-filename: 1.6.3 uint8arrays: 5.1.0 + '@libp2p/logger@5.2.0': + dependencies: + '@libp2p/interface': 2.11.0 + '@multiformats/multiaddr': 12.5.1 + interface-datastore: 8.3.2 + multiformats: 13.4.2 + weald: 1.1.1 + '@libp2p/logger@6.2.2': dependencies: '@libp2p/interface': 3.1.0 @@ -7325,6 +8178,13 @@ snapshots: '@libp2p/utils': 7.0.13 multiformats: 13.4.2 + '@libp2p/peer-id@5.1.9': + dependencies: + '@libp2p/crypto': 5.1.13 + '@libp2p/interface': 2.11.0 + multiformats: 13.4.2 + uint8arrays: 5.1.0 + '@libp2p/peer-id@6.0.4': dependencies: '@libp2p/crypto': 5.1.13 @@ -7527,10 +8387,24 @@ snapshots: dependencies: '@multiformats/multiaddr': 13.0.1 + '@multiformats/multiaddr-to-uri@11.0.2': + dependencies: + '@multiformats/multiaddr': 12.5.1 + '@multiformats/multiaddr-to-uri@12.0.0': dependencies: '@multiformats/multiaddr': 13.0.1 + '@multiformats/multiaddr@12.5.1': + dependencies: + '@chainsafe/is-ip': 2.1.0 + '@chainsafe/netmask': 2.0.0 + '@multiformats/dns': 1.0.11 + abort-error: 1.0.1 + multiformats: 13.4.2 + uint8-varint: 2.0.4 + uint8arrays: 5.1.0 + '@multiformats/multiaddr@13.0.1': dependencies: '@chainsafe/is-ip': 2.1.0 @@ -7639,7 +8513,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/schedule@6.1.1(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)': + '@nestjs/schedule@6.1.1(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))': dependencies: '@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -7656,7 +8530,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.2.6(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.2.6(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.16.0 '@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -7671,7 +8545,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.3 - '@nestjs/testing@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-express@11.1.13)': + '@nestjs/testing@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13))': dependencies: '@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -7679,7 +8553,7 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) - '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(ts-node@10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(ts-node@10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3)))': dependencies: '@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -7691,6 +8565,10 @@ snapshots: '@noble/ciphers@2.1.1': {} + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + '@noble/curves@1.9.1': dependencies: '@noble/hashes': 1.8.0 @@ -7703,6 +8581,8 @@ snapshots: dependencies: '@noble/hashes': 2.0.1 + '@noble/hashes@1.4.0': {} + '@noble/hashes@1.8.0': {} '@noble/hashes@2.0.1': {} @@ -7723,6 +8603,77 @@ snapshots: dependencies: consola: 3.4.2 + '@oclif/core@4.10.5': + dependencies: + ansi-escapes: 4.3.2 + ansis: 3.17.0 + clean-stack: 3.0.1 + cli-spinners: 2.9.2 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + get-package-type: 0.1.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + lilconfig: 3.1.3 + minimatch: 10.2.5 + semver: 7.7.4 + string-width: 4.2.3 + supports-color: 8.1.1 + tinyglobby: 0.2.15 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + + '@oclif/core@4.5.5': + dependencies: + ansi-escapes: 4.3.2 + ansis: 3.17.0 + clean-stack: 3.0.1 + cli-spinners: 2.9.2 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + get-package-type: 0.1.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + lilconfig: 3.1.3 + minimatch: 9.0.5 + semver: 7.7.4 + string-width: 4.2.3 + supports-color: 8.1.1 + tinyglobby: 0.2.15 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + + '@oclif/plugin-autocomplete@3.2.45': + dependencies: + '@oclif/core': 4.10.5 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + transitivePeerDependencies: + - supports-color + + '@oclif/plugin-not-found@3.2.80(@types/node@25.2.3)': + dependencies: + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) + '@oclif/core': 4.10.5 + ansis: 3.17.0 + fast-levenshtein: 3.0.0 + transitivePeerDependencies: + - '@types/node' + + '@oclif/plugin-warn-if-update-available@3.1.60': + dependencies: + '@oclif/core': 4.10.5 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + http-call: 5.3.0 + lodash: 4.18.1 + registry-auth-token: 5.1.1 + transitivePeerDependencies: + - supports-color + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -8101,11 +9052,25 @@ snapshots: tslib: 2.8.1 tsyringe: 4.10.0 + '@pinax/graph-networks-registry@0.7.1': {} + '@pinojs/redact@0.4.0': {} '@pkgjs/parseargs@0.11.0': optional: true + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@3.0.2': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + '@prisma/instrumentation@7.4.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -8214,7 +9179,7 @@ snapshots: '@react-native/community-cli-plugin@0.83.1': dependencies: '@react-native/dev-middleware': 0.83.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 metro: 0.83.3 metro-config: 0.83.3 @@ -8240,7 +9205,7 @@ snapshots: chrome-launcher: 0.15.2 chromium-edge-launcher: 0.2.0 connect: 3.7.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) invariant: 2.2.4 nullthrows: 1.1.1 open: 7.4.2 @@ -8278,6 +9243,8 @@ snapshots: react: 19.2.4 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) + '@rescript/std@9.0.0': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/pluginutils@5.3.0(rollup@4.53.4)': @@ -8356,14 +9323,27 @@ snapshots: '@scarf/scarf@1.4.0': {} + '@scure/base@1.1.9': {} + '@scure/base@1.2.6': {} + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + '@scure/bip32@1.7.0': dependencies: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + '@scure/bip39@1.6.0': dependencies: '@noble/hashes': 1.8.0 @@ -8608,7 +9588,7 @@ snapshots: '@tokenizer/inflate@0.4.1': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) token-types: 6.1.1 transitivePeerDependencies: - supports-color @@ -8748,10 +9728,14 @@ snapshots: dependencies: '@types/node': 25.2.3 + '@types/node@12.20.55': {} + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 + '@types/parse-json@4.0.2': {} + '@types/pg-pool@2.0.7': dependencies: '@types/pg': 8.15.6 @@ -8813,6 +9797,10 @@ snapshots: '@types/validator@13.15.10': {} + '@types/ws@7.4.7': + dependencies: + '@types/node': 25.2.3 + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -8967,6 +9955,27 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@whatwg-node/disposablestack@0.0.6': + dependencies: + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@whatwg-node/fetch@0.10.13': + dependencies: + '@whatwg-node/node-fetch': 0.8.5 + urlpattern-polyfill: 10.1.0 + + '@whatwg-node/node-fetch@0.8.5': + dependencies: + '@fastify/busboy': 3.2.0 + '@whatwg-node/disposablestack': 0.0.6 + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@whatwg-node/promise-helpers@1.3.2': + dependencies: + tslib: 2.8.1 + '@willsoto/nestjs-prometheus@6.0.2(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(prom-client@15.1.3)': dependencies: '@nestjs/common': 11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -8976,6 +9985,12 @@ snapshots: '@xtuc/long@4.2.2': {} + abitype@0.7.1(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + optionalDependencies: + zod: 3.25.76 + abitype@1.2.3(typescript@5.9.3)(zod@4.3.6): optionalDependencies: typescript: 5.9.3 @@ -9004,7 +10019,7 @@ snapshots: '@peculiar/x509': 1.14.3 asn1js: 3.0.7 axios: 1.13.5(debug@4.4.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) node-forge: 1.3.3 transitivePeerDependencies: - supports-color @@ -9060,10 +10075,20 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@4.1.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -9072,6 +10097,8 @@ snapshots: ansi-styles@6.2.3: {} + ansis@3.17.0: {} + ansis@4.2.0: {} any-signal@4.2.0: {} @@ -9081,6 +10108,14 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + apisauce@2.1.6(debug@4.4.3): + dependencies: + axios: 0.21.4(debug@4.4.3) + transitivePeerDependencies: + - debug + + app-module-path@2.2.0: {} + app-root-path@3.1.0: {} append-field@1.0.0: {} @@ -9107,6 +10142,17 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 + assemblyscript@0.19.23: + dependencies: + binaryen: 102.0.0-nightly.20211028 + long: 5.3.2 + source-map-support: 0.5.21 + + assemblyscript@0.27.31: + dependencies: + binaryen: 116.0.0-nightly.20240114 + long: 5.3.2 + assertion-error@2.0.1: {} ast-v8-to-istanbul@0.3.11: @@ -9115,6 +10161,8 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + async@3.2.6: {} + asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -9133,6 +10181,12 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.20.1 + axios@0.21.4(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + transitivePeerDependencies: + - debug + axios@1.13.5(debug@4.4.3): dependencies: follow-redirects: 1.15.11(debug@4.4.3) @@ -9212,8 +10266,17 @@ snapshots: dependencies: require-from-string: 2.0.2 + binaryen@102.0.0-nightly.20211028: {} + + binaryen@116.0.0-nightly.20240114: {} + bintrees@1.0.2: {} + bl@1.2.3: + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -9226,6 +10289,10 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + blob-to-it@2.0.12: + dependencies: + browser-readablestream-to-it: 2.0.10 + blockstore-core@6.1.2: dependencies: '@libp2p/logger': 6.2.2 @@ -9240,7 +10307,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) http-errors: 2.0.1 iconv-lite: 0.7.1 on-finished: 2.4.1 @@ -9263,6 +10330,10 @@ snapshots: dependencies: balanced-match: 4.0.4 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -9281,6 +10352,17 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + + buffer-crc32@0.2.13: {} + + buffer-fill@1.0.0: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -9293,6 +10375,10 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -9328,6 +10414,12 @@ snapshots: chai@6.2.1: {} + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -9381,12 +10473,23 @@ snapshots: dependencies: clsx: 2.1.1 + clean-stack@3.0.1: + dependencies: + escape-string-regexp: 4.0.0 + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 cli-spinners@2.9.2: {} + cli-table3@0.6.0: + dependencies: + object-assign: 4.1.1 + string-width: 4.2.3 + optionalDependencies: + colors: 1.4.0 + cli-table3@0.6.5: dependencies: string-width: 4.2.3 @@ -9409,12 +10512,20 @@ snapshots: clsx@2.1.1: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} + colors@1.4.0: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -9456,6 +10567,11 @@ snapshots: semver: 7.7.4 uint8array-extras: 1.5.0 + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + connect@3.7.0: dependencies: debug: 2.6.9 @@ -9490,6 +10606,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cosmiconfig@7.0.1: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.3 + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 @@ -9510,6 +10634,12 @@ snapshots: '@types/luxon': 3.7.1 luxon: 3.7.2 + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -9570,6 +10700,11 @@ snapshots: d3-timer@3.0.1: {} + dag-jose@5.1.1: + dependencies: + '@ipld/dag-cbor': 9.2.5 + multiformats: 13.1.3 + data-urls@7.0.0(@noble/hashes@2.0.1): dependencies: whatwg-mimetype: 5.0.0 @@ -9608,9 +10743,11 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decimal.js-light@2.5.1: {} @@ -9620,12 +10757,57 @@ snapshots: dependencies: mimic-response: 3.1.0 + decompress-tar@4.1.1: + dependencies: + file-type: 5.2.0 + is-stream: 1.1.0 + tar-stream: 1.6.2 + + decompress-tarbz2@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 6.2.0 + is-stream: 1.1.0 + seek-bzip: 1.0.6 + unbzip2-stream: 1.4.3 + + decompress-targz@4.1.1: + dependencies: + decompress-tar: 4.1.1 + file-type: 5.2.0 + is-stream: 1.1.0 + + decompress-unzip@4.0.1: + dependencies: + file-type: 3.9.0 + get-stream: 2.3.1 + pify: 2.3.0 + yauzl: 2.10.0 + + decompress@4.2.1: + dependencies: + decompress-tar: 4.1.1 + decompress-tarbz2: 4.1.1 + decompress-targz: 4.1.1 + decompress-unzip: 4.0.1 + graceful-fs: 4.2.11 + make-dir: 1.3.0 + pify: 2.3.0 + strip-dirs: 2.1.0 + dedent@1.7.0: {} deep-extend@0.6.0: {} deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -9636,6 +10818,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + + delay@5.0.0: {} + delay@6.0.0: {} delay@7.0.0: @@ -9670,6 +10856,10 @@ snapshots: dependencies: from-exponential: 1.1.1 + docker-compose@1.3.0: + dependencies: + yaml: 2.8.2 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -9696,6 +10886,18 @@ snapshots: ee-first@1.1.1: {} + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + ejs@3.1.8: + dependencies: + jake: 10.9.4 + + electron-fetch@1.9.1: + dependencies: + encoding: 0.1.13 + electron-to-chromium@1.5.267: {} emoji-regex@8.0.0: {} @@ -9706,6 +10908,10 @@ snapshots: encodeurl@2.0.0: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -9715,6 +10921,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + enquirer@2.3.6: + dependencies: + ansi-colors: 4.1.3 + entities@6.0.1: {} env-paths@3.0.0: {} @@ -9750,6 +10960,12 @@ snapshots: es-toolkit@1.44.0: {} + es6-promise@4.2.8: {} + + es6-promisify@5.0.0: + dependencies: + es6-promise: 4.2.8 + esbuild@0.27.1: optionalDependencies: '@esbuild/aix-ppc64': 0.27.1 @@ -9783,6 +10999,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@2.0.0: {} escape-string-regexp@4.0.0: {} @@ -9810,6 +11028,13 @@ snapshots: etag@1.8.1: {} + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + event-target-shim@5.0.1: {} event-target-shim@6.0.2: {} @@ -9820,6 +11045,18 @@ snapshots: events@3.3.0: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + expand-template@2.0.3: {} expect-type@1.3.0: {} @@ -9834,7 +11071,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -9859,10 +11096,14 @@ snapshots: transitivePeerDependencies: - supports-color + eyes@0.1.8: {} + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -9882,6 +11123,10 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 + fast-levenshtein@3.0.0: + dependencies: + fastest-levenshtein: 1.0.16 + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -9890,6 +11135,8 @@ snapshots: fast-uri@3.1.0: {} + fastest-levenshtein@1.0.16: {} + fastify@5.8.2: dependencies: '@fastify/ajv-compiler': 4.0.5 @@ -9918,6 +11165,10 @@ snapshots: dependencies: bser: 2.1.1 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -9931,7 +11182,13 @@ snapshots: transitivePeerDependencies: - supports-color - filecoin-pin@0.20.0(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)(zod@4.3.6): + file-type@3.9.0: {} + + file-type@5.2.0: {} + + file-type@6.2.0: {} + + filecoin-pin@0.20.0(encoding@0.1.13)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)(zod@4.3.6): dependencies: '@chainsafe/libp2p-noise': 17.0.0 '@chainsafe/libp2p-yamux': 8.0.1 @@ -9939,7 +11196,7 @@ snapshots: '@filoz/synapse-core': 0.3.3(typescript@5.9.3)(viem@2.47.5(typescript@5.9.3)(zod@4.3.6)) '@filoz/synapse-sdk': 0.40.2(typescript@5.9.3)(viem@2.47.5(typescript@5.9.3)(zod@4.3.6)) '@helia/block-brokers': 5.1.4 - '@helia/unixfs': 7.1.0 + '@helia/unixfs': 7.1.0(encoding@0.1.13) '@ipld/car': 5.4.2 '@libp2p/identify': 4.0.13 '@libp2p/tcp': 11.0.13 @@ -9970,6 +11227,10 @@ snapshots: - utf-8-validate - zod + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -9988,7 +11249,7 @@ snapshots: finalhandler@2.1.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -10012,7 +11273,7 @@ snapshots: follow-redirects@1.15.11(debug@4.4.3): optionalDependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) for-each@0.3.5: dependencies: @@ -10035,7 +11296,7 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.3 + semver: 7.7.4 tapable: 2.3.0 typescript: 5.9.3 webpack: 5.104.1(@swc/core@1.15.11) @@ -10074,6 +11335,17 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-jetpack@4.3.1: + dependencies: + minimatch: 3.1.2 + rimraf: 2.7.1 + fs-monkey@1.1.0: {} fs.realpath@1.0.0: {} @@ -10085,6 +11357,8 @@ snapshots: function-timeout@0.1.1: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -10102,6 +11376,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-iterator@1.0.2: {} + get-iterator@2.0.1: {} get-package-type@0.1.0: {} @@ -10113,6 +11389,13 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@2.3.1: + dependencies: + object-assign: 4.1.1 + pinkie-promise: 2.0.1 + + get-stream@6.0.1: {} + github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -10130,9 +11413,18 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + glob@13.0.0: dependencies: - minimatch: 10.1.1 + minimatch: 10.2.4 minipass: 7.1.2 path-scurry: 2.0.1 @@ -10145,10 +11437,53 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + gluegun@5.2.0(debug@4.4.3): + dependencies: + apisauce: 2.1.6(debug@4.4.3) + app-module-path: 2.2.0 + cli-table3: 0.6.0 + colors: 1.4.0 + cosmiconfig: 7.0.1 + cross-spawn: 7.0.3 + ejs: 3.1.8 + enquirer: 2.3.6 + execa: 5.1.1 + fs-jetpack: 4.3.1 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.lowercase: 4.3.0 + lodash.lowerfirst: 4.3.1 + lodash.pad: 4.5.1 + lodash.padend: 4.6.1 + lodash.padstart: 4.6.1 + lodash.repeat: 4.1.0 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.trim: 4.18.0 + lodash.trimend: 4.18.0 + lodash.trimstart: 4.5.1 + lodash.uppercase: 4.3.0 + lodash.upperfirst: 4.3.1 + ora: 4.0.2 + pluralize: 8.0.0 + semver: 7.3.5 + which: 2.0.2 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - debug + gopd@1.2.0: {} + graceful-fs@4.2.10: {} + graceful-fs@4.2.11: {} + graphql-import-node@0.0.5(graphql@16.12.0): + dependencies: + graphql: 16.12.0 + + graphql@16.11.0: {} + graphql@16.12.0: {} hamt-sharding@3.0.6: @@ -10156,6 +11491,8 @@ snapshots: sparse-array: 1.3.2 uint8arrays: 5.1.0 + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -10235,6 +11572,17 @@ snapshots: html-escaper@2.0.2: {} + http-call@5.3.0: + dependencies: + content-type: 1.0.5 + debug: 4.4.3(supports-color@8.1.1) + is-retry-allowed: 1.2.0 + is-stream: 2.0.1 + parse-json: 4.0.0 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - supports-color + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -10246,17 +11594,23 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.1: dependencies: safer-buffer: 2.1.2 @@ -10273,6 +11627,8 @@ snapshots: immer@11.1.3: {} + immutable@5.1.4: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -10310,11 +11666,18 @@ snapshots: interface-store: 7.0.1 multiformats: 13.4.2 + interface-datastore@8.3.2: + dependencies: + interface-store: 6.0.3 + uint8arrays: 5.1.0 + interface-datastore@9.0.2: dependencies: interface-store: 7.0.1 uint8arrays: 5.1.0 + interface-store@6.0.3: {} + interface-store@7.0.1: {} internmap@2.0.3: {} @@ -10348,7 +11711,7 @@ snapshots: p-queue: 9.1.0 progress-events: 1.0.1 - ipfs-unixfs-importer@16.1.4: + ipfs-unixfs-importer@16.1.4(encoding@0.1.13): dependencies: '@ipld/dag-pb': 4.1.5 '@multiformats/murmur3': 2.1.8 @@ -10363,13 +11726,18 @@ snapshots: it-parallel-batch: 3.0.9 multiformats: 13.4.2 progress-events: 1.0.1 - rabin-wasm: 0.1.5 + rabin-wasm: 0.1.5(encoding@0.1.13) uint8arraylist: 2.4.8 uint8arrays: 5.1.0 transitivePeerDependencies: - encoding - supports-color + ipfs-unixfs@11.2.5: + dependencies: + protons-runtime: 5.6.0 + uint8arraylist: 2.4.8 + ipfs-unixfs@12.0.1: dependencies: protons-runtime: 5.6.0 @@ -10388,22 +11756,41 @@ snapshots: uint8arraylist: 2.4.8 uint8arrays: 5.1.0 + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-arrayish@0.2.1: {} is-callable@1.2.7: {} is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-electron@2.2.2: {} is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@1.0.0: {} is-ip@5.0.1: @@ -10413,6 +11800,8 @@ snapshots: is-loopback-addr@2.0.2: {} + is-natural-number@4.0.1: {} + is-network-error@1.3.0: {} is-node-process@1.2.0: {} @@ -10425,8 +11814,21 @@ snapshots: is-promise@4.0.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + is-regexp@3.1.0: {} + is-retry-allowed@1.2.0: {} + + is-stream@1.1.0: {} + + is-stream@2.0.1: {} + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.19 @@ -10437,6 +11839,12 @@ snapshots: dependencies: is-docker: 2.2.1 + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isarray@1.0.0: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -10447,12 +11855,18 @@ snapshots: idb-keyval: 6.2.2 kysely: 0.28.9 + iso-url@1.2.1: {} + iso-web@2.1.1: dependencies: delay: 7.0.0 iso-kv: 3.1.1 p-retry: 7.1.1 + isomorphic-ws@4.0.1(ws@7.5.10): + dependencies: + ws: 7.5.10 + isows@1.0.7(ws@8.18.3): dependencies: ws: 8.18.3 @@ -10602,6 +12016,15 @@ snapshots: dependencies: uint8arrays: 5.1.0 + it-to-stream@1.0.0: + dependencies: + buffer: 6.0.3 + fast-fifo: 1.3.2 + get-iterator: 1.0.2 + p-defer: 3.0.0 + p-fifo: 1.0.0 + readable-stream: 3.6.2 + iterare@1.2.1: {} jackspeak@3.4.3: @@ -10610,6 +12033,34 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + + jayson@4.2.0: + dependencies: + '@types/connect': 3.4.38 + '@types/node': 12.20.55 + '@types/ws': 7.4.7 + commander: 2.20.3 + delay: 5.0.0 + es6-promisify: 5.0.0 + eyes: 0.1.8 + isomorphic-ws: 4.0.1(ws@7.5.10) + json-stringify-safe: 5.0.1 + stream-json: 1.9.1 + uuid: 8.3.2 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 @@ -10709,6 +12160,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -10743,6 +12198,8 @@ snapshots: jsesc@3.1.0: {} + json-parse-better-errors@1.0.2: {} + json-parse-even-better-errors@2.3.1: {} json-schema-ref-resolver@3.0.0: @@ -10755,6 +12212,8 @@ snapshots: json-schema-typed@8.0.2: {} + json-stringify-safe@5.0.1: {} + json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -10765,6 +12224,44 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + kubo-rpc-client@5.4.1(undici@7.16.0): + dependencies: + '@ipld/dag-cbor': 9.2.5 + '@ipld/dag-json': 10.2.5 + '@ipld/dag-pb': 4.1.5 + '@libp2p/crypto': 5.1.13 + '@libp2p/interface': 2.11.0 + '@libp2p/logger': 5.2.0 + '@libp2p/peer-id': 5.1.9 + '@multiformats/multiaddr': 12.5.1 + '@multiformats/multiaddr-to-uri': 11.0.2 + any-signal: 4.2.0 + blob-to-it: 2.0.12 + browser-readablestream-to-it: 2.0.10 + dag-jose: 5.1.1 + electron-fetch: 1.9.1 + err-code: 3.0.1 + ipfs-unixfs: 11.2.5 + iso-url: 1.2.1 + it-all: 3.0.9 + it-first: 3.0.9 + it-glob: 3.0.4 + it-last: 3.0.9 + it-map: 3.1.4 + it-peekable: 3.0.8 + it-to-stream: 1.0.0 + merge-options: 3.0.4 + multiformats: 13.4.2 + nanoid: 5.1.6 + native-fetch: 4.0.2(undici@7.16.0) + parse-duration: 2.1.6 + react-native-fetch-api: 3.0.0 + stream-to-it: 1.0.1 + uint8arrays: 5.1.0 + wherearewe: 2.0.1 + transitivePeerDependencies: + - undici + kysely@0.28.9: {} leven@3.1.0: {} @@ -10863,6 +12360,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} load-esm@1.0.3: {} @@ -10875,31 +12374,69 @@ snapshots: dependencies: p-locate: 4.1.0 + lodash.camelcase@4.3.0: {} + + lodash.kebabcase@4.1.1: {} + + lodash.lowercase@4.3.0: {} + + lodash.lowerfirst@4.3.1: {} + + lodash.pad@4.5.1: {} + + lodash.padend@4.6.1: {} + + lodash.padstart@4.6.1: {} + + lodash.repeat@4.1.0: {} + + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + lodash.throttle@4.1.1: {} - lodash@4.17.21: {} + lodash.trim@4.18.0: {} + + lodash.trimend@4.18.0: {} + + lodash.trimstart@4.5.1: {} + + lodash.uppercase@4.3.0: {} + + lodash.upperfirst@4.3.1: {} lodash@4.17.23: {} + lodash@4.18.1: {} + + log-symbols@3.0.0: + dependencies: + chalk: 2.4.2 + log-symbols@4.1.0: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + long@5.3.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 lru-cache@10.4.3: {} - lru-cache@11.2.4: {} - lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lucide-react@0.563.0(react@19.2.4): dependencies: react: 19.2.4 @@ -10924,9 +12461,13 @@ snapshots: main-event@1.0.1: {} + make-dir@1.3.0: + dependencies: + pify: 3.0.0 + make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 make-error@1.3.6: {} @@ -10936,6 +12477,10 @@ snapshots: marky@1.3.0: {} + matchstick-as@0.6.0: + dependencies: + wabt: 1.0.24 + math-intrinsics@1.1.0: {} mdn-data@2.12.2: {} @@ -11007,7 +12552,7 @@ snapshots: metro-file-map@0.83.3: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -11103,7 +12648,7 @@ snapshots: chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -11166,18 +12711,22 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.1.1: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -11250,6 +12799,8 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + multiformats@13.1.3: {} + multiformats@13.4.2: {} murmurhash3js-revisited@3.0.0: {} @@ -11262,6 +12813,10 @@ snapshots: napi-build-utils@2.0.0: {} + native-fetch@4.0.2(undici@7.16.0): + dependencies: + undici: 7.16.0 + negotiator@0.6.3: {} negotiator@1.0.0: {} @@ -11294,11 +12849,13 @@ snapshots: node-emoji@1.11.0: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 - node-fetch@2.7.0: + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 node-forge@1.3.3: {} @@ -11310,6 +12867,10 @@ snapshots: normalize-path@3.0.0: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + nullthrows@1.1.1: {} ob1@0.83.3: @@ -11340,11 +12901,28 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@7.4.2: dependencies: is-docker: 2.2.1 is-wsl: 2.2.0 + ora@4.0.2: + dependencies: + chalk: 2.4.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + log-symbols: 3.0.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -11374,12 +12952,19 @@ snapshots: transitivePeerDependencies: - zod + p-defer@3.0.0: {} + p-defer@4.0.1: {} p-event@7.0.2: dependencies: p-timeout: 6.1.4 + p-fifo@1.0.0: + dependencies: + fast-fifo: 1.3.2 + p-defer: 3.0.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -11421,6 +13006,13 @@ snapshots: dependencies: callsites: 3.1.0 + parse-duration@2.1.6: {} + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -11447,7 +13039,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.6 minipass: 7.1.2 path-to-regexp@6.3.0: {} @@ -11458,6 +13050,8 @@ snapshots: pathe@2.0.3: {} + pend@1.2.0: {} + pg-boss@12.11.1: dependencies: cron-parser: 5.5.0 @@ -11509,6 +13103,16 @@ snapshots: picomatch@4.0.3: {} + pify@2.3.0: {} + + pify@3.0.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 @@ -11573,6 +13177,8 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + prettier@3.6.2: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -11585,6 +13191,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + process-nextick-args@2.0.1: {} + process-warning@4.0.1: {} process-warning@5.0.0: {} @@ -11593,6 +13201,8 @@ snapshots: progress-events@1.0.1: {} + progress@2.0.3: {} + prom-client@15.1.3: dependencies: '@opentelemetry/api': 1.9.0 @@ -11602,6 +13212,8 @@ snapshots: dependencies: asap: 2.0.6 + proto-list@1.2.4: {} + protons-runtime@5.6.0: dependencies: uint8-varint: 2.0.4 @@ -11640,13 +13252,13 @@ snapshots: quick-format-unescaped@4.0.4: {} - rabin-wasm@0.1.5: + rabin-wasm@0.1.5(encoding@0.1.13): dependencies: '@assemblyscript/loader': 0.9.4 bl: 5.1.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) minimist: 1.2.8 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) readable-stream: 3.6.2 transitivePeerDependencies: - encoding @@ -11704,6 +13316,10 @@ snapshots: react-is@18.3.1: {} + react-native-fetch-api@3.0.0: + dependencies: + p-defer: 3.0.0 + react-native-webrtc@124.0.7(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4)): dependencies: base64-js: 1.5.1 @@ -11790,6 +13406,16 @@ snapshots: react@19.2.4: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -11843,13 +13469,17 @@ snapshots: regenerator-runtime@0.13.11: {} + registry-auth-token@5.1.1: + dependencies: + '@pnpm/npm-conf': 3.0.2 + require-directory@2.1.1: {} require-from-string@2.0.2: {} require-in-the-middle@8.0.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) module-details-from-path: 1.0.4 transitivePeerDependencies: - supports-color @@ -11875,6 +13505,10 @@ snapshots: rfdc@1.4.1: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -11909,7 +13543,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -11917,6 +13551,8 @@ snapshots: transitivePeerDependencies: - supports-color + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -11929,8 +13565,16 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + safe-regex2@5.0.0: dependencies: ret: 0.5.0 @@ -11966,8 +13610,16 @@ snapshots: secure-json-parse@4.1.0: {} + seek-bzip@1.0.6: + dependencies: + commander: 2.20.3 + semver@6.3.1: {} + semver@7.3.5: + dependencies: + lru-cache: 6.0.0 + semver@7.7.3: {} semver@7.7.4: {} @@ -11992,7 +13644,7 @@ snapshots: send@1.2.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -12153,6 +13805,16 @@ snapshots: std-env@3.10.0: {} + stream-chain@2.2.5: {} + + stream-json@1.9.1: + dependencies: + stream-chain: 2.2.5 + + stream-to-it@1.0.1: + dependencies: + it-stream-types: 2.0.2 + streamsearch@1.1.0: {} strict-event-emitter@0.5.1: {} @@ -12169,10 +13831,18 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -12183,6 +13853,12 @@ snapshots: strip-bom@3.0.0: {} + strip-dirs@2.1.0: + dependencies: + is-natural-number: 4.0.1 + + strip-final-newline@2.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -12209,7 +13885,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.5 formidable: 3.5.4 @@ -12229,6 +13905,10 @@ snapshots: supports-color@10.2.2: {} + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12264,6 +13944,16 @@ snapshots: pump: 3.0.3 tar-stream: 2.2.0 + tar-stream@1.6.2: + dependencies: + bl: 1.2.3 + buffer-alloc: 1.2.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + readable-stream: 2.3.8 + to-buffer: 1.2.2 + xtend: 4.0.2 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -12306,6 +13996,8 @@ snapshots: throat@5.0.0: {} + through@2.3.8: {} + thunky@1.1.0: {} time-span@5.1.0: @@ -12333,6 +14025,12 @@ snapshots: dependencies: tldts-core: 7.0.23 + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.5 + + tmp@0.2.5: {} + tmpl@1.0.5: {} to-buffer@1.2.2: @@ -12426,6 +14124,8 @@ snapshots: type-detect@4.0.8: {} + type-fest@0.21.3: {} + type-fest@0.7.1: {} type-fest@5.4.4: @@ -12458,7 +14158,7 @@ snapshots: app-root-path: 3.1.0 buffer: 6.0.3 dayjs: 1.11.19 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) dedent: 1.7.0 dotenv: 16.6.1 glob: 10.5.0 @@ -12496,8 +14196,15 @@ snapshots: dependencies: multiformats: 13.4.2 + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + undici-types@7.16.0: {} + undici@7.16.0: {} + undici@7.21.0: {} universalify@2.0.1: {} @@ -12534,6 +14241,8 @@ snapshots: dependencies: punycode: 2.3.1 + urlpattern-polyfill@10.1.0: {} + use-sync-external-store@1.6.0(react@19.2.4): dependencies: react: 19.2.4 @@ -12542,10 +14251,20 @@ snapshots: util-deprecate@1.0.2: {} + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 + utils-merge@1.0.1: {} uuid@11.1.0: {} + uuid@8.3.2: {} + v8-compile-cache-lib@3.0.1: {} validator@13.15.23: {} @@ -12649,6 +14368,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wabt@1.0.24: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -12667,6 +14388,39 @@ snapshots: ms: 3.0.0-canary.202508261828 supports-color: 10.2.2 + web3-errors@1.3.1: + dependencies: + web3-types: 1.10.0 + + web3-eth-abi@4.4.1(typescript@5.9.3)(zod@3.25.76): + dependencies: + abitype: 0.7.1(typescript@5.9.3)(zod@3.25.76) + web3-errors: 1.3.1 + web3-types: 1.10.0 + web3-utils: 4.3.3 + web3-validator: 2.0.6 + transitivePeerDependencies: + - typescript + - zod + + web3-types@1.10.0: {} + + web3-utils@4.3.3: + dependencies: + ethereum-cryptography: 2.2.1 + eventemitter3: 5.0.4 + web3-errors: 1.3.1 + web3-types: 1.10.0 + web3-validator: 2.0.6 + + web3-validator@2.0.6: + dependencies: + ethereum-cryptography: 2.2.1 + util: 0.12.5 + web3-errors: 1.3.1 + web3-types: 1.10.0 + zod: 3.25.76 + webcrypto-core@1.8.1: dependencies: '@peculiar/asn1-schema': 2.6.0 @@ -12759,6 +14513,12 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + wordwrap@1.0.0: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -12790,6 +14550,10 @@ snapshots: ws@8.19.0: {} + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xml-name-validator@5.0.0: {} xml2js@0.6.2: @@ -12807,6 +14571,12 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + + yaml@1.10.3: {} + + yaml@2.8.1: {} + yaml@2.8.2: {} yargs-parser@21.1.1: {} @@ -12821,10 +14591,17 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yn@3.1.1: {} yocto-queue@1.2.2: {} yoctocolors-cjs@2.1.3: {} + zod@3.25.76: {} + zod@4.3.6: {}