diff --git a/.env.example b/.env.example index 18a9b3d7..805af528 100644 --- a/.env.example +++ b/.env.example @@ -25,19 +25,14 @@ NEXT_PUBLIC_VIBENET_RPC_URL=https://rpc.vibes.base.org # Only affects local runs — Vercel sets no value, so the public build is external. # NEXT_PUBLIC_DEPLOY_TARGET=internal -# Internal Explorer: per-chain S3 + RPC, read server-side by +# Internal Explorer: per-chain RPC + audit endpoints, read server-side by # /api/internal-explorer (chain via ?chain=). # One deployment serves all chains; provide a set per chain # (TIPS_MAINNET_, TIPS_SEPOLIA_, TIPS_ZERONET_). Secrets → Vercel env, not git. -# TIPS_MAINNET_S3_BUCKET= -# TIPS_MAINNET_S3_REGION=us-east-1 -# TIPS_MAINNET_S3_ENDPOINT= # optional (e.g. MinIO); omit for AWS -# TIPS_MAINNET_S3_ACCESS_KEY_ID= -# TIPS_MAINNET_S3_SECRET_ACCESS_KEY= # TIPS_MAINNET_RPC_URL= -# Audit events JSON-RPC endpoint (transaction observability). Opt-in per chain: -# when set, /api/internal-explorer prefers it over S3 and falls back to S3 when -# unset/empty. +# Audit events JSON-RPC endpoint (Postgres-backed transaction observability). +# Opt-in per chain: the only source for bundle and rejected views, and the +# enrichment source for block and transaction views. # TIPS_MAINNET_AUDIT_RPC_URL= # Public origin that serves a chain's Internal Explorer (runtime, not # NEXT_PUBLIC_*). Unset locally so the chain toggle stays on this origin. diff --git a/app/api/internal-explorer/block/[hash]/route.ts b/app/api/internal-explorer/block/[hash]/route.ts index 443b57f6..d260b951 100644 --- a/app/api/internal-explorer/block/[hash]/route.ts +++ b/app/api/internal-explorer/block/[hash]/route.ts @@ -16,13 +16,7 @@ import { import { getAuditRpcUrl, getRpcUrl } from '../../config'; import { explorerDisabledResponse } from '../../guard'; import { getTransactionReceiptSummaries } from '../../receipts'; -import { - cacheBlockData, - getBlockFromCache, - getBundleHistory, - getTransactionMetadataByHash, -} from '../../s3'; -import type { BlockData, BlockTransaction, BundleEvent } from '../../transaction-data'; +import type { BlockData, BlockTransaction } from '../../transaction-data'; import { publicClientFor } from '../../viem'; export const runtime = 'nodejs'; @@ -168,13 +162,6 @@ function timedAuditEvents(events: AuditTransactionEventRecord[]): TimedExplorerE })); } -function timedHistoryEvents(history: BundleEvent[]): TimedExplorerEvent[] { - return history.map((event) => ({ - event: event.event, - timestamp: event.data.timestamp, - })); -} - function auditEventsForHash( txHash: string, events: AuditTransactionEventRecord[], @@ -183,43 +170,14 @@ function auditEventsForHash( return events.filter((event) => event.tx_hash?.toLowerCase() === normalized); } -async function enrichTransactionFromS3( - chain: ExplorerChain, - txHash: string, -): Promise<{ - bundleId: string | null; - meterBundleResponse: Record | null; - history: BundleEvent[]; -}> { - const metadata = await getTransactionMetadataByHash(chain, txHash); - if (!metadata || metadata.bundle_ids.length === 0) { - return { bundleId: null, meterBundleResponse: null, history: [] }; - } - - const bundleId = metadata.bundle_ids[0]; - const bundleHistory = await getBundleHistory(chain, bundleId); - if (!bundleHistory) { - return { bundleId, meterBundleResponse: null, history: [] }; - } - - const receivedEvent = bundleHistory.history.find((event) => event.event === 'Received'); - return { - bundleId, - meterBundleResponse: receivedEvent?.data?.bundle?.meter_bundle_response - ? (receivedEvent.data.bundle.meter_bundle_response as unknown as Record) - : null, - history: bundleHistory.history, - }; -} - type TransactionEnrichment = { bundleId: string | null; meterBundleResponse: Record | null; inclusionLatencyMs: number | null; }; -// Audit-first, S3-fallback per-transaction enrichment (bundle id, metering, and -// inclusion latency from the same event set the transaction page uses). +// Audit-backed per-transaction enrichment (bundle id, metering, and inclusion +// latency from the same event set the transaction page uses). async function enrichTransactionWithBundleData( chain: ExplorerChain, txHash: string, @@ -232,7 +190,7 @@ async function enrichTransactionWithBundleData( try { hashEvents = await getAuditEventsByTransactionHash(auditRpcUrl, txHash); } catch { - // Audit is an optional read path; fall back to the S3-backed enrichment on errors. + // Audit is an optional read path; render the block without enrichment on errors. } } @@ -240,10 +198,10 @@ async function enrichTransactionWithBundleData( hashEvents, auditEventsForHash(txHash, blockAuditEvents), ]); - let inclusionLatency = inclusionLatencyMs(timedAuditEvents(mergedAuditEvents)); + const inclusionLatency = inclusionLatencyMs(timedAuditEvents(mergedAuditEvents)); const metadata = transactionMetadataFromAuditEvents(hashEvents); - let bundleId = metadata?.bundle_ids[0] ?? null; + const bundleId = metadata?.bundle_ids[0] ?? null; let meterBundleResponse: Record | null = null; if (bundleId !== null) { const accepted = hashEvents.find((event) => event.event_type === 'SIMULATION_SUCCEEDED'); @@ -252,13 +210,6 @@ async function enrichTransactionWithBundleData( : null; } - if (bundleId === null) { - const s3 = await enrichTransactionFromS3(chain, txHash); - bundleId = s3.bundleId; - meterBundleResponse = s3.meterBundleResponse; - inclusionLatency = inclusionLatency ?? inclusionLatencyMs(timedHistoryEvents(s3.history)); - } - return { bundleId, meterBundleResponse, @@ -335,17 +286,6 @@ async function buildBlockData(chain: ExplorerChain, rpcBlock: ParsedFullBlock): }; } -// Build live from RPC (audit-first enrichment), writing through to the block -// cache, which also serves as a fallback when the RPC is unavailable. -async function buildAndCacheBlockData( - chain: ExplorerChain, - rpcBlock: ParsedFullBlock, -): Promise { - const blockData = await buildBlockData(chain, rpcBlock); - await cacheBlockData(chain, blockData); - return blockData; -} - export async function GET(request: Request, { params }: { params: Promise<{ hash: string }> }) { const disabled = explorerDisabledResponse(); if (disabled) return disabled; @@ -362,21 +302,16 @@ export async function GET(request: Request, { params }: { params: Promise<{ hash return Response.json({ error: 'Block not found' }, { status: 404 }); } - const blockData = await buildAndCacheBlockData(chain, rpcBlock); + const blockData = await buildBlockData(chain, rpcBlock); return Response.json(serializeBlockData(blockData)); } const rpcBlock = await fetchBlockFromRpc(rpcUrl, identifier); if (!rpcBlock) { - // RPC could not serve the block; fall back to the block cache. - const cachedBlock = await getBlockFromCache(chain, identifier); - if (cachedBlock) { - return Response.json(serializeBlockData(cachedBlock)); - } return Response.json({ error: 'Block not found' }, { status: 404 }); } - const blockData = await buildAndCacheBlockData(chain, rpcBlock); + const blockData = await buildBlockData(chain, rpcBlock); return Response.json(serializeBlockData(blockData)); } catch (error) { console.error('Error fetching block data:', error); diff --git a/app/api/internal-explorer/bundle/[hash]/route.ts b/app/api/internal-explorer/bundle/[hash]/route.ts index 8d1f789a..1cafd14f 100644 --- a/app/api/internal-explorer/bundle/[hash]/route.ts +++ b/app/api/internal-explorer/bundle/[hash]/route.ts @@ -1,6 +1,5 @@ import { type Hash } from 'viem'; -import { type ExplorerChain } from '../../../../internal-explorer/chains'; import { resolveExplorerChainFromRequest } from '../../chain'; import { bundleHistoryFromAuditEvents, @@ -8,8 +7,7 @@ import { } from '../../audit-events'; import { getAuditRpcUrl, getRpcUrl } from '../../config'; import { explorerDisabledResponse } from '../../guard'; -import { getBundleHistory } from '../../s3'; -import type { BundleEvent, BundleHistory, BundleTransaction } from '../../transaction-data'; +import type { BundleEvent, BundleTransaction } from '../../transaction-data'; import { publicClientFor, type ExplorerPublicClient } from '../../viem'; export const runtime = 'nodejs'; @@ -51,7 +49,7 @@ function bundleTransactionFromViem( } // Fills in full transaction fields for each bundle tx hash from the execution RPC. -// Audit/S3 bundle events only carry the tx hashes (and metering), so the on-chain +// Audit bundle events only carry the tx hashes (and metering), so the on-chain // signer/value/gas come from getTransaction. async function enrichBundleTransactionsFromRpc( rpcUrl: string, @@ -98,10 +96,24 @@ export async function GET(request: Request, { params }: { params: Promise<{ hash if (disabled) return disabled; const chain = resolveExplorerChainFromRequest(request); + // Bundle history comes only from the audit events RPC (Postgres-backed). + const auditRpcUrl = getAuditRpcUrl(chain); + if (!auditRpcUrl) { + return Response.json({ error: 'Bundle not found' }, { status: 404 }); + } + + const { hash } = await params; + + let events; try { - const { hash } = await params; + events = await getJoinedAuditEventsByBundle(auditRpcUrl, hash); + } catch (error) { + console.error('Error fetching bundle history from audit RPC:', error); + return Response.json({ error: 'Audit RPC unavailable' }, { status: 502 }); + } - const bundle = await getAuditBundleHistory(chain, hash); + try { + const bundle = bundleHistoryFromAuditEvents(hash, events); if (!bundle) { return Response.json({ error: 'Bundle not found' }, { status: 404 }); } @@ -120,21 +132,3 @@ export async function GET(request: Request, { params }: { params: Promise<{ hash return Response.json({ error: 'Internal server error' }, { status: 500 }); } } - -async function getAuditBundleHistory( - chain: ExplorerChain, - hash: string, -): Promise { - const auditRpcUrl = getAuditRpcUrl(chain); - if (!auditRpcUrl) { - return getBundleHistory(chain, hash); - } - - try { - const events = await getJoinedAuditEventsByBundle(auditRpcUrl, hash); - return bundleHistoryFromAuditEvents(hash, events) ?? (await getBundleHistory(chain, hash)); - } catch (error) { - console.error('Falling back to S3 bundle history:', error); - return getBundleHistory(chain, hash); - } -} diff --git a/app/api/internal-explorer/config.ts b/app/api/internal-explorer/config.ts index 7df99d98..661bcebf 100644 --- a/app/api/internal-explorer/config.ts +++ b/app/api/internal-explorer/config.ts @@ -1,8 +1,6 @@ // Per-chain server config for the Internal Explorer API. One deployment serves all chains, so -// the S3 client, bucket, and RPC/audit URLs are resolved per ExplorerChain from -// TIPS__* env vars. Server-only: never import from client. -import { S3Client, type S3ClientConfig } from '@aws-sdk/client-s3'; - +// the RPC/audit URLs are resolved per ExplorerChain from TIPS__* env vars. +// Server-only: never import from client. import type { ExplorerChain } from '../../internal-explorer/chains'; import type { ExplorerHostMap } from '../../internal-explorer/hosts'; @@ -13,73 +11,18 @@ const ENV_PREFIX: Record = { zeronet: 'ZERONET', }; -type ChainS3Config = { - bucket: string; - region: string; - endpoint?: string; - accessKeyId?: string; - secretAccessKey?: string; -}; - function envValue(names: string[]): string | undefined { return names.map((name) => process.env[name]).find(Boolean); } -function getS3Config(chain: ExplorerChain): ChainS3Config { - const prefix = ENV_PREFIX[chain]; - return { - bucket: envValue([`TIPS_${prefix}_S3_BUCKET`]) ?? 'tips', - region: envValue([`TIPS_${prefix}_S3_REGION`]) ?? 'us-east-1', - endpoint: envValue([`TIPS_${prefix}_S3_ENDPOINT`]), - accessKeyId: envValue([`TIPS_${prefix}_S3_ACCESS_KEY_ID`]), - secretAccessKey: envValue([`TIPS_${prefix}_S3_SECRET_ACCESS_KEY`]), - }; -} - -const s3Clients = new Map(); - -// Path-style S3 client (optional endpoint + optional static credentials), -// memoized per chain. With no endpoint or credentials this behaves like the -// default AWS SDK config. -export function getS3Client(chain: ExplorerChain): S3Client { - const existing = s3Clients.get(chain); - if (existing) { - return existing; - } - - const s3Config = getS3Config(chain); - const config: S3ClientConfig = { - region: s3Config.region, - forcePathStyle: true, - }; - - if (s3Config.endpoint) { - config.endpoint = s3Config.endpoint; - } - - if (s3Config.accessKeyId && s3Config.secretAccessKey) { - config.credentials = { - accessKeyId: s3Config.accessKeyId, - secretAccessKey: s3Config.secretAccessKey, - }; - } - - const client = new S3Client(config); - s3Clients.set(chain, client); - return client; -} - -export function getBucketName(chain: ExplorerChain): string { - return getS3Config(chain).bucket; -} - export function getRpcUrl(chain: ExplorerChain): string { return envValue([`TIPS_${ENV_PREFIX[chain]}_RPC_URL`]) ?? 'http://localhost:8545'; } -// Audit events JSON-RPC endpoint for a chain. Unlike S3/RPC there is no default: -// audit is opt-in per chain via TIPS__AUDIT_RPC_URL. When unset, the audit -// source is treated as disabled and the routes fall back to the S3 archive. +// Audit events JSON-RPC endpoint (Postgres-backed) for a chain. Unlike the +// execution RPC there is no default: audit is opt-in per chain via +// TIPS__AUDIT_RPC_URL. When unset, the audit source is treated as +// disabled: bundle/rejected views are empty and block/txn views show chain data only. export function getAuditRpcUrl(chain: ExplorerChain): string | undefined { return envValue([`TIPS_${ENV_PREFIX[chain]}_AUDIT_RPC_URL`]); } diff --git a/app/api/internal-explorer/rejected/route.ts b/app/api/internal-explorer/rejected/route.ts index 9f22a735..8a460535 100644 --- a/app/api/internal-explorer/rejected/route.ts +++ b/app/api/internal-explorer/rejected/route.ts @@ -1,4 +1,3 @@ -import type { ExplorerChain } from '../../../internal-explorer/chains'; import { resolveExplorerChainFromRequest } from '../chain'; import { getAuditRejectedTransactionEvents, @@ -6,7 +5,6 @@ import { } from '../audit-events'; import { getAuditRpcUrl } from '../config'; import { explorerDisabledResponse } from '../guard'; -import { getRejectedTransaction, listRejectedTransactions } from '../s3'; import type { RejectedTransaction } from '../transaction-data'; export const runtime = 'nodejs'; @@ -20,41 +18,23 @@ export async function GET(request: Request) { if (disabled) return disabled; const chain = resolveExplorerChainFromRequest(request); - try { - // Audit-first, S3 fallback: use the S3 archive only when audit is not - // configured for this chain or returns no rejected events. - const auditTransactions = await getAuditRejectedTransactions(chain); - const transactions = - auditTransactions.length > 0 ? auditTransactions : await getS3RejectedTransactions(chain); - - const response: RejectedTransactionsResponse = { transactions }; - return Response.json(response); - } catch (error) { - console.error('Error fetching rejected transactions:', error); - return Response.json({ error: 'Internal server error' }, { status: 500 }); - } -} - -async function getAuditRejectedTransactions(chain: ExplorerChain): Promise { + // Rejected transactions come only from the audit events RPC (Postgres-backed). + // With no audit endpoint configured for this chain there is nothing to list. const auditRpcUrl = getAuditRpcUrl(chain); if (!auditRpcUrl) { - return []; + const response: RejectedTransactionsResponse = { transactions: [] }; + return Response.json(response); } try { - return (await getAuditRejectedTransactionEvents(auditRpcUrl, 100)) + const transactions = (await getAuditRejectedTransactionEvents(auditRpcUrl, 100)) .map(rejectedTransactionFromAuditEvent) .filter((tx): tx is RejectedTransaction => tx !== null); + + const response: RejectedTransactionsResponse = { transactions }; + return Response.json(response); } catch (error) { - console.error('Falling back to S3 rejected transactions:', error); - return []; + console.error('Error fetching rejected transactions from audit RPC:', error); + return Response.json({ error: 'Audit RPC unavailable' }, { status: 502 }); } } - -async function getS3RejectedTransactions(chain: ExplorerChain): Promise { - const summaries = await listRejectedTransactions(chain, 100); - const transactions = await Promise.all( - summaries.map((summary) => getRejectedTransaction(chain, summary.blockNumber, summary.txHash)), - ); - return transactions.filter((tx): tx is RejectedTransaction => tx !== null); -} diff --git a/app/api/internal-explorer/s3.ts b/app/api/internal-explorer/s3.ts deleted file mode 100644 index 796d5fb6..00000000 --- a/app/api/internal-explorer/s3.ts +++ /dev/null @@ -1,238 +0,0 @@ -// S3 data access for the Internal Explorer API. Every data function is chain-aware: it takes a -// ExplorerChain and resolves the per-chain S3 client + bucket from config.ts, so one -// deployment can serve all chains. S3 is the fallback source — routes prefer the -// audit events RPC (audit-events.ts) and fall back here when audit is not -// configured or returns nothing. getBlockFromCache / cacheBlockData are this app's -// read-through cache of RPC block data. Domain types live in transaction-data.ts -// and are re-exported here. Server-only: never import from client. -import { - GetObjectCommand, - ListObjectsV2Command, - PutObjectCommand, -} from '@aws-sdk/client-s3'; - -import type { ExplorerChain } from '../../internal-explorer/chains'; -import { getBucketName, getS3Client } from './config'; -import type { - BlockData, - BundleEvent, - BundleHistory, - RejectedTransaction, - TransactionMetadata, -} from './transaction-data'; - -export type { - BlockData, - BlockTransaction, - BundleData, - BundleEvent, - BundleEventData, - BundleHistory, - BundleTransaction, - MeterBundleResponse, - MeterBundleResult, - RejectedTransaction, - RejectionReason, - TransactionMetadata, -} from './transaction-data'; -export { formatRejectionReason } from './transaction-data'; - -async function getObjectContent(chain: ExplorerChain, key: string): Promise { - try { - const command = new GetObjectCommand({ - Bucket: getBucketName(chain), - Key: key, - }); - - const response = await getS3Client(chain).send(command); - const body = await response.Body?.transformToString(); - return body || null; - } catch (_error) { - return null; - } -} - -export async function getTransactionMetadataByHash( - chain: ExplorerChain, - hash: string, -): Promise { - const key = `transactions/by_hash/${hash}`; - const content = await getObjectContent(chain, key); - - if (!content) { - return null; - } - - try { - return JSON.parse(content) as TransactionMetadata; - } catch (error) { - console.error(`Failed to parse transaction metadata for hash ${hash}:`, error); - return null; - } -} - -export async function getBundleHistory( - chain: ExplorerChain, - bundleKey: string, -): Promise { - const prefix = `bundles/${bundleKey}/`; - const listCommand = new ListObjectsV2Command({ - Bucket: getBucketName(chain), - Prefix: prefix, - }); - - const listResponse = await getS3Client(chain).send(listCommand); - const keys = listResponse.Contents?.map((obj) => obj.Key).filter(Boolean) as string[]; - - if (!keys || keys.length === 0) { - return null; - } - - const history: BundleEvent[] = []; - for (const key of keys) { - const content = await getObjectContent(chain, key); - if (content) { - try { - history.push(JSON.parse(content) as BundleEvent); - } catch (error) { - console.error(`Failed to parse event at ${key}:`, error); - } - } - } - - return { history }; -} - -export async function getBlockFromCache( - chain: ExplorerChain, - blockHash: string, -): Promise { - const key = `blocks/${blockHash}`; - const content = await getObjectContent(chain, key); - - if (!content) { - return null; - } - - try { - const parsed = JSON.parse(content); - return { - ...parsed, - number: BigInt(parsed.number), - timestamp: BigInt(parsed.timestamp), - gasUsed: BigInt(parsed.gasUsed), - gasLimit: BigInt(parsed.gasLimit), - baseFeePerGas: - parsed.baseFeePerGas === null || parsed.baseFeePerGas === undefined - ? null - : BigInt(parsed.baseFeePerGas), - transactions: parsed.transactions.map( - (tx: { gasLimit?: string; [key: string]: unknown }) => ({ - ...tx, - blockHash: String(tx.blockHash ?? parsed.hash ?? blockHash), - blockNumber: BigInt((tx.blockNumber as string) ?? parsed.number), - blockTimestamp: BigInt((tx.blockTimestamp as string) ?? parsed.timestamp), - input: String(tx.input ?? '0x'), - value: BigInt(String(tx.value ?? '0')), - gasLimit: BigInt(tx.gasLimit ?? '0'), - gasUsed: - tx.gasUsed === null || tx.gasUsed === undefined ? null : BigInt(tx.gasUsed as string), - effectiveGasPrice: - tx.effectiveGasPrice === null || tx.effectiveGasPrice === undefined - ? null - : BigInt(tx.effectiveGasPrice as string), - transactionFee: - tx.transactionFee === null || tx.transactionFee === undefined - ? null - : BigInt(tx.transactionFee as string), - meterBundleResponse: tx.meterBundleResponse ?? null, - inclusionLatencyMs: - typeof tx.inclusionLatencyMs === 'number' && Number.isFinite(tx.inclusionLatencyMs) - ? tx.inclusionLatencyMs - : null, - }), - ), - } as BlockData; - } catch (error) { - console.error(`Failed to parse block data for hash ${blockHash}:`, error); - return null; - } -} - -export interface RejectedTransactionSummary { - blockNumber: number; - txHash: string; -} - -export async function listRejectedTransactions( - chain: ExplorerChain, - limit = 100, -): Promise { - try { - const command = new ListObjectsV2Command({ - Bucket: getBucketName(chain), - Prefix: 'rejected/', - MaxKeys: limit, - }); - - const response = await getS3Client(chain).send(command); - const contents = response.Contents || []; - - const summaries: RejectedTransactionSummary[] = []; - for (const obj of contents) { - if (!obj.Key) continue; - // S3 key format matches Rust S3Key::Rejected: rejected/{block_number}/{tx_hash} - const parts = obj.Key.split('/'); - if (parts.length !== 3) continue; - const blockNumber = parseInt(parts[1], 10); - const txHash = parts[2]; - if (Number.isNaN(blockNumber) || !txHash) continue; - summaries.push({ blockNumber, txHash }); - } - - summaries.sort((a, b) => b.blockNumber - a.blockNumber); - return summaries; - } catch (error) { - console.error('Failed to list rejected transactions:', error); - return []; - } -} - -export async function getRejectedTransaction( - chain: ExplorerChain, - blockNumber: number, - txHash: string, -): Promise { - const key = `rejected/${blockNumber}/${txHash}`; - const content = await getObjectContent(chain, key); - - if (!content) { - return null; - } - - try { - return JSON.parse(content) as RejectedTransaction; - } catch (error) { - console.error(`Failed to parse rejected transaction ${blockNumber}/${txHash}:`, error); - return null; - } -} - -export async function cacheBlockData(chain: ExplorerChain, blockData: BlockData): Promise { - const key = `blocks/${blockData.hash}`; - - try { - const command = new PutObjectCommand({ - Bucket: getBucketName(chain), - Key: key, - Body: JSON.stringify(blockData, (_, value) => - typeof value === 'bigint' ? value.toString() : value, - ), - ContentType: 'application/json', - }); - - await getS3Client(chain).send(command); - } catch (error) { - console.error(`Failed to cache block data for ${blockData.hash}:`, error); - } -} diff --git a/app/api/internal-explorer/transaction-data.ts b/app/api/internal-explorer/transaction-data.ts index d00986c9..27d97c43 100644 --- a/app/api/internal-explorer/transaction-data.ts +++ b/app/api/internal-explorer/transaction-data.ts @@ -1,6 +1,6 @@ -// Source-agnostic Internal Explorer domain types, shared by every data source — the S3 archive -// (s3.ts), the audit events RPC (audit-events.ts), and direct execution-RPC reads. -// They live here rather than in any one source module; s3.ts re-exports them. +// Source-agnostic Internal Explorer domain types, shared by every data source — the audit +// events RPC (audit-events.ts) and direct execution-RPC reads. They live here +// rather than in any one source module. export interface TransactionMetadata { bundle_ids: string[]; diff --git a/app/api/internal-explorer/transaction-lookup.test.ts b/app/api/internal-explorer/transaction-lookup.test.ts index 920c2562..cc427ef7 100644 --- a/app/api/internal-explorer/transaction-lookup.test.ts +++ b/app/api/internal-explorer/transaction-lookup.test.ts @@ -3,7 +3,6 @@ import assert from 'node:assert/strict'; import { describe, test } from 'vitest'; import type { AuditTransactionEventRecord } from './audit-events'; -import type { BundleHistory } from './transaction-data'; import { type ChainLookupResult, type ChainTransactionData, @@ -82,8 +81,6 @@ function dependencies( getJoinedAuditEventsByBundle: async () => [], getAuditEventsByBlockHash: async () => [], getAuditEventsByBlockNumber: async () => [], - getTransactionMetadataByHash: async () => null, - getBundleHistory: async () => null, getChainData: async (): Promise => ({ status: 'empty', data: null, @@ -178,20 +175,8 @@ describe('transaction lookup', () => { assert.equal(result.response.coverage.block_events, 'disabled'); }); - test('merges audit events with every available archive history', async () => { - const proxyEvent = event(); - const archiveHistory: BundleHistory = { - history: [ - { - event: 'Received', - data: { - key: 'archive-event', - timestamp: Date.parse('2026-06-02T00:00:02Z'), - originalEvent: { source: 's3' }, - }, - }, - ], - }; + test('uses bundle keys from audit events for related bundle queries', async () => { + const bundleKeys: string[] = []; const result = await lookupTransaction( CHAIN, txHash, @@ -199,28 +184,22 @@ describe('transaction lookup', () => { getTransactionEventsByHash: async () => [ event({ event_id: 'bundle-event', - data: { bundle_hash: '0xbundle' }, + data: { bundle_id: 'bundle-uuid', bundle_hash: '0xbundle' }, }), - proxyEvent, ], - getTransactionMetadataByHash: async () => ({ - bundle_ids: ['0xbundle'], - sender: '', - nonce: '', - }), - getBundleHistory: async () => archiveHistory, + getJoinedAuditEventsByBundle: async (bundleKey) => { + bundleKeys.push(bundleKey); + return []; + }, }), ); assert.equal(result.found, true); - assert.deepEqual(result.response.archive.histories, [ - { key: '0xbundle', history: archiveHistory.history }, - ]); - assert.equal(result.response.history.length, 3); - assert.equal(result.response.coverage.archive, 'available'); + assert.deepEqual(result.response.bundle_ids, ['bundle-uuid', '0xbundle']); + assert.deepEqual(bundleKeys.sort(), ['0xbundle', 'bundle-uuid']); }); - test('keeps chain data when audit and archive queries fail', async () => { + test('keeps chain data when audit queries fail', async () => { const result = await lookupTransaction( CHAIN, txHash, @@ -228,9 +207,6 @@ describe('transaction lookup', () => { getTransactionEventsByHash: async () => { throw new Error('audit unavailable'); }, - getTransactionMetadataByHash: async () => { - throw new Error('archive unavailable'); - }, getChainData: async () => ({ status: 'available', data: chainData(), @@ -241,7 +217,6 @@ describe('transaction lookup', () => { assert.equal(result.found, true); assert.ok(result.response.chain); assert.equal(result.response.coverage.audit, 'unavailable'); - assert.equal(result.response.coverage.archive, 'unavailable'); }); test('reports a true all-sources-missing lookup', async () => { @@ -251,7 +226,6 @@ describe('transaction lookup', () => { assert.equal(result.unavailable, false); assert.equal(result.response.coverage.audit, 'empty'); assert.equal(result.response.coverage.chain, 'empty'); - assert.equal(result.response.coverage.archive, 'empty'); }); test('distinguishes unavailable sources from a missing transaction', async () => { @@ -262,9 +236,6 @@ describe('transaction lookup', () => { getTransactionEventsByHash: async () => { throw new Error('audit unavailable'); }, - getTransactionMetadataByHash: async () => { - throw new Error('archive unavailable'); - }, getChainData: async () => { throw new Error('rpc unavailable'); }, @@ -275,6 +246,5 @@ describe('transaction lookup', () => { assert.equal(result.unavailable, true); assert.equal(result.response.coverage.audit, 'unavailable'); assert.equal(result.response.coverage.chain, 'unavailable'); - assert.equal(result.response.coverage.archive, 'unavailable'); }); }); diff --git a/app/api/internal-explorer/transaction-lookup.ts b/app/api/internal-explorer/transaction-lookup.ts index 9d32c6dc..555543e4 100644 --- a/app/api/internal-explorer/transaction-lookup.ts +++ b/app/api/internal-explorer/transaction-lookup.ts @@ -1,8 +1,8 @@ // Canonical multi-source single-transaction lookup behind /api/internal-explorer/txn/[hash]. -// defaultDependencies(chain) binds the audit RPC URL, S3 chain, and execution-RPC -// client; chain data is read with viem. Queries three independent sources in -// parallel — audit events, on-chain tx+receipt, and the S3 archive — and merges -// them, reporting per-source coverage. Dependency-injected for testability. +// defaultDependencies(chain) binds the audit RPC URL and execution-RPC client; +// chain data is read with viem. Queries two independent sources in parallel — +// audit events (Postgres-backed RPC) and on-chain tx+receipt — and merges them, +// reporting per-source coverage. Dependency-injected for testability. // Server-only. import { type Hash } from 'viem'; @@ -19,8 +19,7 @@ import { transactionHistoryFromAuditEvents, } from './audit-events'; import { getAuditRpcUrl, getRpcUrl, isAuditConfigured } from './config'; -import { getBundleHistory, getTransactionMetadataByHash } from './s3'; -import type { BundleEvent, BundleHistory, TransactionMetadata } from './transaction-data'; +import type { BundleEvent } from './transaction-data'; import { publicClientFor, type ExplorerPublicClient } from './viem'; export type CoverageState = 'available' | 'empty' | 'disabled' | 'unavailable' | 'not_applicable'; @@ -65,11 +64,6 @@ export interface ChainTransactionData { receipt: ChainReceipt | null; } -export interface TransactionArchiveHistory { - key: string; - history: BundleEvent[]; -} - export interface TransactionAuditSource { configured: boolean; events: AuditTransactionEventRecord[]; @@ -78,15 +72,9 @@ export interface TransactionAuditSource { block_events: AuditTransactionEventRecord[]; } -export interface TransactionArchiveSource { - metadata: TransactionMetadata | null; - histories: TransactionArchiveHistory[]; -} - export interface TransactionCoverage { audit: CoverageState; chain: CoverageState; - archive: CoverageState; block_events: CoverageState; } @@ -96,7 +84,6 @@ export interface TransactionLookupResponse { history: BundleEvent[]; audit: TransactionAuditSource; chain: ChainTransactionData | null; - archive: TransactionArchiveSource; coverage: TransactionCoverage; } @@ -123,8 +110,6 @@ export interface TransactionLookupDependencies { blockNumber: number, limit: number, ) => Promise; - getTransactionMetadataByHash: (hash: string) => Promise; - getBundleHistory: (bundleKey: string) => Promise; getChainData: (hash: string) => Promise; } @@ -146,35 +131,24 @@ export async function lookupTransaction( }> { const hash = normalizeTransactionHash(inputHash); - const [auditResult, chainResult, archiveResult] = await Promise.all([ + const [auditResult, chainResult] = await Promise.all([ loadTransactionAuditEvents(hash, dependencies), loadChainData(hash, dependencies), - loadArchiveSource(hash, dependencies), ]); - const bundleKeys = uniqueStrings([ - ...transactionBundleKeysFromAuditEvents(auditResult.transactionEvents).all, - ...(archiveResult.metadata?.bundle_ids ?? []), - ]); + const bundleKeys = transactionBundleKeysFromAuditEvents(auditResult.transactionEvents).all; - const relatedEvents = await loadRelatedAuditEvents(bundleKeys, dependencies); - const blockResult = await loadBlockAuditEvents(hash, chainResult.data, dependencies); - const archiveHistories = await loadArchiveHistories( - bundleKeys, - archiveResult.histories, - dependencies, - ); + const [relatedEvents, blockResult] = await Promise.all([ + loadRelatedAuditEvents(bundleKeys, dependencies), + loadBlockAuditEvents(hash, chainResult.data, dependencies), + ]); const auditEvents = mergeAuditEvents([ auditResult.transactionEvents, relatedEvents, blockResult.events, ]); - const auditHistory = transactionHistoryFromAuditEvents(hash, auditEvents); - const history = mergeBundleEvents([ - auditHistory, - ...archiveHistories.map((entry) => entry.history), - ]); + const history = transactionHistoryFromAuditEvents(hash, auditEvents); const auditState = auditResult.status === 'disabled' @@ -182,17 +156,8 @@ export async function lookupTransaction( : auditEvents.length > 0 ? 'available' : auditResult.status; - const archiveState = - archiveResult.metadata !== null || archiveHistories.length > 0 - ? 'available' - : archiveResult.status; - const found = - auditEvents.length > 0 || - chainResult.data !== null || - archiveResult.metadata !== null || - archiveHistories.length > 0; - const unavailable = - !found && [auditState, chainResult.status, archiveState].includes('unavailable'); + const found = auditEvents.length > 0 || chainResult.data !== null; + const unavailable = !found && [auditState, chainResult.status].includes('unavailable'); return { found, @@ -209,14 +174,9 @@ export async function lookupTransaction( block_events: blockResult.events, }, chain: chainResult.data, - archive: { - metadata: archiveResult.metadata, - histories: archiveHistories, - }, coverage: { audit: auditState, chain: chainResult.status, - archive: archiveState, block_events: blockResult.status, }, }, @@ -247,8 +207,6 @@ function defaultDependencies(chain: ExplorerChain): TransactionLookupDependencie getAuditEventsByBlockHash(auditRpcUrl, blockHash, limit), getAuditEventsByBlockNumber: (blockNumber, limit) => getAuditEventsByBlockNumber(auditRpcUrl, blockNumber, limit), - getTransactionMetadataByHash: (hash) => getTransactionMetadataByHash(chain, hash), - getBundleHistory: (bundleKey) => getBundleHistory(chain, bundleKey), getChainData: (hash) => getChainDataFromRpc(rpcUrl, hash), }; } @@ -354,49 +312,6 @@ async function loadBlockAuditEvents( }; } -async function loadArchiveSource( - hash: string, - dependencies: TransactionLookupDependencies, -): Promise<{ - status: Extract; - metadata: TransactionMetadata | null; - histories: TransactionArchiveHistory[]; -}> { - try { - const metadata = await dependencies.getTransactionMetadataByHash(hash); - return { - status: metadata === null ? 'empty' : 'available', - metadata, - histories: [], - }; - } catch { - return { status: 'unavailable', metadata: null, histories: [] }; - } -} - -async function loadArchiveHistories( - bundleKeys: string[], - existing: TransactionArchiveHistory[], - dependencies: TransactionLookupDependencies, -): Promise { - const existingKeys = new Set(existing.map((entry) => entry.key)); - const additionalKeys = bundleKeys.filter((key) => !existingKeys.has(key)); - const results = await Promise.all( - additionalKeys.map(async (key) => { - try { - const bundle = await dependencies.getBundleHistory(key); - return bundle === null ? null : { key, history: bundle.history }; - } catch { - return null; - } - }), - ); - return [ - ...existing, - ...results.filter((entry): entry is TransactionArchiveHistory => entry !== null), - ]; -} - async function getChainDataFromRpc(rpcUrl: string, hash: string): Promise { const client = publicClientFor(rpcUrl); try { @@ -472,22 +387,6 @@ function numericHex(value: bigint | number | null | undefined): string | null { return value === null || value === undefined ? null : `0x${value.toString(16)}`; } -function mergeBundleEvents(eventGroups: BundleEvent[][]): BundleEvent[] { - const events = new Map(); - for (const group of eventGroups) { - for (const event of group) { - const key = `${event.event}:${event.data.key ?? `${event.data.timestamp}`}`; - events.set(key, event); - } - } - - return Array.from(events.values()).sort((lhs, rhs) => lhs.data.timestamp - rhs.data.timestamp); -} - -function uniqueStrings(values: string[]): string[] { - return Array.from(new Set(values.filter((value) => value.length > 0))); -} - function normalizeTransactionHash(hash: string): string { const normalized = hash.trim().toLowerCase(); if (!/^0x[0-9a-f]{64}$/.test(normalized)) { diff --git a/app/api/internal-explorer/txn/[hash]/route.ts b/app/api/internal-explorer/txn/[hash]/route.ts index 57cfc490..d0a32b4a 100644 --- a/app/api/internal-explorer/txn/[hash]/route.ts +++ b/app/api/internal-explorer/txn/[hash]/route.ts @@ -9,7 +9,7 @@ import { export const runtime = 'nodejs'; // The canonical transaction view is multi-source (audit events + on-chain -// tx/receipt + S3 archive), merged and coverage-annotated by lookupTransaction. +// tx/receipt), merged and coverage-annotated by lookupTransaction. // See app/api/internal-explorer/transaction-lookup.ts. export type TransactionHistoryResponse = TransactionLookupResponse; diff --git a/app/internal-explorer/chains.ts b/app/internal-explorer/chains.ts index d2cc8e42..4b2e351c 100644 --- a/app/internal-explorer/chains.ts +++ b/app/internal-explorer/chains.ts @@ -1,7 +1,7 @@ // Chain model for Internal Explorer. Client-safe: the data for each chain // used to be a separate deployment; here chain is a runtime parameter carried in // the URL (?chain=) and passed to /api/internal-explorer/* which resolves -// per-chain S3 + RPC. +// per-chain RPC + audit endpoints. import { defaultExplorerChainForOrigin, type ExplorerHostMap } from './hosts'; diff --git a/app/internal-explorer/components/EventHistoryRow.tsx b/app/internal-explorer/components/EventHistoryRow.tsx index 3f97e183..56e979df 100644 --- a/app/internal-explorer/components/EventHistoryRow.tsx +++ b/app/internal-explorer/components/EventHistoryRow.tsx @@ -1,6 +1,6 @@ 'use client'; -// Single timeline event for the audit/archive history on the block, bundle, and +// Single timeline event for the audit history on the block, bundle, and // transaction pages. Chain-aware: block links carry ?chain=. import Link from 'next/link'; import { useState } from 'react'; diff --git a/app/internal-explorer/library/types.ts b/app/internal-explorer/library/types.ts index 8213c88a..54723fd8 100644 --- a/app/internal-explorer/library/types.ts +++ b/app/internal-explorer/library/types.ts @@ -4,8 +4,7 @@ // re-exported type-only (erased at build, so no server code reaches the client // bundle). Two things stay local: the block-detail wire shape (the block route // serializes its response inline and exports no type), and formatRejectionReason -// (a pure helper — importing it from the s3 module would pull server deps into -// the client). +// (a pure helper kept local so the client never imports a server module at runtime). export type { BundleData, @@ -17,7 +16,7 @@ export type { MeterBundleResult, RejectedTransaction, RejectionReason, -} from '../../api/internal-explorer/s3'; +} from '../../api/internal-explorer/transaction-data'; export type { BlocksPage, BlockSummary, BlocksResponse } from '../../api/internal-explorer/blocks/route'; export type { LatestActiveBlockResponse } from '../../api/internal-explorer/blocks/latest-active/route'; export type { TransactionListItem, TransactionsResponse } from '../../api/internal-explorer/txs/route'; @@ -29,7 +28,6 @@ export type { ChainTransaction, ChainTransactionData, CoverageState, - TransactionArchiveSource, TransactionAuditSource, TransactionCoverage, TransactionLookupResponse, @@ -37,8 +35,8 @@ export type { export type { AuditTransactionEventRecord } from '../../api/internal-explorer/audit-events'; export type { ShadowBlockDetail, ShadowBlockSummary, ShadowTxSummary } from '../../api/internal-explorer/shadow'; -import type { BundleEvent } from '../../api/internal-explorer/s3'; -import type { MeterBundleResponse, MeterBundleResult, RejectionReason } from '../../api/internal-explorer/s3'; +import type { BundleEvent } from '../../api/internal-explorer/transaction-data'; +import type { MeterBundleResponse, MeterBundleResult, RejectionReason } from '../../api/internal-explorer/transaction-data'; // Block detail (/api/internal-explorer/block/[hash]). The route serializes inline (bigints as // strings, metering split into transaction/bundle), so the wire shape is modeled diff --git a/app/internal-explorer/page.tsx b/app/internal-explorer/page.tsx index dc1bfef8..ef0d804a 100644 --- a/app/internal-explorer/page.tsx +++ b/app/internal-explorer/page.tsx @@ -50,7 +50,7 @@ function SearchBar({ chain, onError }: { chain: ExplorerChain; onError: (error: if (!hash) return; // The transaction detail page is now the canonical, multi-source view (it - // resolves audit + on-chain + archive data itself and works even when the + // resolves audit + on-chain data itself and works even when the // transaction is not part of a bundle), so route straight to it. setLoading(true); onError(null); diff --git a/app/internal-explorer/txn/[hash]/page.tsx b/app/internal-explorer/txn/[hash]/page.tsx index d76709db..5689c244 100644 --- a/app/internal-explorer/txn/[hash]/page.tsx +++ b/app/internal-explorer/txn/[hash]/page.tsx @@ -189,7 +189,7 @@ function SimulationCard({ meter }: { meter: MeterBundleResponse | null }) { Unavailable @@ -215,7 +215,7 @@ function SimulationCard({ meter }: { meter: MeterBundleResponse | null }) { Available @@ -263,7 +263,6 @@ function ProvenanceFooter({
-
@@ -377,7 +376,7 @@ function TransactionContent({ params }: PageProps) {
- Audit and archive timeline + Audit timeline {data.history.length > 0 ? ( data.history.map((event, index) => ( @@ -392,7 +391,7 @@ function TransactionContent({ params }: PageProps) { )) ) : (
- No journal or archive events are available for this transaction. + No audit events are available for this transaction.
)}
diff --git a/package-lock.json b/package-lock.json index cb5dbd03..e7ebd766 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@aws-sdk/client-s3": "3.1116.0", "@base-ui/react": "^1.7.0", "@vercel/analytics": "^2.0.1", "classnames": "^2.5.1", @@ -61,314 +60,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/checksums": { - "version": "3.1000.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.29.tgz", - "integrity": "sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.1116.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1116.0.tgz", - "integrity": "sha512-UKRl9qSVW0rZpvSOauQNpYAy8+ONBAVYnpfKVtCyOF+FZVT1tl6MunYuHvuarCrroD2/YJs+tHTALYNgAlec3Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/checksums": "^3.1000.29", - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/credential-provider-node": "^3.972.81", - "@aws-sdk/middleware-sdk-s3": "^3.972.75", - "@aws-sdk/signature-v4-multi-region": "^3.996.46", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/fetch-http-handler": "^5.7.2", - "@smithy/node-http-handler": "^4.11.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.977.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", - "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.974.5", - "@aws-sdk/xml-builder": "^3.972.40", - "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.33.3", - "@smithy/signature-v4": "^5.6.12", - "@smithy/types": "^4.17.2", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", - "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", - "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/fetch-http-handler": "^5.7.2", - "@smithy/node-http-handler": "^4.11.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", - "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-login": "^3.972.77", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/credential-provider-imds": "^4.4.16", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.77", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", - "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.81", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.81.tgz", - "integrity": "sha512-Rml+WitoFvXmv6JZ18U/xGdGDGGvB/mOin0ya0lTnTrdC0Z1lrVxTYh7iNklZBcvcRMrs4DoEf6xy1KWyrLQQw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-ini": "^3.973.15", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/credential-provider-imds": "^4.4.16", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", - "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", - "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/token-providers": "3.1116.0", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", - "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.75", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.75.tgz", - "integrity": "sha512-wMIsNumRVKaNMKhvU/s9VrdEwE8S6gSzXp4RygFG5BEMnGkkXf8cjh8zf7cKJBpUDpqTWqwbz5isEgp9rH6Lng==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/signature-v4-multi-region": "^3.996.46", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", - "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/signature-v4-multi-region": "^3.996.46", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/fetch-http-handler": "^5.7.2", - "@smithy/node-http-handler": "^4.11.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", - "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.974.5", - "@smithy/signature-v4": "^5.6.12", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1116.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", - "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.974.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", - "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.40", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", - "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", - "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -2102,87 +1793,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@smithy/core": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", - "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", - "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz", - "integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.11.3.tgz", - "integrity": "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", - "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.17.2.tgz", - "integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3486,12 +3096,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", diff --git a/package.json b/package.json index 0dc3c856..e98d5c30 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "docs:check": "node scripts/llms.mjs --check && node scripts/agents.mjs --check" }, "dependencies": { - "@aws-sdk/client-s3": "3.1116.0", "@base-ui/react": "^1.7.0", "@vercel/analytics": "^2.0.1", "classnames": "^2.5.1",