diff --git a/apps/scan/src/services/db/resources/accepts.ts b/apps/scan/src/services/db/resources/accepts.ts index e595ad1be..7c7dbfa2a 100644 --- a/apps/scan/src/services/db/resources/accepts.ts +++ b/apps/scan/src/services/db/resources/accepts.ts @@ -1,6 +1,7 @@ import { scanDb } from '@x402scan/scan-db'; import { mixedAddressSchema } from '@/lib/schemas'; +import { createCachedQuery, createStandardCacheKey } from '@/lib/cache'; import type { Chain } from '@/types/chain'; import type { AcceptsNetwork, ResourceOrigin } from '@x402scan/scan-db'; @@ -10,7 +11,7 @@ interface GetAcceptsAddressesInput { tags?: string[]; } -export const getAcceptsAddresses = async (input: GetAcceptsAddressesInput) => { +const getAcceptsAddressesUncached = async (input: GetAcceptsAddressesInput) => { const { chain, tags } = input; const accepts = await scanDb.accepts.findMany({ include: { @@ -58,3 +59,16 @@ export const getAcceptsAddresses = async (input: GetAcceptsAddressesInput) => { {} as Record> ); }; + +/** + * Get accepts addresses grouped by origin (cached) + * This is used to determine which addresses are "bazaar" sellers + */ +export const getAcceptsAddresses = createCachedQuery({ + queryFn: getAcceptsAddressesUncached, + cacheKeyPrefix: 'accepts:addresses', + createCacheKey: input => + createStandardCacheKey(input as Record), + dateFields: [], + tags: ['accepts', 'bazaar'], +}); diff --git a/apps/scan/src/services/transfers/sellers/stats/bucketed.ts b/apps/scan/src/services/transfers/sellers/stats/bucketed.ts index 041aa5573..bfc0e0dce 100644 --- a/apps/scan/src/services/transfers/sellers/stats/bucketed.ts +++ b/apps/scan/src/services/transfers/sellers/stats/bucketed.ts @@ -36,6 +36,8 @@ const getBucketedSellerStatisticsUncached = async ( Math.floor(timeRangeMs / numBuckets / 1000) ); + // Use the recipient_first_transaction materialized view for fast lookups + // This avoids scanning all hypertable chunks to compute MIN(block_timestamp) const sql = Prisma.sql` WITH all_buckets AS ( SELECT generate_series( @@ -46,13 +48,6 @@ const getBucketedSellerStatisticsUncached = async ( (${bucketSizeSeconds} || ' seconds')::interval ) AS bucket_start ), - seller_first_transactions AS ( - SELECT - recipient, - MIN(block_timestamp) AS first_transaction_date - FROM "TransferEvent" - GROUP BY recipient - ), bucket_stats AS ( SELECT to_timestamp( @@ -61,14 +56,14 @@ const getBucketedSellerStatisticsUncached = async ( COUNT(DISTINCT t.recipient)::int AS total_sellers, COUNT(DISTINCT CASE WHEN to_timestamp( - floor(extract(epoch from sft.first_transaction_date) / ${bucketSizeSeconds}) * ${bucketSizeSeconds} + floor(extract(epoch from rft.first_transaction_date) / ${bucketSizeSeconds}) * ${bucketSizeSeconds} ) = to_timestamp( floor(extract(epoch from t.block_timestamp) / ${bucketSizeSeconds}) * ${bucketSizeSeconds} ) THEN t.recipient END)::int AS new_sellers FROM "TransferEvent" t - LEFT JOIN seller_first_transactions sft ON t.recipient = sft.recipient + LEFT JOIN recipient_first_transaction rft ON t.recipient = rft.recipient ${transfersWhereClause(input)} GROUP BY bucket_start ) diff --git a/apps/scan/src/services/transfers/sellers/stats/overall.ts b/apps/scan/src/services/transfers/sellers/stats/overall.ts index c205c851d..fe28fec36 100644 --- a/apps/scan/src/services/transfers/sellers/stats/overall.ts +++ b/apps/scan/src/services/transfers/sellers/stats/overall.ts @@ -13,15 +13,11 @@ const getOverallSellerStatisticsUncached = async ( input: z.infer ) => { const { startDate, endDate } = getTimeRangeFromTimeframe(input.timeframe); + + // Use the recipient_first_transaction materialized view for fast lookups + // This avoids scanning all hypertable chunks to compute MIN(block_timestamp) const sql = Prisma.sql` - WITH seller_first_transactions AS ( - SELECT - recipient, - MIN(block_timestamp) AS first_transaction_date - FROM "TransferEvent" - GROUP BY recipient - ), - filtered_transfers AS ( + WITH filtered_transfers AS ( SELECT DISTINCT t.recipient FROM "TransferEvent" t ${transfersWhereClause(input)} @@ -29,12 +25,12 @@ const getOverallSellerStatisticsUncached = async ( SELECT COUNT(DISTINCT ft.recipient)::int AS total_sellers, COUNT(DISTINCT CASE - WHEN sft.first_transaction_date >= ${startDate ?? Prisma.sql`'1970-01-01'::timestamp`} - AND sft.first_transaction_date <= ${endDate ?? Prisma.sql`NOW()`} + WHEN rft.first_transaction_date >= ${startDate ?? Prisma.sql`'1970-01-01'::timestamp`} + AND rft.first_transaction_date <= ${endDate ?? Prisma.sql`NOW()`} THEN ft.recipient END)::int AS new_sellers FROM filtered_transfers ft - LEFT JOIN seller_first_transactions sft ON ft.recipient = sft.recipient + LEFT JOIN recipient_first_transaction rft ON ft.recipient = rft.recipient `; const result = await queryRaw(