From e6f5b8cbf63b05a87666c8c39c87c1c2f1d4b369 Mon Sep 17 00:00:00 2001 From: sirpy Date: Fri, 24 Jul 2026 16:57:27 +0300 Subject: [PATCH 01/25] add: daily analytics --- backend/README.md | 18 +- backend/src/analytics.ts | 808 ++++++++++++++++++++++++++++++++++++ backend/src/env.ts | 9 + backend/src/logging.ts | 1 - backend/src/worker.ts | 51 +++ backend/test/worker.test.ts | 518 ++++++++++++++++++++++- backend/wrangler.toml | 5 +- 7 files changed, 1402 insertions(+), 8 deletions(-) create mode 100644 backend/src/analytics.ts diff --git a/backend/README.md b/backend/README.md index 101db5e..0e1f201 100644 --- a/backend/README.md +++ b/backend/README.md @@ -8,16 +8,18 @@ Cloudflare Worker for GoodDollar Celo-vault credit accounting and Celo → Base - KV namespace binding: `ANTSEED_KV` - Celo `CeloGdAntSeedVault` tx-log ingestion for G$ deposits and Superfluid stream updates - Optional Base `AntseedBuyerOperator` bridge client that calls `depositForWithId(buyer, principal, bonus, id)` -- Cron trigger every minute for stream bonus settlement checks +- Cron trigger every 6 hours for stream bonus settlement checks and analytics aggregation ## Endpoints - `GET /health` - `GET /config/status` - `GET /config/values` +- `GET /v1/analytics` - `GET /v1/accounts/:account/profile` - `GET /v1/accounts/:account/credit-history` - `GET /v1/accounts/:account/outstanding` +- `POST /v1/analytics/refresh` - `POST /v1/accounts/:account/stream-credits` - `POST /v1/accounts/:account/operator-consent` - `POST /v1/accounts/:account/withdraw` @@ -38,6 +40,17 @@ Cloudflare Worker for GoodDollar Celo-vault credit accounting and Celo → Base - `{ "txHash": "0x..." }` - `{ "account": "0x...", "fromBlock": "0x...", "toBlock": "latest" }` +`GET /analytics` returns daily analytics (last 30 days by default). + +- query: `days` (default `30`, max `365`) +- response: `{ days, daily, global, lastRun }` +- `daily` — one record per UTC day, newest last; the current day is always recomputed from midnight to now on each refresh and its snapshot is overwritten +- `global` — all-time totals composed as persisted closed-day totals plus the current day's live snapshot +- `lastRun.finalizedThroughDate` — the most recent closed day whose values have been merged into persisted globals +- Base usage (`aiCreditsUsedWei`, `uniqueCreditUsers`) is filtered to known buyers only; known buyers are learned from Celo vault `buyer` fields. + +`POST /v1/analytics/refresh` recomputes the current UTC day from midnight to now, overwrites the day snapshot, finalizes any closed days into persisted globals, and returns a summary. + ## Setup ```bash @@ -54,6 +67,9 @@ Optional secrets/config: - `REGULAR_BONUS_BPS` - bonus basis points for deposits/non-stream sources, defaults to `1000` (10%). - `STREAMING_BONUS_BPS` - bonus basis points for stream sources, defaults to `2000` (20%). - `MIN_GD_STREAMED_FOR_BONUS` - minimum stream amount in G$ to issue stream credits, defaults to `4000`. +- `CELO_BLOCKSCOUT_API_URL` - Blockscout API URL for Celo logs (defaults to `https://celo.blockscout.com/api`). +- `BASE_BLOCKSCOUT_API_URL` - Blockscout API URL for Base logs (defaults to `https://base.blockscout.com/api`). +- `ANTSEED_CHANNELS_ADDRESS` - Antseed Channels contract address on Base used for AI credit usage metrics. ## Config And Constants diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts new file mode 100644 index 0000000..38e599d --- /dev/null +++ b/backend/src/analytics.ts @@ -0,0 +1,808 @@ +import { Interface } from "ethers"; +import { Env } from "./env.js"; +import { errorMessage, logError, logInfo, logWarn, redactAddress } from "./logging.js"; + +type KV = Pick; + +type ExplorerLog = { + address: string; + topics: string[]; + data: string; + transactionHash: string; + logIndex: string; + blockNumber: string; + timeStamp: string; +}; + +type StreamSnapshot = { + sender: string; + flowRateWeiPerSecond: bigint; + totalStreamedWei: bigint; +}; + +type AnalyticsState = { + finalizedThroughDate?: string; + updatedAt: string; +}; + +export type AnalyticsDailyRecord = { + date: string; + gdOneTimeDepositsWei: string; + gdStreamedWei: string; + gdTotalFlowRateWeiPerSecond: string; + aiCreditsUsedWei: string; + uniqueGdBuyers: number; + uniqueCreditUsers: number; + updatedAt: string; +}; + +export type AnalyticsGlobalTotals = { + gdOneTimeDepositsWei: string; + gdStreamedWei: string; + aiCreditsUsedWei: string; + gdTotalFlowRateWeiPerSecond: string; + updatedAt: string; +}; + +export type AnalyticsLastRun = { + currentDate: string; + finalizedThroughDate?: string; + updatedAt: string; +}; + +export type AnalyticsResponse = { + days: number; + daily: AnalyticsDailyRecord[]; + global: AnalyticsGlobalTotals; + lastRun: AnalyticsLastRun; +}; + +export type AnalyticsRunSummary = { + ok: true; + currentDate: string; + finalizedDates: string[]; + celo: { fromTimestamp: number; toTimestamp: number; scanned: number; matched: number }; + base: { fromTimestamp: number; toTimestamp: number; scanned: number; matched: number }; + streams: { senders: number; totalFlowRateWeiPerSecond: string }; +}; + +type DailyAggregate = { + gdOneTimeDepositsWei: bigint; + gdStreamedWei: bigint; + gdTotalFlowRateWeiPerSecond: bigint; + aiCreditsUsedWei: bigint; + gdBuyers: Set; + creditUsers: Set; +}; + +type AnalyticsConfig = { + celoBlockscoutUrl: string; + baseBlockscoutUrl: string; + celoVaultAddress?: string; + baseChannelsAddress: string; + celoSuperTokenAddress?: string; + celoStreamReceiverAddress?: string; + superfluidSubgraphUrl: string; +}; + +const CELO_VAULT_EVENTS = new Interface(["event GdDeposited(address indexed account,address indexed buyer,uint256 gdAmount,bytes data)"]); + +const BASE_CHANNEL_EVENTS = new Interface([ + "event Reserved(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 maxAmount)", + "event ChannelSettled(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 cumulativeAmount,uint128 delta,uint128 totalSettled,uint256 platformFee,bytes metadata)", + "event ChannelClosed(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 settledAmount,uint128 refund)", + "event ChannelTopUp(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 additionalAmount,uint128 newDeposit)", + "event ChannelWithdrawn(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 refund)", + "event CloseRequested(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint256 gracePeriodEnd)" +]); + +const DAILY_PREFIX = "analytics:daily:"; +const GLOBAL_KEY = "analytics:global"; +const STATE_KEY = "analytics:state"; +const BUYER_REGISTRY_KEY = "analytics:buyers:registry"; +const GD_BUYERS_PREFIX = "analytics:buyers:gd:"; +const CREDIT_USERS_PREFIX = "analytics:buyers:credits:"; +const EXPLORER_LOG_BATCH_LIMIT = 1000; + +const ZERO_DAILY = { + gdOneTimeDepositsWei: "0", + gdStreamedWei: "0", + gdTotalFlowRateWeiPerSecond: "0", + aiCreditsUsedWei: "0", + uniqueGdBuyers: 0, + uniqueCreditUsers: 0 +}; + +export async function runAnalyticsAggregation(env: Env, now = new Date()): Promise { + const cfg = analyticsConfigFromEnv(env); + const store = new KVAnalyticsStore(env.ANTSEED_KV); + const currentDate = dayFromDate(now); + const state = (await store.getState()) ?? { + updatedAt: now.toISOString() + }; + + const finalizedDates = await finalizeClosedDays(store, state, currentDate, now); + const dayWindow = getUtcDayWindow(now); + const aggregate = createDailyAggregate(); + const knownBuyers = await store.getBuyerRegistry(); + const discoveredBuyers = new Set(); + + const celoMetrics = await collectCeloDayMetrics(cfg, dayWindow, aggregate, discoveredBuyers); + logInfo("got celo metrics"); + if (discoveredBuyers.size > 0) { + await store.addBuyersToRegistry([...discoveredBuyers]); + for (const buyer of discoveredBuyers) knownBuyers.add(buyer); + } + logInfo("getting base metrics...."); + const baseMetrics = await collectBaseDayMetrics(cfg, dayWindow, aggregate, knownBuyers); + logInfo("got base metrics...."); + + const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, now); + logInfo("building dialy reocrd...."); + + const dailyRecord = buildDailyRecord(currentDate, aggregate, now); + await store.replaceDaily(currentDate, dailyRecord, [...aggregate.gdBuyers], [...aggregate.creditUsers]); + const latestState = await store.getState(); + await store.putState({ + finalizedThroughDate: latestState?.finalizedThroughDate, + updatedAt: now.toISOString() + }); + + logInfo("analytics.sync.end", { + currentDate, + finalizedDates, + aggregate, + celoMetrics, + baseMetrics, + streamMetrics + }); + + return { + ok: true, + currentDate, + finalizedDates, + celo: celoMetrics, + base: baseMetrics, + streams: streamMetrics + }; +} + +export async function getAnalyticsWindow(env: Env, days = 30, now = new Date()): Promise { + const normalizedDays = Math.max(1, Math.min(days, 365)); + const store = new KVAnalyticsStore(env.ANTSEED_KV); + const currentDate = dayFromDate(now); + + const daily: AnalyticsDailyRecord[] = []; + for (let i = normalizedDays - 1; i >= 0; i -= 1) { + const date = dayFromDate(new Date(now.getTime() - i * 24 * 60 * 60 * 1000)); + daily.push(await store.getDaily(date)); + } + + const persistedGlobal = await store.getGlobal(); + const today = await store.getDaily(currentDate); + const global = addDailyToGlobal(persistedGlobal, today, now.toISOString()); + const state = (await store.getState()) ?? { updatedAt: now.toISOString() }; + + return { + days: normalizedDays, + daily, + global, + lastRun: { + currentDate, + finalizedThroughDate: state.finalizedThroughDate, + updatedAt: state.updatedAt + } + }; +} + +export class KVAnalyticsStore { + constructor(private readonly kv: KV) {} + + async getBuyerRegistry(): Promise> { + const buyers = (await this.getJson(BUYER_REGISTRY_KEY)) ?? []; + return new Set(buyers.map((buyer) => buyer.toLowerCase())); + } + + async addBuyersToRegistry(buyers: string[]): Promise { + if (buyers.length === 0) return; + const existing = await this.getBuyerRegistry(); + for (const buyer of buyers) { + existing.add(buyer.toLowerCase()); + } + await this.putJson(BUYER_REGISTRY_KEY, [...existing].sort()); + } + + async getDaily(date: string): Promise { + const key = `${DAILY_PREFIX}${date}`; + const value = await this.getJson(key); + if (value) return value; + return { + date, + ...ZERO_DAILY, + updatedAt: new Date().toISOString() + }; + } + + async replaceDaily(date: string, value: AnalyticsDailyRecord, gdBuyers: string[], creditUsers: string[]): Promise { + await this.putJson(`${DAILY_PREFIX}${date}`, value); + await this.putJson(`${GD_BUYERS_PREFIX}${date}`, dedupeAccounts(gdBuyers)); + await this.putJson(`${CREDIT_USERS_PREFIX}${date}`, dedupeAccounts(creditUsers)); + } + + async getGlobal(): Promise { + const value = await this.getJson(GLOBAL_KEY); + if (value) return value; + return { + gdOneTimeDepositsWei: "0", + gdStreamedWei: "0", + aiCreditsUsedWei: "0", + gdTotalFlowRateWeiPerSecond: "0", + updatedAt: new Date().toISOString() + }; + } + + async putGlobal(value: AnalyticsGlobalTotals): Promise { + await this.putJson(GLOBAL_KEY, value); + } + + async getState(): Promise { + return this.getJson(STATE_KEY); + } + + async putState(value: AnalyticsState): Promise { + await this.putJson(STATE_KEY, value); + } + + private async getJson(key: string): Promise { + const value = await this.kv.get(key, "json"); + return (value ?? undefined) as T | undefined; + } + + private async putJson(key: string, value: unknown): Promise { + await this.kv.put(key, JSON.stringify(value)); + } +} + +function analyticsConfigFromEnv(env: Env): AnalyticsConfig { + return { + celoBlockscoutUrl: env.CELO_BLOCKSCOUT_API_URL ?? "https://celo.blockscout.com/api", + baseBlockscoutUrl: env.BASE_BLOCKSCOUT_API_URL ?? "https://base.blockscout.com/api", + celoVaultAddress: env.CELO_VAULT_ADDRESS, + baseChannelsAddress: (env.ANTSEED_CHANNELS_ADDRESS ?? "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d").toLowerCase(), + celoSuperTokenAddress: env.CELO_GD_SUPERTOKEN_ADDRESS?.toLowerCase(), + celoStreamReceiverAddress: env.CELO_VAULT_ADDRESS?.toLowerCase(), + superfluidSubgraphUrl: env.SUPERFLUID_SUBGRAPH_URL ?? "https://celo-mainnet.subgraph.x.superfluid.dev/" + }; +} + +async function finalizeClosedDays(store: KVAnalyticsStore, state: AnalyticsState, currentDate: string, now: Date): Promise { + const finalizedDates: string[] = []; + const yesterday = previousDate(currentDate); + if (!yesterday) return finalizedDates; + + const startDate = state.finalizedThroughDate ? nextDate(state.finalizedThroughDate) : yesterday; + if (!startDate) { + return finalizedDates; + } + + if (startDate > yesterday) { + return finalizedDates; + } + + let global = await store.getGlobal(); + let cursor = startDate; + while (cursor <= yesterday) { + const day = await store.getDaily(cursor); + global = addDailyToGlobal(global, day, now.toISOString()); + finalizedDates.push(cursor); + const next = nextDate(cursor); + if (!next) break; + cursor = next; + } + + await store.putGlobal(global); + await store.putState({ + finalizedThroughDate: finalizedDates[finalizedDates.length - 1], + updatedAt: now.toISOString() + }); + return finalizedDates; +} + +async function collectCeloDayMetrics( + cfg: AnalyticsConfig, + dayWindow: UtcDayWindow, + aggregate: DailyAggregate, + discoveredBuyers: Set +): Promise<{ fromTimestamp: number; toTimestamp: number; scanned: number; matched: number }> { + if (!cfg.celoVaultAddress) { + return { + fromTimestamp: dayWindow.startUnix, + toTimestamp: dayWindow.endUnix, + scanned: 0, + matched: 0 + }; + } + + const range = await getExplorerBlockRange(cfg.celoBlockscoutUrl, dayWindow); + + const logs = await getExplorerEvents(cfg.celoBlockscoutUrl, { + address: cfg.celoVaultAddress, + topic0: getTopicHash(CELO_VAULT_EVENTS, "GdDeposited"), + fromBlock: range.fromBlock, + toBlock: range.toBlock + }); + logInfo("analytics.celo.scan", { + range, + dayWindow, + foundLogs: logs.length + }); + let matched = 0; + for (const log of logs) { + const decoded = decodeEventSafe(CELO_VAULT_EVENTS, log); + if (!decoded || decoded.name !== "GdDeposited") continue; + matched += 1; + aggregate.gdOneTimeDepositsWei += BigInt(decoded.args.gdAmount.toString()); + aggregate.gdBuyers.add(String(decoded.args.account).toLowerCase()); + discoveredBuyers.add(String(decoded.args.buyer).toLowerCase()); + } + + return { + fromTimestamp: dayWindow.startUnix, + toTimestamp: dayWindow.endUnix, + scanned: logs.length, + matched + }; +} + +async function collectBaseDayMetrics( + cfg: AnalyticsConfig, + dayWindow: UtcDayWindow, + aggregate: DailyAggregate, + knownBuyers: Set +): Promise<{ fromTimestamp: number; toTimestamp: number; scanned: number; matched: number }> { + const eventNames = ["Reserved", "ChannelSettled", "ChannelClosed", "ChannelTopUp", "ChannelWithdrawn", "CloseRequested"] as const; + + let scanned = 0; + let matched = 0; + const range = await getExplorerBlockRange(cfg.baseBlockscoutUrl, dayWindow); + logInfo("base range:", range); + for (const eventName of eventNames) { + const logs = await getExplorerEvents(cfg.baseBlockscoutUrl, { + address: cfg.baseChannelsAddress, + topic0: getTopicHash(BASE_CHANNEL_EVENTS, eventName), + fromBlock: range.fromBlock, + toBlock: range.toBlock + }); + logInfo("analytics.base.scan", { + eventName, + range, + dayWindow, + foundLogs: logs.length + }); + scanned += logs.length; + + for (const log of logs) { + const decoded = decodeEventSafe(BASE_CHANNEL_EVENTS, log); + if (!decoded) continue; + const buyer = String(decoded.args.buyer).toLowerCase(); + if (!knownBuyers.has(buyer)) continue; + matched += 1; + aggregate.creditUsers.add(buyer); + if (decoded.name === "ChannelSettled") { + aggregate.aiCreditsUsedWei += BigInt(decoded.args.delta.toString()); + } + } + } + + return { + fromTimestamp: dayWindow.startUnix, + toTimestamp: dayWindow.endUnix, + scanned, + matched + }; +} + +async function collectStreamDayMetrics( + cfg: AnalyticsConfig, + dayWindow: UtcDayWindow, + aggregate: DailyAggregate, + now: Date +): Promise<{ senders: number; totalFlowRateWeiPerSecond: string }> { + const snapshots = await fetchStreamSnapshots(cfg, now, dayWindow.startUnix); + + for (const snapshot of snapshots) { + aggregate.gdTotalFlowRateWeiPerSecond += snapshot.flowRateWeiPerSecond; + if (snapshot.totalStreamedWei > 0n) { + aggregate.gdStreamedWei += snapshot.totalStreamedWei; + aggregate.gdBuyers.add(snapshot.sender); + } + } + + return { + senders: snapshots.length, + totalFlowRateWeiPerSecond: aggregate.gdTotalFlowRateWeiPerSecond.toString() + }; +} + +function buildDailyRecord(date: string, aggregate: DailyAggregate, now: Date): AnalyticsDailyRecord { + return { + date, + gdOneTimeDepositsWei: aggregate.gdOneTimeDepositsWei.toString(), + gdStreamedWei: aggregate.gdStreamedWei.toString(), + gdTotalFlowRateWeiPerSecond: aggregate.gdTotalFlowRateWeiPerSecond.toString(), + aiCreditsUsedWei: aggregate.aiCreditsUsedWei.toString(), + uniqueGdBuyers: aggregate.gdBuyers.size, + uniqueCreditUsers: aggregate.creditUsers.size, + updatedAt: now.toISOString() + }; +} + +function addDailyToGlobal(global: AnalyticsGlobalTotals, day: AnalyticsDailyRecord, updatedAt: string): AnalyticsGlobalTotals { + return { + gdOneTimeDepositsWei: (BigInt(global.gdOneTimeDepositsWei) + BigInt(day.gdOneTimeDepositsWei)).toString(), + gdStreamedWei: (BigInt(global.gdStreamedWei) + BigInt(day.gdStreamedWei)).toString(), + aiCreditsUsedWei: (BigInt(global.aiCreditsUsedWei) + BigInt(day.aiCreditsUsedWei)).toString(), + gdTotalFlowRateWeiPerSecond: BigInt(day.gdTotalFlowRateWeiPerSecond).toString(), + updatedAt + }; +} + +async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUnix: number): Promise { + if (!cfg.celoSuperTokenAddress || !cfg.celoStreamReceiverAddress) { + logWarn("analytics.streams.skipped", { + reason: "missing_config", + hasToken: Boolean(cfg.celoSuperTokenAddress), + hasReceiver: Boolean(cfg.celoStreamReceiverAddress) + }); + return []; + } + + const snapshotsBySender = new Map(); + const pageSize = 1000; + let skip = 0; + const nowUnix = Math.floor(now.getTime() / 1000); + + while (true) { + const body = { + query: ` + query StreamsPage($receiver: String!, $token: String!, $first: Int!, $skip: Int!) { + streams( + where: { receiver: $receiver, token: $token } + first: $first + skip: $skip + ) { + sender { id } + currentFlowRate + streamedUntilUpdatedAt + updatedAtTimestamp + } + } + `, + variables: { + receiver: cfg.celoStreamReceiverAddress, + token: cfg.celoSuperTokenAddress, + first: pageSize, + skip + } + }; + + const response = await retry( + () => + fetch(cfg.superfluidSubgraphUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body) + }), + 3, + 500 + ); + + if (!response.ok) { + throw new Error(`Superfluid subgraph HTTP ${response.status}`); + } + + const json = (await response.json()) as { + data?: { + streams?: Array<{ + sender: { id: string }; + currentFlowRate: string; + streamedUntilUpdatedAt: string; + updatedAtTimestamp: string; + }>; + }; + }; + + const batch = json.data?.streams ?? []; + for (const stream of batch) { + const sender = stream.sender.id.toLowerCase(); + const currentFlowRate = BigInt(stream.currentFlowRate || "0"); + const streamedUntilUpdatedAt = BigInt(stream.streamedUntilUpdatedAt || "0"); + const updatedAtTimestamp = parseNumberish(stream.updatedAtTimestamp || "0"); + const totalStreamedWei = streamedWithinDay(streamedUntilUpdatedAt, currentFlowRate, updatedAtTimestamp, dayStartUnix, nowUnix); + const existing = snapshotsBySender.get(sender); + if (existing) { + existing.flowRateWeiPerSecond += currentFlowRate; + existing.totalStreamedWei += totalStreamedWei; + } else { + snapshotsBySender.set(sender, { + flowRateWeiPerSecond: currentFlowRate, + totalStreamedWei + }); + } + } + + if (batch.length < pageSize) break; + skip += pageSize; + } + + return [...snapshotsBySender.entries()].map(([sender, value]) => ({ + sender, + flowRateWeiPerSecond: value.flowRateWeiPerSecond, + totalStreamedWei: value.totalStreamedWei + })); +} + +async function getExplorerEvents( + apiUrl: string, + params: { + address: string; + topic0: string; + fromBlock: number; + toBlock: number; + } +): Promise { + return getExplorerEventsByRange(apiUrl, params, params.fromBlock, params.toBlock); +} + +async function getExplorerEventsByRange( + apiUrl: string, + params: { + address: string; + topic0: string; + fromBlock: number; + toBlock: number; + }, + fromBlock: number, + toBlock: number +): Promise { + const batch = await fetchExplorerLogBatch(apiUrl, { + address: params.address, + topic0: params.topic0, + fromBlock, + toBlock + }); + + if (batch.length < EXPLORER_LOG_BATCH_LIMIT) { + return batch; + } + + if (fromBlock >= toBlock) { + logWarn("analytics.explorer.truncated", { + apiUrl, + address: redactAddress(params.address), + topic0: params.topic0, + fromBlock, + toBlock, + returned: batch.length + }); + return dedupeExplorerLogs(batch); + } + + const midpoint = fromBlock + Math.floor((toBlock - fromBlock) / 2); + const left = await getExplorerEventsByRange(apiUrl, params, fromBlock, midpoint); + const right = await getExplorerEventsByRange(apiUrl, params, midpoint + 1, toBlock); + return dedupeExplorerLogs([...left, ...right]); +} + +async function fetchExplorerLogBatch( + apiUrl: string, + params: { + address: string; + topic0: string; + fromBlock: number; + toBlock: number; + } +): Promise { + const all: ExplorerLog[] = []; + const url = new URL(apiUrl); + url.searchParams.set("module", "logs"); + url.searchParams.set("action", "getLogs"); + url.searchParams.set("address", params.address); + url.searchParams.set("topic0", params.topic0); + url.searchParams.set("sort", "asc"); + url.searchParams.set("offset", String(EXPLORER_LOG_BATCH_LIMIT)); + url.searchParams.set("fromBlock", String(params.fromBlock)); + url.searchParams.set("toBlock", String(params.toBlock)); + + const response = await retry(() => fetch(url), 3, 300); + if (!response.ok) { + throw new Error(`Explorer HTTP ${response.status}`); + } + + const payload = (await response.json()) as { + status?: string; + message?: string; + result?: ExplorerLog[] | string; + }; + if (Array.isArray(payload.result)) { + all.push(...payload.result); + return all; + } + + if (typeof payload.result === "string" && payload.result.toLowerCase().includes("no records")) { + return all; + } + + if (payload.status === "0" && payload.message?.toLowerCase().includes("no records")) { + return all; + } + + logWarn("analytics.explorer.unexpected", { + apiUrl, + address: redactAddress(params.address), + topic0: params.topic0, + fromBlock: params.fromBlock, + toBlock: params.toBlock, + payload + }); + return all; +} + +async function getExplorerBlockRange(apiUrl: string, dayWindow: UtcDayWindow): Promise<{ fromBlock: number; toBlock: number }> { + const fromBlock = await getBlockByTimestamp(apiUrl, dayWindow.startUnix, "after"); + const toBlock = await getBlockByTimestamp(apiUrl, dayWindow.endUnix, "before"); + return { + fromBlock, + toBlock: toBlock >= fromBlock ? toBlock : fromBlock + }; +} + +async function getBlockByTimestamp(apiUrl: string, timestamp: number, closest: "before" | "after"): Promise { + const url = new URL(apiUrl); + url.searchParams.set("module", "block"); + url.searchParams.set("action", "getblocknobytime"); + url.searchParams.set("timestamp", String(timestamp)); + url.searchParams.set("closest", closest); + + const response = await retry(() => fetch(url), 3, 300); + if (!response.ok) { + throw new Error(`Explorer HTTP ${response.status}`); + } + + const payload = (await response.json()) as { + status?: string; + message?: string; + result: { blockNumber: string }; + }; + + if (payload.result && payload.status !== "0") { + return parseNumberish(payload.result?.blockNumber ?? payload.result); + } + + throw new Error(`Explorer getblocknobytime failed: ${payload.message ?? "unknown"}`); +} + +function createDailyAggregate(): DailyAggregate { + return { + gdOneTimeDepositsWei: 0n, + gdStreamedWei: 0n, + gdTotalFlowRateWeiPerSecond: 0n, + aiCreditsUsedWei: 0n, + gdBuyers: new Set(), + creditUsers: new Set() + }; +} + +type UtcDayWindow = { + startUnix: number; + endUnix: number; +}; + +function getUtcDayWindow(date: Date, mode: "full-day" | "until-now" = "full-day"): UtcDayWindow { + const startMs = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0, 0); + const endMs = mode === "full-day" ? Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 23, 59, 59, 999) : date.getTime(); + + return { + startUnix: Math.floor(startMs / 1000), + endUnix: Math.floor(endMs / 1000) + }; +} + +function dayFromDate(date: Date): string { + return date.toISOString().slice(0, 10); +} + +function previousDate(date: string): string | undefined { + const parsed = new Date(`${date}T00:00:00.000Z`); + if (Number.isNaN(parsed.getTime())) return undefined; + parsed.setUTCDate(parsed.getUTCDate() - 1); + return dayFromDate(parsed); +} + +function nextDate(date: string): string | undefined { + const parsed = new Date(`${date}T00:00:00.000Z`); + if (Number.isNaN(parsed.getTime())) return undefined; + parsed.setUTCDate(parsed.getUTCDate() + 1); + return dayFromDate(parsed); +} + +function dedupeAccounts(accounts: string[]): string[] { + return [...new Set(accounts.map((account) => account.toLowerCase()))].sort(); +} + +function streamedWithinDay(streamedUntilUpdatedAt: bigint, currentFlowRate: bigint, updatedAtTimestamp: number, dayStartUnix: number, nowUnix: number): bigint { + if (updatedAtTimestamp >= dayStartUnix) { + const boundedActiveSeconds = BigInt(Math.max(0, nowUnix - updatedAtTimestamp)); + return streamedUntilUpdatedAt + currentFlowRate * boundedActiveSeconds; + } + if (currentFlowRate === 0n) { + return 0n; + } + const boundedUpdatedAt = Math.max(updatedAtTimestamp, dayStartUnix); + const activeSeconds = BigInt(Math.max(0, nowUnix - boundedUpdatedAt)); + return currentFlowRate * activeSeconds; +} + +function getTopicHash(iface: Interface, eventName: string): string { + const event = iface.getEvent(eventName); + if (!event) throw new Error(`event not found: ${eventName}`); + return event.topicHash; +} + +function decodeEventSafe(iface: Interface, log: ExplorerLog) { + try { + log.topics = log.topics.filter((topic) => topic); + return iface.parseLog(log); + } catch (error) { + logWarn("analytics.decode.failed", { + txHash: log.transactionHash, + logIndex: log.logIndex, + message: errorMessage(error) + }); + return null; + } +} + +function parseNumberish(value: string | number): number { + if (typeof value === "number") return value; + if (value.startsWith("0x")) return Number.parseInt(value, 16); + return Number.parseInt(value, 10); +} + +function dedupeExplorerLogs(logs: ExplorerLog[]): ExplorerLog[] { + const seen = new Set(); + const deduped: ExplorerLog[] = []; + + for (const log of logs) { + const key = `${log.transactionHash}:${log.logIndex}`.toLowerCase(); + if (seen.has(key)) continue; + seen.add(key); + deduped.push(log); + } + + return deduped; +} + +async function retry(fn: () => Promise, retries: number, waitMs: number): Promise { + let attempt = 0; + let lastError: unknown; + while (attempt <= retries) { + try { + return await fn(); + } catch (error) { + lastError = error; + attempt += 1; + if (attempt > retries) break; + await wait(waitMs); + } + } + logError("analytics.retry.failed", { + retries, + message: errorMessage(lastError) + }); + throw lastError instanceof Error ? lastError : new Error("retry failed"); +} + +function wait(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/backend/src/env.ts b/backend/src/env.ts index 466819e..4dfe9c2 100644 --- a/backend/src/env.ts +++ b/backend/src/env.ts @@ -16,6 +16,9 @@ export interface Env { CELO_STATIC_ORACLE_ADDRESS?: string; CELO_CUSD_ADDRESS?: string; SUPERFLUID_SUBGRAPH_URL?: string; + CELO_BLOCKSCOUT_API_URL?: string; + BASE_BLOCKSCOUT_API_URL?: string; + ANTSEED_CHANNELS_ADDRESS?: string; MAX_BONUS_CAP_USD?: string; REGULAR_BONUS_BPS?: string; STREAMING_BONUS_BPS?: string; @@ -36,6 +39,9 @@ export type RuntimeConfig = { CELO_STATIC_ORACLE_ADDRESS?: string; CELO_CUSD_ADDRESS?: string; SUPERFLUID_SUBGRAPH_URL?: string; + CELO_BLOCKSCOUT_API_URL?: string; + BASE_BLOCKSCOUT_API_URL?: string; + ANTSEED_CHANNELS_ADDRESS?: string; MAX_BONUS_CAP_USD: bigint; REGULAR_BONUS_BPS: bigint; STREAMING_BONUS_BPS: bigint; @@ -56,6 +62,9 @@ export function configFromEnv(env: Env): RuntimeConfig { CELO_STATIC_ORACLE_ADDRESS: env.CELO_STATIC_ORACLE_ADDRESS, CELO_CUSD_ADDRESS: env.CELO_CUSD_ADDRESS, SUPERFLUID_SUBGRAPH_URL: env.SUPERFLUID_SUBGRAPH_URL, + CELO_BLOCKSCOUT_API_URL: env.CELO_BLOCKSCOUT_API_URL, + BASE_BLOCKSCOUT_API_URL: env.BASE_BLOCKSCOUT_API_URL, + ANTSEED_CHANNELS_ADDRESS: env.ANTSEED_CHANNELS_ADDRESS, MAX_BONUS_CAP_USD: parseUnits(env.MAX_BONUS_CAP_USD || "100", 18), REGULAR_BONUS_BPS: bigintEnv(env.REGULAR_BONUS_BPS, 1_000n), STREAMING_BONUS_BPS: bigintEnv(env.STREAMING_BONUS_BPS, 2_000n), diff --git a/backend/src/logging.ts b/backend/src/logging.ts index 3c79d05..1938371 100644 --- a/backend/src/logging.ts +++ b/backend/src/logging.ts @@ -21,7 +21,6 @@ function emit(level: LogLevel, event: string, data?: LogData): void { console.warn(event, payload); return; } - console.log(event, payload); } export function logInfo(event: string, data?: LogData): void { diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 6b3bed0..ef698f8 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -5,6 +5,7 @@ import { Env, configFromEnv } from "./env.js"; import { KVCreditStore } from "./kv-credit-store.js"; import { GdCreditEntry } from "./types.js"; import { errorMessage, logError, logInfo, logWarn, redactAddress, redactHash } from "./logging.js"; +import { getAnalyticsWindow, runAnalyticsAggregation, KVAnalyticsStore } from "./analytics.js"; const CeloEventsRecordSchema = z .object({ @@ -84,6 +85,9 @@ const SuperfluidStreamsResponseSchema = z.object({ ) }) }); +const AnalyticsQuerySchema = z.object({ + days: z.coerce.number().int().min(1).max(365).default(30) +}); const SUPERFLUID_CELO_SUBGRAPH_URL = "https://subgraph-endpoints.superfluid.dev/celo-mainnet/protocol-v1"; @@ -208,6 +212,23 @@ export default { failed, elapsedMs: Date.now() - startedAt }); + + try { + const analyticsSummary = await runAnalyticsAggregation(env); + logInfo("cron.analytics.summary", analyticsSummary); + } catch (error) { + logError("cron.analytics.failed", { + message: errorMessage(error) + }); + ctx.waitUntil( + notifySlackOnRequestError(env, { + method: "SCHEDULED", + path: "/v1/analytics/refresh", + body: undefined, + message: errorMessage(error) + }) + ); + } } }; @@ -270,6 +291,36 @@ async function route(request: Request, env: Env, _ctx: ExecutionContext): Promis }); } + if (request.method === "GET" && url.pathname === "/v1/analytics") { + const parsed = AnalyticsQuerySchema.safeParse({ + days: url.searchParams.get("days") ?? undefined + }); + if (!parsed.success) return json({ error: parsed.error.flatten() }, 400); + const analytics = await getAnalyticsWindow(env, parsed.data.days); + return json(analytics); + } + + if (request.method === "POST" && url.pathname === "/v1/analytics/refresh") { + const store = new KVAnalyticsStore(env.ANTSEED_KV); + // const state = undefined; + const state = await store.getState(); + if (!state) { + logInfo("analytics.refresh.first-run"); + const startDate = new Date("2026-07-02T00:00:00Z"); + while (startDate < new Date()) { + logInfo(`Running analytics aggregation for ${startDate.toISOString().slice(0, 10)}`); + await runAnalyticsAggregation(env, startDate); + startDate.setUTCDate(startDate.getUTCDate() + 1); + } + return json({}); + } else { + const startDate = new Date("2026-07-16T00:00:00Z"); + + const summary = await runAnalyticsAggregation(env, startDate); + return json(summary); + } + } + const accountMatch = url.pathname.match(/^\/v1\/accounts\/([^/]+)\/profile$/); if (request.method === "GET" && accountMatch) { const account = decodeURIComponent(accountMatch[1]); diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index 322a11d..e8cc5b3 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -1,5 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; +import { Interface } from "ethers"; import worker from "../src/worker.js"; import { Env } from "../src/env.js"; import { encodeVaultEventLog } from "../src/celo-events.js"; @@ -25,6 +26,34 @@ function env(overrides: Partial = {}): Env { } as Env; } +const ChannelsEvents = new Interface([ + "event Reserved(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 maxAmount)", + "event ChannelSettled(bytes32 indexed channelId,address indexed buyer,address indexed seller,uint128 cumulativeAmount,uint128 delta,uint128 totalSettled,uint256 platformFee,bytes metadata)" +]); + +function encodeChannelLog( + eventName: "Reserved" | "ChannelSettled", + args: readonly unknown[], + address: string, + txHash: string, + logIndex: number, + blockNumber: number, + timestamp: number +) { + const event = ChannelsEvents.getEvent(eventName); + if (!event) throw new Error(`unknown event ${eventName}`); + const encoded = ChannelsEvents.encodeEventLog(event, args); + return { + address, + topics: encoded.topics, + data: encoded.data, + transactionHash: txHash, + logIndex: `0x${logIndex.toString(16)}`, + blockNumber: `0x${blockNumber.toString(16)}`, + timeStamp: `0x${timestamp.toString(16)}` + }; +} + test("health exposes bridge status", async () => { const res = await worker.fetch(new Request("https://worker.test/health"), env(), {} as ExecutionContext); assert.equal(res.status, 200); @@ -96,11 +125,7 @@ test("GET /v1/accounts/:account/credit-history returns paginated empty history", test("GET /v1/accounts/:account/credit-history returns 400 on invalid query", async () => { const account = "0x0000000000000000000000000000000000000abc"; - const res = await worker.fetch( - new Request(`https://worker.test/v1/accounts/${account}/credit-history?source=not-a-source`), - env(), - {} as ExecutionContext - ); + const res = await worker.fetch(new Request(`https://worker.test/v1/accounts/${account}/credit-history?source=not-a-source`), env(), {} as ExecutionContext); assert.equal(res.status, 400); }); @@ -296,6 +321,489 @@ test("OPTIONS returns CORS preflight", async () => { assert.equal(res.headers.get("access-control-allow-origin"), "*"); }); +test("analytics refresh overwrites current day and query adds current day to persisted globals", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const account = "0x0000000000000000000000000000000000000abc"; + const buyer = "0x0000000000000000000000000000000000000def"; + const seller = "0x0000000000000000000000000000000000000fed"; + const channelId = `0x${"1".repeat(64)}`; + const now = new Date("2026-07-24T12:00:00.000Z"); + const timestamp = Math.floor(now.getTime() / 1000) - 60; + const date = now.toISOString().slice(0, 10); + + const celoDepositLog = { + ...encodeVaultEventLog("GdDeposited", [account, buyer, 2_000_000_000_000_000_000n, "0x"], testEnv.CELO_VAULT_ADDRESS!, `0x${"2".repeat(64)}`, 0), + blockNumber: "0x64", + timeStamp: `0x${timestamp.toString(16)}` + }; + + const reservedLog = encodeChannelLog( + "Reserved", + [channelId, buyer, seller, 1_000_000n], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"3".repeat(64)}`, + 0, + 210, + timestamp + ); + const settledLog = encodeChannelLog( + "ChannelSettled", + [channelId, buyer, seller, 3_000_000n, 500_000n, 1_000_000n, 0n, "0x"], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"4".repeat(64)}`, + 1, + 211, + timestamp + ); + + const originalFetch = globalThis.fetch; + try { + let celoAmountWei = 2_000_000_000_000_000_000n; + + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + const module = url.searchParams.get("module"); + const action = url.searchParams.get("action"); + if (module === "block" && action === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (module === "proxy" && action === "eth_blockNumber") { + return Response.json({ result: "0x64" }); + } + if (module === "logs" && action === "getLogs") { + const dynamicCeloLog = { + ...encodeVaultEventLog("GdDeposited", [account, buyer, celoAmountWei, "0x"], testEnv.CELO_VAULT_ADDRESS!, `0x${"2".repeat(64)}`, 0), + blockNumber: "0x64", + timeStamp: `0x${timestamp.toString(16)}` + }; + return Response.json({ status: "1", message: "OK", result: [dynamicCeloLog] }); + } + } + + if (url.host === "base.blockscout.test") { + const module = url.searchParams.get("module"); + const action = url.searchParams.get("action"); + if (module === "block" && action === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (module === "proxy" && action === "eth_blockNumber") { + return Response.json({ result: "0xd3" }); + } + if (module === "logs" && action === "getLogs") { + const topic0 = url.searchParams.get("topic0")?.toLowerCase(); + const reservedTopic = ChannelsEvents.getEvent("Reserved")?.topicHash.toLowerCase(); + const settledTopic = ChannelsEvents.getEvent("ChannelSettled")?.topicHash.toLowerCase(); + if (topic0 === reservedTopic) { + return Response.json({ status: "1", message: "OK", result: [reservedLog] }); + } + if (topic0 === settledTopic) { + return Response.json({ status: "1", message: "OK", result: [settledLog] }); + } + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: account }, + currentFlowRate: "0", + streamedUntilUpdatedAt: "1000000000000000000", + updatedAtTimestamp: String(timestamp) + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await import("../src/analytics.js").then(async ({ runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, now); + }); + + celoAmountWei = 5_000_000_000_000_000_000n; + + await import("../src/analytics.js").then(async ({ runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, now); + }); + + const analyticsBody = (await import("../src/analytics.js").then(async ({ getAnalyticsWindow }) => getAnalyticsWindow(testEnv, 1, now))) as { + daily: Array<{ + date: string; + gdOneTimeDepositsWei: string; + gdStreamedWei: string; + aiCreditsUsedWei: string; + uniqueGdBuyers: number; + uniqueCreditUsers: number; + }>; + global: { + gdOneTimeDepositsWei: string; + gdStreamedWei: string; + aiCreditsUsedWei: string; + }; + }; + + assert.equal(analyticsBody.daily.length, 1); + assert.equal(analyticsBody.daily[0].date, date); + assert.equal(analyticsBody.daily[0].gdOneTimeDepositsWei, "5000000000000000000"); + assert.equal(analyticsBody.daily[0].gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.daily[0].aiCreditsUsedWei, "500000"); + assert.equal(analyticsBody.daily[0].uniqueGdBuyers, 1); + assert.equal(analyticsBody.daily[0].uniqueCreditUsers, 1); + assert.equal(analyticsBody.global.gdOneTimeDepositsWei, "5000000000000000000"); + assert.equal(analyticsBody.global.gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.global.aiCreditsUsedWei, "500000"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("analytics refresh excludes base usage for buyers outside known buyer registry", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const account = "0x0000000000000000000000000000000000000abc"; + const knownBuyer = "0x0000000000000000000000000000000000000def"; + const unknownBuyer = "0x0000000000000000000000000000000000000bad"; + const seller = "0x0000000000000000000000000000000000000fed"; + const channelId = `0x${"7".repeat(64)}`; + const now = new Date("2026-07-24T12:00:00.000Z"); + const timestamp = Math.floor(now.getTime() / 1000) - 60; + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (url.searchParams.get("module") === "logs") { + return Response.json({ + status: "1", + message: "OK", + result: [ + { + ...encodeVaultEventLog( + "GdDeposited", + [account, knownBuyer, 1_000_000_000_000_000_000n, "0x"], + testEnv.CELO_VAULT_ADDRESS!, + `0x${"8".repeat(64)}`, + 0 + ), + blockNumber: "0x64", + timeStamp: `0x${timestamp.toString(16)}` + } + ] + }); + } + } + + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("module") === "logs") { + const topic0 = url.searchParams.get("topic0")?.toLowerCase(); + const settledTopic = ChannelsEvents.getEvent("ChannelSettled")?.topicHash.toLowerCase(); + if (topic0 === settledTopic) { + const known = encodeChannelLog( + "ChannelSettled", + [channelId, knownBuyer, seller, 3_000_000n, 500_000n, 1_000_000n, 0n, "0x"], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"9".repeat(64)}`, + 1, + 211, + timestamp + ); + const unknown = encodeChannelLog( + "ChannelSettled", + [channelId, unknownBuyer, seller, 5_000_000n, 700_000n, 2_000_000n, 0n, "0x"], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"6".repeat(64)}`, + 2, + 212, + timestamp + ); + return Response.json({ status: "1", message: "OK", result: [known, unknown] }); + } + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: account }, + currentFlowRate: "0", + streamedUntilUpdatedAt: "0", + updatedAtTimestamp: String(timestamp) + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await import("../src/analytics.js").then(async ({ runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, now); + }); + + const analyticsBody = (await import("../src/analytics.js").then(async ({ getAnalyticsWindow }) => getAnalyticsWindow(testEnv, 1, now))) as { + daily: Array<{ + aiCreditsUsedWei: string; + uniqueCreditUsers: number; + }>; + }; + + assert.equal(analyticsBody.daily[0].aiCreditsUsedWei, "500000"); + assert.equal(analyticsBody.daily[0].uniqueCreditUsers, 1); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("analytics refresh finalizes previous day into persisted globals once day rolls over", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const account = "0x0000000000000000000000000000000000000abc"; + const buyer = "0x0000000000000000000000000000000000000def"; + const seller = "0x0000000000000000000000000000000000000fed"; + const channelId = `0x${"9".repeat(64)}`; + const firstNow = new Date("2026-07-23T10:00:00.000Z"); + const secondNow = new Date("2026-07-24T01:00:00.000Z"); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + const activeNow = url.host === "superfluid.test" ? (JSON.parse(String(init?.body)) as { variables: { skip: number } }).variables.skip : 0; + const timestamp = Math.floor(firstNow.getTime() / 1000) - 60; + + if (url.host === "celo.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (url.searchParams.get("module") === "logs") { + return Response.json({ + status: "1", + message: "OK", + result: [ + { + ...encodeVaultEventLog( + "GdDeposited", + [account, buyer, 2_000_000_000_000_000_000n, "0x"], + testEnv.CELO_VAULT_ADDRESS!, + `0x${"a".repeat(64)}`, + 0 + ), + blockNumber: "0x64", + timeStamp: `0x${timestamp.toString(16)}` + } + ] + }); + } + } + + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("module") === "logs") { + const topic0 = url.searchParams.get("topic0")?.toLowerCase(); + const settledTopic = ChannelsEvents.getEvent("ChannelSettled")?.topicHash.toLowerCase(); + if (topic0 === settledTopic) { + return Response.json({ + status: "1", + message: "OK", + result: [ + encodeChannelLog( + "ChannelSettled", + [channelId, buyer, seller, 3_000_000n, 500_000n, 1_000_000n, 0n, "0x"], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"b".repeat(64)}`, + 1, + 211, + timestamp + ) + ] + }); + } + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + if (activeNow > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: account }, + currentFlowRate: "0", + streamedUntilUpdatedAt: "1000000000000000000", + updatedAtTimestamp: String(timestamp) + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await import("../src/analytics.js").then(async ({ runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, firstNow); + await runAnalyticsAggregation(testEnv, secondNow); + }); + + const analyticsBody = await import("../src/analytics.js").then(async ({ getAnalyticsWindow }) => getAnalyticsWindow(testEnv, 2, secondNow)); + assert.equal(analyticsBody.global.gdOneTimeDepositsWei, "4000000000000000000"); + assert.equal(analyticsBody.global.gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.global.aiCreditsUsedWei, "1000000"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("analytics refresh collects logs beyond the explorer 1000-result cap", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const account = "0x0000000000000000000000000000000000000abc"; + const buyer = "0x0000000000000000000000000000000000000def"; + const now = new Date("2026-07-24T12:00:00.000Z"); + const timestamp = Math.floor(now.getTime() / 1000) - 60; + const totalLogs = 1001; + const expectedDepositsWei = (1_000_000_000_000_000_000n * BigInt(totalLogs)).toString(); + const celoLogs = Array.from({ length: totalLogs }, (_, index) => ({ + ...encodeVaultEventLog( + "GdDeposited", + [account, buyer, 1_000_000_000_000_000_000n, "0x"], + testEnv.CELO_VAULT_ADDRESS!, + `0x${(index + 1).toString(16).padStart(64, "0")}`, + 0 + ), + blockNumber: `0x${(100 + index).toString(16)}`, + timeStamp: `0x${timestamp.toString(16)}` + })); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + const module = url.searchParams.get("module"); + const action = url.searchParams.get("action"); + if (module === "block" && action === "getblocknobytime") { + const closest = url.searchParams.get("closest"); + return Response.json({ status: "1", message: "OK", result: closest === "after" ? "100" : String(99 + totalLogs) }); + } + if (module === "logs" && action === "getLogs") { + const fromBlock = Number(url.searchParams.get("fromBlock")); + const toBlock = Number(url.searchParams.get("toBlock")); + const filtered = celoLogs.filter((log) => { + const blockNumber = Number.parseInt(log.blockNumber, 16); + return blockNumber >= fromBlock && blockNumber <= toBlock; + }); + return Response.json({ status: "1", message: "OK", result: filtered.slice(0, 1000) }); + } + } + + if (url.host === "base.blockscout.test") { + const module = url.searchParams.get("module"); + const action = url.searchParams.get("action"); + if (module === "block" && action === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (module === "logs" && action === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ data: { streams: [] } }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await import("../src/analytics.js").then(async ({ runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, now); + }); + + const analyticsBody = (await import("../src/analytics.js").then(async ({ getAnalyticsWindow }) => getAnalyticsWindow(testEnv, 1, now))) as { + daily: Array<{ + gdOneTimeDepositsWei: string; + uniqueGdBuyers: number; + }>; + global: { + gdOneTimeDepositsWei: string; + }; + }; + + assert.equal(analyticsBody.daily[0].gdOneTimeDepositsWei, expectedDepositsWei); + assert.equal(analyticsBody.daily[0].uniqueGdBuyers, 1); + assert.equal(analyticsBody.global.gdOneTimeDepositsWei, expectedDepositsWei); + } finally { + globalThis.fetch = originalFetch; + } +}); + const CHANNEL_ID = `0x${"a".repeat(64)}`; test("POST /v1/channels/:channelId/close returns enabled:false when vault not configured", async () => { diff --git a/backend/wrangler.toml b/backend/wrangler.toml index 1acc8c6..fcb6af8 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -23,6 +23,9 @@ CELO_GOODID_ADDRESS = "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42" CELO_STATIC_ORACLE_ADDRESS = "0x00851A91a3c4E9a4c1B48df827Bacc1f884bdE28" CELO_CUSD_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a" CELO_GD_SUPERTOKEN_ADDRESS = "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" +CELO_BLOCKSCOUT_API_URL = "https://celo.blockscout.com/api" +BASE_BLOCKSCOUT_API_URL = "https://base.blockscout.com/api" +ANTSEED_CHANNELS_ADDRESS = "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d" [[kv_namespaces]] binding = "ANTSEED_KV" @@ -30,7 +33,7 @@ id = "0831898ded5048049353498b8fc962f0" preview_id = "11111111111111111111111111111111" [triggers] -crons = ["* * * * *"] +crons = ["0 */6 * * *"] [observability.logs] enabled = true From b28cbc366d36417fd0d09e5d7f18ca264d2f20da Mon Sep 17 00:00:00 2001 From: sirpy Date: Fri, 24 Jul 2026 17:01:19 +0300 Subject: [PATCH 02/25] fix: analytics first run --- backend/src/worker.ts | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index ef698f8..377a955 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -214,8 +214,21 @@ export default { }); try { - const analyticsSummary = await runAnalyticsAggregation(env); - logInfo("cron.analytics.summary", analyticsSummary); + const store = new KVAnalyticsStore(env.ANTSEED_KV); + // const state = undefined; + const state = await store.getState(); + if (!state) { + logInfo("cron.analytics.first-run"); + const startDate = new Date("2026-07-02T00:00:00Z"); + while (startDate < new Date()) { + logInfo(`Running analytics aggregation for ${startDate.toISOString().slice(0, 10)}`); + await runAnalyticsAggregation(env, startDate); + startDate.setUTCDate(startDate.getUTCDate() + 1); + } + } else { + const analyticsSummary = await runAnalyticsAggregation(env); + logInfo("cron.analytics.summary", analyticsSummary); + } } catch (error) { logError("cron.analytics.failed", { message: errorMessage(error) @@ -301,24 +314,8 @@ async function route(request: Request, env: Env, _ctx: ExecutionContext): Promis } if (request.method === "POST" && url.pathname === "/v1/analytics/refresh") { - const store = new KVAnalyticsStore(env.ANTSEED_KV); - // const state = undefined; - const state = await store.getState(); - if (!state) { - logInfo("analytics.refresh.first-run"); - const startDate = new Date("2026-07-02T00:00:00Z"); - while (startDate < new Date()) { - logInfo(`Running analytics aggregation for ${startDate.toISOString().slice(0, 10)}`); - await runAnalyticsAggregation(env, startDate); - startDate.setUTCDate(startDate.getUTCDate() + 1); - } - return json({}); - } else { - const startDate = new Date("2026-07-16T00:00:00Z"); - - const summary = await runAnalyticsAggregation(env, startDate); - return json(summary); - } + const summary = await runAnalyticsAggregation(env); + return json(summary); } const accountMatch = url.pathname.match(/^\/v1\/accounts\/([^/]+)\/profile$/); From 05cdda02270cf7808489ae3c66a722681df763ef Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:06:47 +0300 Subject: [PATCH 03/25] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/src/analytics.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 38e599d..2ae6bf4 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -122,7 +122,8 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi }; const finalizedDates = await finalizeClosedDays(store, state, currentDate, now); - const dayWindow = getUtcDayWindow(now); + const isCurrentDay = dayFromDate(now) === dayFromDate(new Date()); + const dayWindow = getUtcDayWindow(now, isCurrentDay ? "until-now" : "full-day"); const aggregate = createDailyAggregate(); const knownBuyers = await store.getBuyerRegistry(); const discoveredBuyers = new Set(); From f22f73717c3f1f0e89d83a6b67542bc6b4337e2e Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:08:00 +0300 Subject: [PATCH 04/25] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/src/analytics.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 2ae6bf4..f98d121 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -673,11 +673,13 @@ async function getBlockByTimestamp(apiUrl: string, timestamp: number, closest: " const payload = (await response.json()) as { status?: string; message?: string; - result: { blockNumber: string }; + result?: string | { blockNumber?: string }; }; if (payload.result && payload.status !== "0") { - return parseNumberish(payload.result?.blockNumber ?? payload.result); + const blockValue = typeof payload.result === "string" ? payload.result : payload.result.blockNumber; + if (!blockValue) throw new Error("Explorer getblocknobytime missing blockNumber"); + return parseNumberish(blockValue); } throw new Error(`Explorer getblocknobytime failed: ${payload.message ?? "unknown"}`); From 20ce9990b8098e78187f5f445c61989a2abd9993 Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:10:04 +0300 Subject: [PATCH 05/25] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/src/worker.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 377a955..4e16d71 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -214,9 +214,8 @@ export default { }); try { - const store = new KVAnalyticsStore(env.ANTSEED_KV); - // const state = undefined; - const state = await store.getState(); + const analyticsStore = new KVAnalyticsStore(env.ANTSEED_KV); + const state = await analyticsStore.getState(); if (!state) { logInfo("cron.analytics.first-run"); const startDate = new Date("2026-07-02T00:00:00Z"); From dec1fb607e274b3b426ed8753c6736dd9e540ea7 Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:10:37 +0300 Subject: [PATCH 06/25] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/src/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 4e16d71..025c6d5 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -220,7 +220,7 @@ export default { logInfo("cron.analytics.first-run"); const startDate = new Date("2026-07-02T00:00:00Z"); while (startDate < new Date()) { - logInfo(`Running analytics aggregation for ${startDate.toISOString().slice(0, 10)}`); + logInfo("cron.analytics.backfill.day", { date: startDate.toISOString().slice(0, 10) }); await runAnalyticsAggregation(env, startDate); startDate.setUTCDate(startDate.getUTCDate() + 1); } From 031b4f51e0a408dc504290623a44be46f5e34944 Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:11:58 +0300 Subject: [PATCH 07/25] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/README.md b/backend/README.md index 0e1f201..e19365f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -40,8 +40,7 @@ Cloudflare Worker for GoodDollar Celo-vault credit accounting and Celo → Base - `{ "txHash": "0x..." }` - `{ "account": "0x...", "fromBlock": "0x...", "toBlock": "latest" }` -`GET /analytics` returns daily analytics (last 30 days by default). - +`GET /v1/analytics` returns daily analytics (last 30 days by default). - query: `days` (default `30`, max `365`) - response: `{ days, daily, global, lastRun }` - `daily` — one record per UTC day, newest last; the current day is always recomputed from midnight to now on each refresh and its snapshot is overwritten From fc1efb5e4194a2c9dc676ca8ba8d6fc58151f640 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:14:00 +0000 Subject: [PATCH 08/25] Add analytics route tests --- backend/test/worker.test.ts | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index e8cc5b3..fe175bc 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -91,6 +91,62 @@ test("config values exposes non-secret runtime constants", async () => { assert.equal(body.config.MIN_STREAM_BONUS_WEI, "4000000000000000000000"); }); +test("GET /v1/analytics returns analytics window with CORS headers", async () => { + const res = await worker.fetch(new Request("https://worker.test/v1/analytics?days=2"), env(), {} as ExecutionContext); + assert.equal(res.status, 200); + assert.equal(res.headers.get("access-control-allow-origin"), "*"); + const body = (await res.json()) as { days: number; daily: Array<{ date: string }>; lastRun: { currentDate: string } }; + assert.equal(body.days, 2); + assert.equal(body.daily.length, 2); + assert.equal(body.daily[1].date, body.lastRun.currentDate); +}); + +test("POST /v1/analytics/refresh returns aggregation summary", async () => { + const originalFetch = globalThis.fetch; + + try { + globalThis.fetch = (async (input: string | URL | Request) => { + const url = new URL(typeof input === "string" ? input : input instanceof URL ? input : input.url); + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ + status: "1", + message: "OK", + result: "123" + }); + } + + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ + status: "0", + message: "No records found", + result: [] + }); + } + + throw new Error(`unexpected fetch ${url.toString()}`); + }) as typeof fetch; + + const res = await worker.fetch(new Request("https://worker.test/v1/analytics/refresh", { method: "POST" }), env(), {} as ExecutionContext); + assert.equal(res.status, 200); + const body = (await res.json()) as { + ok: boolean; + currentDate: string; + celo: { scanned: number; matched: number }; + base: { scanned: number; matched: number }; + streams: { senders: number }; + }; + assert.equal(body.ok, true); + assert.match(body.currentDate, /^\d{4}-\d{2}-\d{2}$/); + assert.equal(body.celo.scanned, 0); + assert.equal(body.celo.matched, 0); + assert.equal(body.base.scanned, 0); + assert.equal(body.base.matched, 0); + assert.equal(body.streams.senders, 0); + } finally { + globalThis.fetch = originalFetch; + } +}); + test("GET /v1/accounts/:account/profile returns profile only", async () => { const testEnv = env(); const account = "0x0000000000000000000000000000000000000abc"; From bf50e9d3e7d978e95635949c849ac0034a7c7c39 Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:17:34 +0300 Subject: [PATCH 09/25] fix: log info issue --- backend/src/analytics.ts | 1 - backend/src/logging.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index f98d121..ea9ff09 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -129,7 +129,6 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi const discoveredBuyers = new Set(); const celoMetrics = await collectCeloDayMetrics(cfg, dayWindow, aggregate, discoveredBuyers); - logInfo("got celo metrics"); if (discoveredBuyers.size > 0) { await store.addBuyersToRegistry([...discoveredBuyers]); for (const buyer of discoveredBuyers) knownBuyers.add(buyer); diff --git a/backend/src/logging.ts b/backend/src/logging.ts index 1938371..c3f740b 100644 --- a/backend/src/logging.ts +++ b/backend/src/logging.ts @@ -21,6 +21,8 @@ function emit(level: LogLevel, event: string, data?: LogData): void { console.warn(event, payload); return; } + + console.log(event, payload); } export function logInfo(event: string, data?: LogData): void { From bcac1851bdf586eb10060bb473beb6f8f399e90d Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 28 Jul 2026 16:21:53 +0300 Subject: [PATCH 10/25] fix: include buyers from streams --- backend/src/analytics.ts | 24 +++++++-- backend/test/worker.test.ts | 97 +++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index ea9ff09..4f15745 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -1,4 +1,5 @@ import { Interface } from "ethers"; +import { decodeBuyerFromUserData } from "./celo-events.js"; import { Env } from "./env.js"; import { errorMessage, logError, logInfo, logWarn, redactAddress } from "./logging.js"; @@ -16,6 +17,7 @@ type ExplorerLog = { type StreamSnapshot = { sender: string; + buyerAddress?: string; flowRateWeiPerSecond: bigint; totalStreamedWei: bigint; }; @@ -129,6 +131,8 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi const discoveredBuyers = new Set(); const celoMetrics = await collectCeloDayMetrics(cfg, dayWindow, aggregate, discoveredBuyers); + + const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, now, discoveredBuyers); if (discoveredBuyers.size > 0) { await store.addBuyersToRegistry([...discoveredBuyers]); for (const buyer of discoveredBuyers) knownBuyers.add(buyer); @@ -136,8 +140,6 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi logInfo("getting base metrics...."); const baseMetrics = await collectBaseDayMetrics(cfg, dayWindow, aggregate, knownBuyers); logInfo("got base metrics...."); - - const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, now); logInfo("building dialy reocrd...."); const dailyRecord = buildDailyRecord(currentDate, aggregate, now); @@ -406,11 +408,15 @@ async function collectStreamDayMetrics( cfg: AnalyticsConfig, dayWindow: UtcDayWindow, aggregate: DailyAggregate, - now: Date + now: Date, + discoveredBuyers: Set ): Promise<{ senders: number; totalFlowRateWeiPerSecond: string }> { const snapshots = await fetchStreamSnapshots(cfg, now, dayWindow.startUnix); for (const snapshot of snapshots) { + if (snapshot.buyerAddress) { + discoveredBuyers.add(snapshot.buyerAddress); + } aggregate.gdTotalFlowRateWeiPerSecond += snapshot.flowRateWeiPerSecond; if (snapshot.totalStreamedWei > 0n) { aggregate.gdStreamedWei += snapshot.totalStreamedWei; @@ -457,7 +463,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni return []; } - const snapshotsBySender = new Map(); + const snapshotsBySender = new Map(); const pageSize = 1000; let skip = 0; const nowUnix = Math.floor(now.getTime() / 1000); @@ -475,6 +481,9 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni currentFlowRate streamedUntilUpdatedAt updatedAtTimestamp + flowUpdatedEvents(orderBy: timestamp, orderDirection: desc, first: 1) { + userData + } } } `, @@ -508,6 +517,9 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni currentFlowRate: string; streamedUntilUpdatedAt: string; updatedAtTimestamp: string; + flowUpdatedEvents?: Array<{ + userData: string; + }>; }>; }; }; @@ -518,13 +530,16 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni const currentFlowRate = BigInt(stream.currentFlowRate || "0"); const streamedUntilUpdatedAt = BigInt(stream.streamedUntilUpdatedAt || "0"); const updatedAtTimestamp = parseNumberish(stream.updatedAtTimestamp || "0"); + const buyerAddress = decodeBuyerFromUserData(stream.flowUpdatedEvents?.[0]?.userData); const totalStreamedWei = streamedWithinDay(streamedUntilUpdatedAt, currentFlowRate, updatedAtTimestamp, dayStartUnix, nowUnix); const existing = snapshotsBySender.get(sender); if (existing) { + existing.buyerAddress ??= buyerAddress; existing.flowRateWeiPerSecond += currentFlowRate; existing.totalStreamedWei += totalStreamedWei; } else { snapshotsBySender.set(sender, { + buyerAddress, flowRateWeiPerSecond: currentFlowRate, totalStreamedWei }); @@ -537,6 +552,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni return [...snapshotsBySender.entries()].map(([sender, value]) => ({ sender, + buyerAddress: value.buyerAddress, flowRateWeiPerSecond: value.flowRateWeiPerSecond, totalStreamedWei: value.totalStreamedWei })); diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index fe175bc..d6b99d8 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -652,6 +652,103 @@ test("analytics refresh excludes base usage for buyers outside known buyer regis } }); +test("analytics refresh includes base usage for buyers learned from stream userData", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const account = "0x0000000000000000000000000000000000000abc"; + const streamBuyer = "0x0000000000000000000000000000000000000def"; + const seller = "0x0000000000000000000000000000000000000fed"; + const channelId = `0x${"5".repeat(64)}`; + const now = new Date("2026-07-24T12:00:00.000Z"); + const timestamp = Math.floor(now.getTime() / 1000) - 60; + const encodedBuyerUserData = `0x${"0".repeat(24)}${streamBuyer.slice(2)}`; + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (url.searchParams.get("module") === "logs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("module") === "logs") { + const topic0 = url.searchParams.get("topic0")?.toLowerCase(); + const settledTopic = ChannelsEvents.getEvent("ChannelSettled")?.topicHash.toLowerCase(); + if (topic0 === settledTopic) { + return Response.json({ + status: "1", + message: "OK", + result: [ + encodeChannelLog( + "ChannelSettled", + [channelId, streamBuyer, seller, 3_000_000n, 500_000n, 1_000_000n, 0n, "0x"], + testEnv.ANTSEED_CHANNELS_ADDRESS!, + `0x${"c".repeat(64)}`, + 1, + 211, + timestamp + ) + ] + }); + } + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: account }, + currentFlowRate: "0", + streamedUntilUpdatedAt: "1000000000000000000", + updatedAtTimestamp: String(timestamp), + flowUpdatedEvents: [{ userData: encodedBuyerUserData }] + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await import("../src/analytics.js").then(async ({ KVAnalyticsStore, getAnalyticsWindow, runAnalyticsAggregation }) => { + await runAnalyticsAggregation(testEnv, now); + const analyticsBody = await getAnalyticsWindow(testEnv, 1, now); + const buyerRegistry = await new KVAnalyticsStore(testEnv.ANTSEED_KV).getBuyerRegistry(); + + assert.equal(analyticsBody.daily[0].aiCreditsUsedWei, "500000"); + assert.equal(analyticsBody.daily[0].uniqueCreditUsers, 1); + assert.ok(buyerRegistry.has(streamBuyer.toLowerCase())); + }); + } finally { + globalThis.fetch = originalFetch; + } +}); + test("analytics refresh finalizes previous day into persisted globals once day rolls over", { concurrency: false }, async () => { const testEnv = env({ CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", From c0edbf44559dc4801a400d4e225530074174cc43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:24:39 +0000 Subject: [PATCH 11/25] Add analytics backfill TODO --- backend/src/worker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 025c6d5..8aaebbc 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -219,6 +219,8 @@ export default { if (!state) { logInfo("cron.analytics.first-run"); const startDate = new Date("2026-07-02T00:00:00Z"); + // TODO: Persist a backfill cursor and process a bounded number of days per cron tick; + // this first-run catch-up loop can exceed scheduled execution limits as the date range grows. while (startDate < new Date()) { logInfo("cron.analytics.backfill.day", { date: startDate.toISOString().slice(0, 10) }); await runAnalyticsAggregation(env, startDate); From f2894c6ca9f25d504087fe11c6564ffd1b733094 Mon Sep 17 00:00:00 2001 From: sirpy Date: Wed, 29 Jul 2026 13:51:54 +0300 Subject: [PATCH 12/25] fix: analytics code review --- backend/src/analytics.ts | 81 ++++++++++++++++++++++++++-------- backend/src/worker.ts | 29 ++++++------ backend/test/analytics.test.ts | 42 ++++++++++++++++++ 3 files changed, 120 insertions(+), 32 deletions(-) create mode 100644 backend/test/analytics.test.ts diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 4f15745..98355f1 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -36,6 +36,7 @@ export type AnalyticsDailyRecord = { uniqueGdBuyers: number; uniqueCreditUsers: number; updatedAt: string; + missing?: boolean; }; export type AnalyticsGlobalTotals = { @@ -115,24 +116,25 @@ const ZERO_DAILY = { uniqueCreditUsers: 0 }; -export async function runAnalyticsAggregation(env: Env, now = new Date()): Promise { +export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Promise { const cfg = analyticsConfigFromEnv(env); const store = new KVAnalyticsStore(env.ANTSEED_KV); - const currentDate = dayFromDate(now); + const runDay = dayFromDate(runAt); const state = (await store.getState()) ?? { - updatedAt: now.toISOString() + updatedAt: runAt.toISOString() }; + const currentDate = resolveRunDate(state, runAt, runDay); + const windowEnd = currentDate === runDay ? runAt : new Date(`${currentDate}T23:59:59.999Z`); - const finalizedDates = await finalizeClosedDays(store, state, currentDate, now); - const isCurrentDay = dayFromDate(now) === dayFromDate(new Date()); - const dayWindow = getUtcDayWindow(now, isCurrentDay ? "until-now" : "full-day"); + const isCurrentDay = currentDate === runDay; + const dayWindow = getUtcDayWindow(windowEnd, isCurrentDay ? "until-now" : "full-day"); const aggregate = createDailyAggregate(); const knownBuyers = await store.getBuyerRegistry(); const discoveredBuyers = new Set(); const celoMetrics = await collectCeloDayMetrics(cfg, dayWindow, aggregate, discoveredBuyers); - const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, now, discoveredBuyers); + const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, windowEnd, discoveredBuyers); if (discoveredBuyers.size > 0) { await store.addBuyersToRegistry([...discoveredBuyers]); for (const buyer of discoveredBuyers) knownBuyers.add(buyer); @@ -142,14 +144,15 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi logInfo("got base metrics...."); logInfo("building dialy reocrd...."); - const dailyRecord = buildDailyRecord(currentDate, aggregate, now); + const dailyRecord = buildDailyRecord(currentDate, aggregate, windowEnd); await store.replaceDaily(currentDate, dailyRecord, [...aggregate.gdBuyers], [...aggregate.creditUsers]); const latestState = await store.getState(); await store.putState({ finalizedThroughDate: latestState?.finalizedThroughDate, - updatedAt: now.toISOString() + updatedAt: windowEnd.toISOString() }); + const finalizedDates = await finalizeClosedDays(store, state, runDay, runAt); logInfo("analytics.sync.end", { currentDate, finalizedDates, @@ -169,6 +172,18 @@ export async function runAnalyticsAggregation(env: Env, now = new Date()): Promi }; } +export function resolveRunDate(state: AnalyticsState, requestedDate: Date, runDay: string): string { + const requested = dayFromDate(requestedDate); + if (state.finalizedThroughDate) { + const next = nextDate(state.finalizedThroughDate); + if (next && next <= runDay) { + return next; + } + } + + return requested <= runDay ? requested : runDay; +} + export async function getAnalyticsWindow(env: Env, days = 30, now = new Date()): Promise { const normalizedDays = Math.max(1, Math.min(days, 365)); const store = new KVAnalyticsStore(env.ANTSEED_KV); @@ -221,7 +236,8 @@ export class KVAnalyticsStore { return { date, ...ZERO_DAILY, - updatedAt: new Date().toISOString() + updatedAt: new Date().toISOString(), + missing: true }; } @@ -295,6 +311,15 @@ async function finalizeClosedDays(store: KVAnalyticsStore, state: AnalyticsState let cursor = startDate; while (cursor <= yesterday) { const day = await store.getDaily(cursor); + //dont finalize if a day is missing + if (day.missing) { + // log warning + logWarn("analytics.finalize.skipped", { + reason: "missing_daily_record", + date: cursor + }); + break; + } global = addDailyToGlobal(global, day, now.toISOString()); finalizedDates.push(cursor); const next = nextDate(cursor); @@ -303,8 +328,9 @@ async function finalizeClosedDays(store: KVAnalyticsStore, state: AnalyticsState } await store.putGlobal(global); + const finalizedThroughDate = finalizedDates.length > 0 ? finalizedDates[finalizedDates.length - 1] : state.finalizedThroughDate; await store.putState({ - finalizedThroughDate: finalizedDates[finalizedDates.length - 1], + finalizedThroughDate, updatedAt: now.toISOString() }); return finalizedDates; @@ -479,10 +505,10 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni ) { sender { id } currentFlowRate - streamedUntilUpdatedAt updatedAtTimestamp flowUpdatedEvents(orderBy: timestamp, orderDirection: desc, first: 1) { userData + oldFlowRate } } } @@ -515,10 +541,10 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni streams?: Array<{ sender: { id: string }; currentFlowRate: string; - streamedUntilUpdatedAt: string; updatedAtTimestamp: string; flowUpdatedEvents?: Array<{ userData: string; + oldFlowRate?: string; }>; }>; }; @@ -528,10 +554,11 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni for (const stream of batch) { const sender = stream.sender.id.toLowerCase(); const currentFlowRate = BigInt(stream.currentFlowRate || "0"); - const streamedUntilUpdatedAt = BigInt(stream.streamedUntilUpdatedAt || "0"); + const previousFlowRateRaw = stream.flowUpdatedEvents?.[0]?.oldFlowRate; + const previousFlowRate = previousFlowRateRaw !== undefined ? BigInt(previousFlowRateRaw || "0") : undefined; const updatedAtTimestamp = parseNumberish(stream.updatedAtTimestamp || "0"); const buyerAddress = decodeBuyerFromUserData(stream.flowUpdatedEvents?.[0]?.userData); - const totalStreamedWei = streamedWithinDay(streamedUntilUpdatedAt, currentFlowRate, updatedAtTimestamp, dayStartUnix, nowUnix); + const totalStreamedWei = streamedWithinDay(previousFlowRate, currentFlowRate, updatedAtTimestamp, dayStartUnix, nowUnix); const existing = snapshotsBySender.get(sender); if (existing) { existing.buyerAddress ??= buyerAddress; @@ -748,14 +775,32 @@ function dedupeAccounts(accounts: string[]): string[] { return [...new Set(accounts.map((account) => account.toLowerCase()))].sort(); } -function streamedWithinDay(streamedUntilUpdatedAt: bigint, currentFlowRate: bigint, updatedAtTimestamp: number, dayStartUnix: number, nowUnix: number): bigint { +function streamedWithinDay( + previousFlowRate: bigint | undefined, + currentFlowRate: bigint, + updatedAtTimestamp: number, + dayStartUnix: number, + nowUnix: number +): bigint { + if (nowUnix <= dayStartUnix) { + return 0n; + } + let streamed = 0n; if (updatedAtTimestamp >= dayStartUnix) { - const boundedActiveSeconds = BigInt(Math.max(0, nowUnix - updatedAtTimestamp)); - return streamedUntilUpdatedAt + currentFlowRate * boundedActiveSeconds; + const boundedUpdatedAt = Math.min(updatedAtTimestamp, nowUnix); + const postUpdateSeconds = BigInt(Math.max(0, nowUnix - boundedUpdatedAt)); + streamed += currentFlowRate * postUpdateSeconds; + if (previousFlowRate !== undefined) { + const preUpdateSeconds = BigInt(Math.max(0, boundedUpdatedAt - dayStartUnix)); + streamed += previousFlowRate * preUpdateSeconds; + } + return streamed; } + if (currentFlowRate === 0n) { return 0n; } + const boundedUpdatedAt = Math.max(updatedAtTimestamp, dayStartUnix); const activeSeconds = BigInt(Math.max(0, nowUnix - boundedUpdatedAt)); return currentFlowRate * activeSeconds; diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 8aaebbc..7b6194b 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -214,22 +214,23 @@ export default { }); try { - const analyticsStore = new KVAnalyticsStore(env.ANTSEED_KV); - const state = await analyticsStore.getState(); - if (!state) { - logInfo("cron.analytics.first-run"); - const startDate = new Date("2026-07-02T00:00:00Z"); - // TODO: Persist a backfill cursor and process a bounded number of days per cron tick; - // this first-run catch-up loop can exceed scheduled execution limits as the date range grows. - while (startDate < new Date()) { - logInfo("cron.analytics.backfill.day", { date: startDate.toISOString().slice(0, 10) }); - await runAnalyticsAggregation(env, startDate); - startDate.setUTCDate(startDate.getUTCDate() + 1); - } - } else { + const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; + const maxRunsPerTick = 2; + const todayDate = new Date().toISOString().slice(0, 10); + for (let i = 0; i < maxRunsPerTick; i += 1) { const analyticsSummary = await runAnalyticsAggregation(env); - logInfo("cron.analytics.summary", analyticsSummary); + summaries.push({ + currentDate: analyticsSummary.currentDate, + finalizedDates: analyticsSummary.finalizedDates + }); + if (analyticsSummary.currentDate === todayDate) { + break; + } } + logInfo("cron.analytics.summary", { + runs: summaries.length, + summaries: JSON.stringify(summaries) + }); } catch (error) { logError("cron.analytics.failed", { message: errorMessage(error) diff --git a/backend/test/analytics.test.ts b/backend/test/analytics.test.ts new file mode 100644 index 0000000..aa9a8af --- /dev/null +++ b/backend/test/analytics.test.ts @@ -0,0 +1,42 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolveRunDate } from "../src/analytics.js"; + +test("resolveRunDate advances to finalizedThroughDate + 1 when still behind today", () => { + const state = { + updatedAt: "2026-07-24T00:00:00.000Z", + finalizedThroughDate: "2026-07-22" + }; + + const runDate = resolveRunDate(state, new Date("2026-07-24T10:00:00.000Z"), "2026-07-24"); + assert.equal(runDate, "2026-07-23"); +}); + +test("resolveRunDate uses requested day when already caught up", () => { + const state = { + updatedAt: "2026-07-24T00:00:00.000Z", + finalizedThroughDate: "2026-07-23" + }; + + const runDate = resolveRunDate(state, new Date("2026-07-24T10:00:00.000Z"), "2026-07-24"); + assert.equal(runDate, "2026-07-24"); +}); + +test("resolveRunDate clamps future requested dates to today", () => { + const state = { + updatedAt: "2026-07-24T00:00:00.000Z" + }; + + const runDate = resolveRunDate(state, new Date("2026-07-30T10:00:00.000Z"), "2026-07-24"); + assert.equal(runDate, "2026-07-24"); +}); + +test("resolveRunDate prefers cursor day over requested older date", () => { + const state = { + updatedAt: "2026-07-24T00:00:00.000Z", + finalizedThroughDate: "2026-07-22" + }; + + const runDate = resolveRunDate(state, new Date("2026-07-20T10:00:00.000Z"), "2026-07-24"); + assert.equal(runDate, "2026-07-23"); +}); From 5d28cd1fb6603929e2962b16c0f268c30f496097 Mon Sep 17 00:00:00 2001 From: sirpy Date: Wed, 29 Jul 2026 16:06:11 +0300 Subject: [PATCH 13/25] fix: analytics test --- backend/test/analytics.test.ts | 164 ++++++++++++++++++++++++++++++++- backend/test/worker.test.ts | 6 +- 2 files changed, 166 insertions(+), 4 deletions(-) diff --git a/backend/test/analytics.test.ts b/backend/test/analytics.test.ts index aa9a8af..a766939 100644 --- a/backend/test/analytics.test.ts +++ b/backend/test/analytics.test.ts @@ -1,6 +1,28 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { resolveRunDate } from "../src/analytics.js"; +import { getAnalyticsWindow, resolveRunDate, runAnalyticsAggregation } from "../src/analytics.js"; +import { Env } from "../src/env.js"; + +class MemoryKV { + private data = new Map(); + + async get(key: string, type?: "text" | "json") { + const raw = this.data.get(key) ?? null; + if (type === "json") return raw ? JSON.parse(raw) : null; + return raw; + } + + async put(key: string, value: string) { + this.data.set(key, value); + } +} + +function env(overrides: Partial = {}): Env { + return { + ANTSEED_KV: new MemoryKV() as never, + ...overrides + } as Env; +} test("resolveRunDate advances to finalizedThroughDate + 1 when still behind today", () => { const state = { @@ -40,3 +62,143 @@ test("resolveRunDate prefers cursor day over requested older date", () => { const runDate = resolveRunDate(state, new Date("2026-07-20T10:00:00.000Z"), "2026-07-24"); assert.equal(runDate, "2026-07-23"); }); + +test("runAnalyticsAggregation calculates streamed G$ across a mid-day flow update", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const now = new Date("2026-07-24T12:00:00.000Z"); + const dayStartUnix = Math.floor(new Date("2026-07-24T00:00:00.000Z").getTime() / 1000); + const updatedAtTimestamp = dayStartUnix + 6 * 60 * 60; + const expectedStreamed = (2n * BigInt(6 * 60 * 60) + 4n * BigInt(6 * 60 * 60)).toString(); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: "0x0000000000000000000000000000000000000abc" }, + currentFlowRate: "4", + updatedAtTimestamp: String(updatedAtTimestamp), + flowUpdatedEvents: [{ userData: "0x", oldFlowRate: "2" }] + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await runAnalyticsAggregation(testEnv, now); + const analytics = await getAnalyticsWindow(testEnv, 1, now); + assert.equal(analytics.daily[0].gdStreamedWei, expectedStreamed); + assert.equal(analytics.global.gdStreamedWei, expectedStreamed); + assert.equal(analytics.daily[0].uniqueGdBuyers, 1); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("runAnalyticsAggregation calculates streamed G$ for streams active since before day start", { concurrency: false }, async () => { + const testEnv = env({ + CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", + CELO_GD_SUPERTOKEN_ADDRESS: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + CELO_BLOCKSCOUT_API_URL: "https://celo.blockscout.test/api", + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api", + ANTSEED_CHANNELS_ADDRESS: "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d", + SUPERFLUID_SUBGRAPH_URL: "https://superfluid.test/subgraph" + }); + + const now = new Date("2026-07-24T12:00:00.000Z"); + const dayStartUnix = Math.floor(new Date("2026-07-24T00:00:00.000Z").getTime() / 1000); + const updatedAtTimestamp = dayStartUnix - 2 * 60 * 60; + const expectedStreamed = (3n * BigInt(12 * 60 * 60)).toString(); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request, init?: RequestInit) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + + if (url.host === "celo.blockscout.test") { + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "100" }); + } + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + + if (url.host === "superfluid.test") { + const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; + if (body.variables.skip > 0) { + return Response.json({ data: { streams: [] } }); + } + return Response.json({ + data: { + streams: [ + { + sender: { id: "0x0000000000000000000000000000000000000abc" }, + currentFlowRate: "3", + updatedAtTimestamp: String(updatedAtTimestamp), + flowUpdatedEvents: [{ userData: "0x" }] + } + ] + } + }); + } + + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + await runAnalyticsAggregation(testEnv, now); + const analytics = await getAnalyticsWindow(testEnv, 1, now); + assert.equal(analytics.daily[0].gdStreamedWei, expectedStreamed); + assert.equal(analytics.global.gdStreamedWei, expectedStreamed); + assert.equal(analytics.daily[0].uniqueGdBuyers, 1); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index d6b99d8..71dcf8c 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -520,12 +520,12 @@ test("analytics refresh overwrites current day and query adds current day to per assert.equal(analyticsBody.daily.length, 1); assert.equal(analyticsBody.daily[0].date, date); assert.equal(analyticsBody.daily[0].gdOneTimeDepositsWei, "5000000000000000000"); - assert.equal(analyticsBody.daily[0].gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.daily[0].gdStreamedWei, "0"); assert.equal(analyticsBody.daily[0].aiCreditsUsedWei, "500000"); assert.equal(analyticsBody.daily[0].uniqueGdBuyers, 1); assert.equal(analyticsBody.daily[0].uniqueCreditUsers, 1); assert.equal(analyticsBody.global.gdOneTimeDepositsWei, "5000000000000000000"); - assert.equal(analyticsBody.global.gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.global.gdStreamedWei, "0"); assert.equal(analyticsBody.global.aiCreditsUsedWei, "500000"); } finally { globalThis.fetch = originalFetch; @@ -855,7 +855,7 @@ test("analytics refresh finalizes previous day into persisted globals once day r const analyticsBody = await import("../src/analytics.js").then(async ({ getAnalyticsWindow }) => getAnalyticsWindow(testEnv, 2, secondNow)); assert.equal(analyticsBody.global.gdOneTimeDepositsWei, "4000000000000000000"); - assert.equal(analyticsBody.global.gdStreamedWei, "1000000000000000000"); + assert.equal(analyticsBody.global.gdStreamedWei, "0"); assert.equal(analyticsBody.global.aiCreditsUsedWei, "1000000"); } finally { globalThis.fetch = originalFetch; From 5bbe3337c05e07f16fc992a25f7a9aba4d6c801c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:49:35 +0000 Subject: [PATCH 14/25] Add hourly throttle for analytics refresh endpoint --- backend/README.md | 2 +- backend/src/worker.ts | 25 +++++++++++++++++++++++ backend/test/worker.test.ts | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index e19365f..c4f42eb 100644 --- a/backend/README.md +++ b/backend/README.md @@ -48,7 +48,7 @@ Cloudflare Worker for GoodDollar Celo-vault credit accounting and Celo → Base - `lastRun.finalizedThroughDate` — the most recent closed day whose values have been merged into persisted globals - Base usage (`aiCreditsUsedWei`, `uniqueCreditUsers`) is filtered to known buyers only; known buyers are learned from Celo vault `buyer` fields. -`POST /v1/analytics/refresh` recomputes the current UTC day from midnight to now, overwrites the day snapshot, finalizes any closed days into persisted globals, and returns a summary. +`POST /v1/analytics/refresh` recomputes the current UTC day from midnight to now, overwrites the day snapshot, finalizes any closed days into persisted globals, and returns a summary. This endpoint is rate-limited to one invocation per hour. ## Setup diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 7b6194b..37d8077 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -7,6 +7,9 @@ import { GdCreditEntry } from "./types.js"; import { errorMessage, logError, logInfo, logWarn, redactAddress, redactHash } from "./logging.js"; import { getAnalyticsWindow, runAnalyticsAggregation, KVAnalyticsStore } from "./analytics.js"; +const ANALYTICS_REFRESH_COOLDOWN_MS = 60 * 60 * 1000; +const ANALYTICS_REFRESH_LAST_RUN_KEY = "analytics:refresh:last-run-at"; + const CeloEventsRecordSchema = z .object({ txHash: z @@ -216,6 +219,7 @@ export default { try { const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; const maxRunsPerTick = 2; + // TODO: replace bounded loop with persisted backfill cursor when we need broader historical catch-up. const todayDate = new Date().toISOString().slice(0, 10); for (let i = 0; i < maxRunsPerTick; i += 1) { const analyticsSummary = await runAnalyticsAggregation(env); @@ -316,6 +320,19 @@ async function route(request: Request, env: Env, _ctx: ExecutionContext): Promis } if (request.method === "POST" && url.pathname === "/v1/analytics/refresh") { + const lastRefreshAt = await readAnalyticsRefreshTimestamp(env.ANTSEED_KV); + const now = Date.now(); + if (lastRefreshAt !== undefined && now - lastRefreshAt < ANALYTICS_REFRESH_COOLDOWN_MS) { + const retryAfterSeconds = Math.ceil((ANALYTICS_REFRESH_COOLDOWN_MS - (now - lastRefreshAt)) / 1000); + return json( + { + error: "analytics refresh is rate-limited", + retryAfterSeconds + }, + 429 + ); + } + await env.ANTSEED_KV.put(ANALYTICS_REFRESH_LAST_RUN_KEY, String(now)); const summary = await runAnalyticsAggregation(env); return json(summary); } @@ -697,6 +714,14 @@ function createStreamFundingId(account: string, date: Date): string { return `stream:${day}:${account.toLowerCase()}`; } +async function readAnalyticsRefreshTimestamp(kv: Pick): Promise { + const raw = await kv.get(ANALYTICS_REFRESH_LAST_RUN_KEY); + if (!raw) return undefined; + const parsed = Number(raw); + if (!Number.isFinite(parsed) || parsed < 0) return undefined; + return parsed; +} + async function fundCredit(entry: GdCreditEntry, store: KVCreditStore, antseedFundingVault: AntSeedFundingVaultClient): Promise<{ [key: string]: unknown }> { if (entry.fundingStatus === "funded") { throw new Error(`cannot fund credit with status ${entry.fundingStatus}`); diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index 71dcf8c..0613f45 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -147,6 +147,46 @@ test("POST /v1/analytics/refresh returns aggregation summary", async () => { } }); +test("POST /v1/analytics/refresh is rate-limited to one call per hour", async () => { + const originalFetch = globalThis.fetch; + + try { + globalThis.fetch = (async (input: string | URL | Request) => { + const url = new URL(typeof input === "string" ? input : input instanceof URL ? input : input.url); + if (url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ + status: "1", + message: "OK", + result: "123" + }); + } + + if (url.searchParams.get("action") === "getLogs") { + return Response.json({ + status: "0", + message: "No records found", + result: [] + }); + } + + throw new Error(`unexpected fetch ${url.toString()}`); + }) as typeof fetch; + + const testEnv = env(); + const first = await worker.fetch(new Request("https://worker.test/v1/analytics/refresh", { method: "POST" }), testEnv, {} as ExecutionContext); + assert.equal(first.status, 200); + + const second = await worker.fetch(new Request("https://worker.test/v1/analytics/refresh", { method: "POST" }), testEnv, {} as ExecutionContext); + assert.equal(second.status, 429); + const body = (await second.json()) as { error: string; retryAfterSeconds: number }; + assert.equal(body.error, "analytics refresh is rate-limited"); + assert.equal(body.retryAfterSeconds > 0, true); + assert.equal(body.retryAfterSeconds <= 3600, true); + } finally { + globalThis.fetch = originalFetch; + } +}); + test("GET /v1/accounts/:account/profile returns profile only", async () => { const testEnv = env(); const account = "0x0000000000000000000000000000000000000abc"; From da8eef233c75df0cc7c52a3cfab5a38b3adf562b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:13:40 +0000 Subject: [PATCH 15/25] Seed 30-day analytics cron backfill cursor --- backend/src/worker.ts | 37 +++++++++++++++++++++++++++++++--- backend/test/worker.test.ts | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 37d8077..7bdf47a 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -9,6 +9,8 @@ import { getAnalyticsWindow, runAnalyticsAggregation, KVAnalyticsStore } from ". const ANALYTICS_REFRESH_COOLDOWN_MS = 60 * 60 * 1000; const ANALYTICS_REFRESH_LAST_RUN_KEY = "analytics:refresh:last-run-at"; +const ANALYTICS_CRON_BACKFILL_DAYS = 30; +const ANALYTICS_CRON_BACKFILL_CURSOR_KEY = "analytics:cron:backfill-cursor"; const CeloEventsRecordSchema = z .object({ @@ -219,17 +221,27 @@ export default { try { const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; const maxRunsPerTick = 2; + const runAt = new Date(); + const todayDate = runAt.toISOString().slice(0, 10); + let cursorDate = await readAnalyticsBackfillCursorDate(env.ANTSEED_KV); + if (!cursorDate) { + cursorDate = dateDaysAgo(runAt, ANALYTICS_CRON_BACKFILL_DAYS); + await env.ANTSEED_KV.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); + } // TODO: replace bounded loop with persisted backfill cursor when we need broader historical catch-up. - const todayDate = new Date().toISOString().slice(0, 10); for (let i = 0; i < maxRunsPerTick; i += 1) { - const analyticsSummary = await runAnalyticsAggregation(env); + const aggregationRunAt = cursorDate === todayDate ? runAt : new Date(`${cursorDate}T23:59:59.999Z`); + const analyticsSummary = await runAnalyticsAggregation(env, aggregationRunAt); summaries.push({ currentDate: analyticsSummary.currentDate, finalizedDates: analyticsSummary.finalizedDates }); - if (analyticsSummary.currentDate === todayDate) { + if (cursorDate === todayDate) { break; } + const nextCursorDate = nextUtcDate(cursorDate); + cursorDate = nextCursorDate && nextCursorDate <= todayDate ? nextCursorDate : todayDate; + await env.ANTSEED_KV.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); } logInfo("cron.analytics.summary", { runs: summaries.length, @@ -722,6 +734,25 @@ async function readAnalyticsRefreshTimestamp(kv: Pick): Prom return parsed; } +async function readAnalyticsBackfillCursorDate(kv: Pick): Promise { + const raw = await kv.get(ANALYTICS_CRON_BACKFILL_CURSOR_KEY); + if (!raw || !/^\d{4}-\d{2}-\d{2}$/.test(raw)) return undefined; + return raw; +} + +function dateDaysAgo(date: Date, days: number): string { + const shifted = new Date(date.getTime()); + shifted.setUTCDate(shifted.getUTCDate() - days); + return shifted.toISOString().slice(0, 10); +} + +function nextUtcDate(date: string): string | undefined { + const parsed = new Date(`${date}T00:00:00.000Z`); + if (Number.isNaN(parsed.getTime())) return undefined; + parsed.setUTCDate(parsed.getUTCDate() + 1); + return parsed.toISOString().slice(0, 10); +} + async function fundCredit(entry: GdCreditEntry, store: KVCreditStore, antseedFundingVault: AntSeedFundingVaultClient): Promise<{ [key: string]: unknown }> { if (entry.fundingStatus === "funded") { throw new Error(`cannot fund credit with status ${entry.fundingStatus}`); diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index 0613f45..20faa4a 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -902,6 +902,46 @@ test("analytics refresh finalizes previous day into persisted globals once day r } }); +test("scheduled analytics seeds a 30-day backfill cursor on fresh KV", { concurrency: false }, async () => { + const testEnv = env({ + BASE_BLOCKSCOUT_API_URL: "https://base.blockscout.test/api" + }); + const now = new Date(); + const expectedFirstDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const expectedSecondDate = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const expectedThirdDate = new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async (urlInput: string | URL | Request) => { + const url = typeof urlInput === "string" ? new URL(urlInput) : urlInput instanceof URL ? urlInput : new URL(urlInput.url); + if (url.host === "base.blockscout.test") { + if (url.searchParams.get("module") === "block" && url.searchParams.get("action") === "getblocknobytime") { + return Response.json({ status: "1", message: "OK", result: "200" }); + } + if (url.searchParams.get("module") === "logs" && url.searchParams.get("action") === "getLogs") { + return Response.json({ status: "0", message: "No records found", result: "No records found" }); + } + } + throw new Error(`unexpected fetch url: ${url.toString()}`); + }) as typeof fetch; + + const event = { scheduledTime: now.getTime(), cron: "0 */6 * * *" } as unknown as ScheduledEvent; + const ctx = { waitUntil: () => undefined } as unknown as ExecutionContext; + await worker.scheduled(event, testEnv, ctx); + + const firstDaily = await testEnv.ANTSEED_KV.get(`analytics:daily:${expectedFirstDate}`, "json"); + const secondDaily = await testEnv.ANTSEED_KV.get(`analytics:daily:${expectedSecondDate}`, "json"); + const cursor = await testEnv.ANTSEED_KV.get("analytics:cron:backfill-cursor"); + + assert.notEqual(firstDaily, null); + assert.notEqual(secondDaily, null); + assert.equal(cursor, expectedThirdDate); + } finally { + globalThis.fetch = originalFetch; + } +}); + test("analytics refresh collects logs beyond the explorer 1000-result cap", { concurrency: false }, async () => { const testEnv = env({ CELO_VAULT_ADDRESS: "0x4Dd0136b9aabD5823cf0F65d89e8fB882C660885", From eef3608aa582235e3935833ac52a9049fdeeb989 Mon Sep 17 00:00:00 2001 From: sirpy Date: Mon, 3 Aug 2026 12:43:23 +0300 Subject: [PATCH 16/25] fix: stream periods --- AGENTS.md | 2 + backend/src/analytics.ts | 97 +++++++++++++++------------------------- 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a1e04f6..c2a043e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,6 +143,8 @@ If real deployed backend URLs are not provided in an issue, use Storybook/fixtur - Keep `fundingStatus` lifecycle (`pending` → `funded` / `failed`) on `GdCreditEntry` correct. - Include tests for credit-bonus calculations, KV persistence, and Worker routes when modifying those areas. - Update docs when changing user-visible flows, payment boundaries, or API contracts. +- Add function/methods documentation for new methods or where missing +- Add inline comments where complicated or long code ### Ask first diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 98355f1..84eb73f 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -494,28 +494,33 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni let skip = 0; const nowUnix = Math.floor(now.getTime() / 1000); + // get streams that are either active or stopped within the day window while (true) { const body = { query: ` - query StreamsPage($receiver: String!, $token: String!, $first: Int!, $skip: Int!) { - streams( - where: { receiver: $receiver, token: $token } + query StreamPeriodsPage($receiver: String!, $token: String!, $daysago: BigInt!, $first: Int!, $skip: Int!) { + streamPeriods( + where: { + or: [ + { receiver: $receiver, token: $token, stoppedAtTimestamp: null } + { receiver: $receiver, token: $token, stoppedAtTimestamp_gt: $daysago } + ] + } first: $first skip: $skip ) { sender { id } - currentFlowRate - updatedAtTimestamp - flowUpdatedEvents(orderBy: timestamp, orderDirection: desc, first: 1) { - userData - oldFlowRate - } + flowRate + startedAtTimestamp + stoppedAtTimestamp + userData } } `, variables: { receiver: cfg.celoStreamReceiverAddress, token: cfg.celoSuperTokenAddress, + daysago: String(dayStartUnix), first: pageSize, skip } @@ -538,37 +543,40 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni const json = (await response.json()) as { data?: { - streams?: Array<{ + streamPeriods?: Array<{ sender: { id: string }; - currentFlowRate: string; - updatedAtTimestamp: string; - flowUpdatedEvents?: Array<{ - userData: string; - oldFlowRate?: string; - }>; + flowRate: string; + startedAtTimestamp: string; + stoppedAtTimestamp: string | null; + userData: string; }>; }; }; - const batch = json.data?.streams ?? []; - for (const stream of batch) { - const sender = stream.sender.id.toLowerCase(); - const currentFlowRate = BigInt(stream.currentFlowRate || "0"); - const previousFlowRateRaw = stream.flowUpdatedEvents?.[0]?.oldFlowRate; - const previousFlowRate = previousFlowRateRaw !== undefined ? BigInt(previousFlowRateRaw || "0") : undefined; - const updatedAtTimestamp = parseNumberish(stream.updatedAtTimestamp || "0"); - const buyerAddress = decodeBuyerFromUserData(stream.flowUpdatedEvents?.[0]?.userData); - const totalStreamedWei = streamedWithinDay(previousFlowRate, currentFlowRate, updatedAtTimestamp, dayStartUnix, nowUnix); + const batch = json.data?.streamPeriods ?? []; + for (const period of batch) { + const sender = period.sender.id.toLowerCase(); + const flowRate = BigInt(period.flowRate || "0"); + const startedAt = parseNumberish(period.startedAtTimestamp || "0"); + const stoppedAt = period.stoppedAtTimestamp ? parseNumberish(period.stoppedAtTimestamp) : null; + const buyerAddress = decodeBuyerFromUserData(period.userData); + + // intersect period with the day window to get seconds streamed today + const effectiveStart = Math.max(startedAt, dayStartUnix); + const effectiveEnd = Math.min(stoppedAt ?? nowUnix, nowUnix); + const activeSeconds = BigInt(Math.max(0, effectiveEnd - effectiveStart)); + const streamedWei = flowRate * activeSeconds; + const existing = snapshotsBySender.get(sender); if (existing) { existing.buyerAddress ??= buyerAddress; - existing.flowRateWeiPerSecond += currentFlowRate; - existing.totalStreamedWei += totalStreamedWei; + existing.totalStreamedWei += streamedWei; + if (stoppedAt === null) existing.flowRateWeiPerSecond += flowRate; } else { snapshotsBySender.set(sender, { buyerAddress, - flowRateWeiPerSecond: currentFlowRate, - totalStreamedWei + flowRateWeiPerSecond: stoppedAt === null ? flowRate : 0n, + totalStreamedWei: streamedWei }); } } @@ -775,37 +783,6 @@ function dedupeAccounts(accounts: string[]): string[] { return [...new Set(accounts.map((account) => account.toLowerCase()))].sort(); } -function streamedWithinDay( - previousFlowRate: bigint | undefined, - currentFlowRate: bigint, - updatedAtTimestamp: number, - dayStartUnix: number, - nowUnix: number -): bigint { - if (nowUnix <= dayStartUnix) { - return 0n; - } - let streamed = 0n; - if (updatedAtTimestamp >= dayStartUnix) { - const boundedUpdatedAt = Math.min(updatedAtTimestamp, nowUnix); - const postUpdateSeconds = BigInt(Math.max(0, nowUnix - boundedUpdatedAt)); - streamed += currentFlowRate * postUpdateSeconds; - if (previousFlowRate !== undefined) { - const preUpdateSeconds = BigInt(Math.max(0, boundedUpdatedAt - dayStartUnix)); - streamed += previousFlowRate * preUpdateSeconds; - } - return streamed; - } - - if (currentFlowRate === 0n) { - return 0n; - } - - const boundedUpdatedAt = Math.max(updatedAtTimestamp, dayStartUnix); - const activeSeconds = BigInt(Math.max(0, nowUnix - boundedUpdatedAt)); - return currentFlowRate * activeSeconds; -} - function getTopicHash(iface: Interface, eventName: string): string { const event = iface.getEvent(eventName); if (!event) throw new Error(`event not found: ${eventName}`); From 22d47751fb8aceba3d548a630bb6e4ea1cedcfda Mon Sep 17 00:00:00 2001 From: sirpy Date: Mon, 3 Aug 2026 12:45:46 +0300 Subject: [PATCH 17/25] fix: backfill --- backend/src/worker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 7bdf47a..49ab68b 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -220,11 +220,12 @@ export default { try { const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; - const maxRunsPerTick = 2; + let maxRunsPerTick = 2; const runAt = new Date(); const todayDate = runAt.toISOString().slice(0, 10); let cursorDate = await readAnalyticsBackfillCursorDate(env.ANTSEED_KV); if (!cursorDate) { + maxRunsPerTick = ANALYTICS_CRON_BACKFILL_DAYS; cursorDate = dateDaysAgo(runAt, ANALYTICS_CRON_BACKFILL_DAYS); await env.ANTSEED_KV.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); } From 1a191dbf3546ad06cee21fe55a87fddce9e3889c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:50:08 +0000 Subject: [PATCH 18/25] test: align analytics fixtures with streamPeriods backfill cursor --- backend/test/analytics.test.ts | 29 +++++++++++++++++++---------- backend/test/worker.test.ts | 16 ++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/backend/test/analytics.test.ts b/backend/test/analytics.test.ts index a766939..182d8b0 100644 --- a/backend/test/analytics.test.ts +++ b/backend/test/analytics.test.ts @@ -104,16 +104,24 @@ test("runAnalyticsAggregation calculates streamed G$ across a mid-day flow updat if (url.host === "superfluid.test") { const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; if (body.variables.skip > 0) { - return Response.json({ data: { streams: [] } }); + return Response.json({ data: { streamPeriods: [] } }); } return Response.json({ data: { - streams: [ + streamPeriods: [ { sender: { id: "0x0000000000000000000000000000000000000abc" }, - currentFlowRate: "4", - updatedAtTimestamp: String(updatedAtTimestamp), - flowUpdatedEvents: [{ userData: "0x", oldFlowRate: "2" }] + flowRate: "2", + startedAtTimestamp: String(dayStartUnix), + stoppedAtTimestamp: String(updatedAtTimestamp), + userData: "0x" + }, + { + sender: { id: "0x0000000000000000000000000000000000000abc" }, + flowRate: "4", + startedAtTimestamp: String(updatedAtTimestamp), + stoppedAtTimestamp: null, + userData: "0x" } ] } @@ -174,16 +182,17 @@ test("runAnalyticsAggregation calculates streamed G$ for streams active since be if (url.host === "superfluid.test") { const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; if (body.variables.skip > 0) { - return Response.json({ data: { streams: [] } }); + return Response.json({ data: { streamPeriods: [] } }); } return Response.json({ data: { - streams: [ + streamPeriods: [ { sender: { id: "0x0000000000000000000000000000000000000abc" }, - currentFlowRate: "3", - updatedAtTimestamp: String(updatedAtTimestamp), - flowUpdatedEvents: [{ userData: "0x" }] + flowRate: "3", + startedAtTimestamp: String(updatedAtTimestamp), + stoppedAtTimestamp: null, + userData: "0x" } ] } diff --git a/backend/test/worker.test.ts b/backend/test/worker.test.ts index 20faa4a..72d6504 100644 --- a/backend/test/worker.test.ts +++ b/backend/test/worker.test.ts @@ -755,17 +755,17 @@ test("analytics refresh includes base usage for buyers learned from stream userD if (url.host === "superfluid.test") { const body = JSON.parse(String(init?.body)) as { variables: { skip: number } }; if (body.variables.skip > 0) { - return Response.json({ data: { streams: [] } }); + return Response.json({ data: { streamPeriods: [] } }); } return Response.json({ data: { - streams: [ + streamPeriods: [ { sender: { id: account }, - currentFlowRate: "0", - streamedUntilUpdatedAt: "1000000000000000000", - updatedAtTimestamp: String(timestamp), - flowUpdatedEvents: [{ userData: encodedBuyerUserData }] + flowRate: "0", + startedAtTimestamp: String(timestamp), + stoppedAtTimestamp: null, + userData: encodedBuyerUserData } ] } @@ -909,7 +909,7 @@ test("scheduled analytics seeds a 30-day backfill cursor on fresh KV", { concurr const now = new Date(); const expectedFirstDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const expectedSecondDate = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); - const expectedThirdDate = new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const expectedCursorDate = now.toISOString().slice(0, 10); const originalFetch = globalThis.fetch; try { @@ -936,7 +936,7 @@ test("scheduled analytics seeds a 30-day backfill cursor on fresh KV", { concurr assert.notEqual(firstDaily, null); assert.notEqual(secondDaily, null); - assert.equal(cursor, expectedThirdDate); + assert.equal(cursor, expectedCursorDate); } finally { globalThis.fetch = originalFetch; } From 73ed113bc0bec8720870396b600fdeba2b94f1dc Mon Sep 17 00:00:00 2001 From: sirpy Date: Tue, 4 Aug 2026 15:29:43 +0300 Subject: [PATCH 19/25] fix: throw subgraph error --- backend/src/analytics.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 84eb73f..1bd6b9a 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -542,6 +542,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni } const json = (await response.json()) as { + errors?: Array; data?: { streamPeriods?: Array<{ sender: { id: string }; @@ -553,6 +554,15 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni }; }; + // if error field is present, log and throw + if (!json.data || json.errors) { + logError("analytics.streams.subgraph_error", { + url: cfg.superfluidSubgraphUrl, + body, + response: json + }); + throw new Error(`Superfluid subgraph error: ${errorMessage(json)}`); + } const batch = json.data?.streamPeriods ?? []; for (const period of batch) { const sender = period.sender.id.toLowerCase(); From f037496cae41fb95f55085c71c86cfa30fa72226 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Aug 2026 11:05:48 +0300 Subject: [PATCH 20/25] fix: stream query userdata --- backend/src/analytics.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 1bd6b9a..831775d 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -513,7 +513,9 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni flowRate startedAtTimestamp stoppedAtTimestamp - userData + stream { + userData + } } } `, @@ -549,7 +551,9 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni flowRate: string; startedAtTimestamp: string; stoppedAtTimestamp: string | null; - userData: string; + stream: { + userData: string; + }; }>; }; }; @@ -569,7 +573,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni const flowRate = BigInt(period.flowRate || "0"); const startedAt = parseNumberish(period.startedAtTimestamp || "0"); const stoppedAt = period.stoppedAtTimestamp ? parseNumberish(period.stoppedAtTimestamp) : null; - const buyerAddress = decodeBuyerFromUserData(period.userData); + const buyerAddress = decodeBuyerFromUserData(period.stream.userData); // intersect period with the day window to get seconds streamed today const effectiveStart = Math.max(startedAt, dayStartUnix); From 9823ea27e359d117b6d8b0b88e72fd77972c42cf Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Aug 2026 15:52:46 +0300 Subject: [PATCH 21/25] add: better refresh --- backend/package.json | 3 +- backend/src/worker.ts | 57 +- backend/yarn.lock | 2438 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2470 insertions(+), 28 deletions(-) create mode 100644 backend/yarn.lock diff --git a/backend/package.json b/backend/package.json index f872212..5adeab8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,5 +24,6 @@ "tsx": "^4.22.4", "typescript": "^5.7.2", "wrangler": "^4.18.0" - } + }, + "packageManager": "yarn@3.6.1+sha256.811210abb5fb5751da12ead8a9cbc0c150b07e43ac9cbedec6752d22abfd2bd6" } diff --git a/backend/src/worker.ts b/backend/src/worker.ts index faa9cbd..808d95e 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -219,31 +219,7 @@ export default { }); try { - const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; - let maxRunsPerTick = 2; - const runAt = new Date(); - const todayDate = runAt.toISOString().slice(0, 10); - let cursorDate = await readAnalyticsBackfillCursorDate(env.ANTSEED_KV); - if (!cursorDate) { - maxRunsPerTick = ANALYTICS_CRON_BACKFILL_DAYS; - cursorDate = dateDaysAgo(runAt, ANALYTICS_CRON_BACKFILL_DAYS); - await env.ANTSEED_KV.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); - } - // TODO: replace bounded loop with persisted backfill cursor when we need broader historical catch-up. - for (let i = 0; i < maxRunsPerTick; i += 1) { - const aggregationRunAt = cursorDate === todayDate ? runAt : new Date(`${cursorDate}T23:59:59.999Z`); - const analyticsSummary = await runAnalyticsAggregation(env, aggregationRunAt); - summaries.push({ - currentDate: analyticsSummary.currentDate, - finalizedDates: analyticsSummary.finalizedDates - }); - if (cursorDate === todayDate) { - break; - } - const nextCursorDate = nextUtcDate(cursorDate); - cursorDate = nextCursorDate && nextCursorDate <= todayDate ? nextCursorDate : todayDate; - await env.ANTSEED_KV.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); - } + const summaries = await backFillAnalytics(env.ANTSEED_KV, ANALYTICS_CRON_BACKFILL_DAYS); logInfo("cron.analytics.summary", { runs: summaries.length, summaries: JSON.stringify(summaries) @@ -264,6 +240,33 @@ export default { } }; +async function backFillAnalytics(env: Env, maxRunsPerTick = 2) { + const kv = env.ANTSEED_KV; + const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; + const runAt = new Date(); + const todayDate = runAt.toISOString().slice(0, 10); + let cursorDate = await readAnalyticsBackfillCursorDate(kv); + if (!cursorDate) { + cursorDate = dateDaysAgo(runAt, ANALYTICS_CRON_BACKFILL_DAYS); + await kv.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); + } + // TODO: replace bounded loop with persisted backfill cursor when we need broader historical catch-up. + for (let i = 0; i < maxRunsPerTick; i += 1) { + const aggregationRunAt = cursorDate === todayDate ? runAt : new Date(`${cursorDate}T23:59:59.999Z`); + const analyticsSummary = await runAnalyticsAggregation(env, aggregationRunAt); + summaries.push({ + currentDate: analyticsSummary.currentDate, + finalizedDates: analyticsSummary.finalizedDates + }); + if (cursorDate === todayDate) { + break; + } + const nextCursorDate = nextUtcDate(cursorDate); + cursorDate = nextCursorDate && nextCursorDate <= todayDate ? nextCursorDate : todayDate; + await kv.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); + } + return summaries; +} async function route(request: Request, env: Env, _ctx: ExecutionContext): Promise { const url = new URL(request.url); const cfg = configFromEnv(env); @@ -346,8 +349,8 @@ async function route(request: Request, env: Env, _ctx: ExecutionContext): Promis ); } await env.ANTSEED_KV.put(ANALYTICS_REFRESH_LAST_RUN_KEY, String(now)); - const summary = await runAnalyticsAggregation(env); - return json(summary); + const summaries = await backFillAnalytics(env.ANTSEED_KV); + return json(summaries); } const accountMatch = url.pathname.match(/^\/v1\/accounts\/([^/]+)\/profile$/); diff --git a/backend/yarn.lock b/backend/yarn.lock new file mode 100644 index 0000000..784c9f8 --- /dev/null +++ b/backend/yarn.lock @@ -0,0 +1,2438 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 0836f394ea256972ec19a0b5e78cb7f5bcdfd48d8a32c7478afc94dd53ae44c04d1aa2303d7f3077b4f3ac2323b1f557ab9188e8059978748fdcd83e04a80dcc + languageName: node + linkType: hard + +"@adraffy/ens-normalize@npm:1.11.1": + version: 1.11.1 + resolution: "@adraffy/ens-normalize@npm:1.11.1" + checksum: e8b17fcc730ccc45a956e1fbb09edfe42be41c291079512082e9964f8ef4287e67913183cdd02fff71d2e215340d5b98a9bbbd9be32c5d36fad4ba2c1ec33ff2 + languageName: node + linkType: hard + +"@antseed/api-adapter@npm:0.1.42": + version: 0.1.42 + resolution: "@antseed/api-adapter@npm:0.1.42" + checksum: 89ba446cf407f0857e5abea2103b1b44cc30f1c79a7207a38412164ab9fccc87f0627702a10f1bf7d40127775cc5aec4f17b2b43164cb4b4ab96ee733922de05 + languageName: node + linkType: hard + +"@antseed/node@npm:^0.2.96": + version: 0.2.106 + resolution: "@antseed/node@npm:0.2.106" + dependencies: + "@antseed/api-adapter": 0.1.42 + "@silentbot1/nat-api": ^0.4.9 + better-sqlite3: ^12.6.2 + bittorrent-dht: ^11.0.11 + ethers: ~6.16.0 + keytar: ^7.9.0 + node-datachannel: ^0.7.0 + tokenx: ^1.3.0 + dependenciesMeta: + keytar: + optional: true + checksum: bd68bf9a712598164517c3e4db0e74ee84df93d0758f0f1a13c5ed84567ff9a6f4aad01388ae904e74136570b1f63b5f9f29f8dee52df04c6f821335fca4117c + languageName: node + linkType: hard + +"@cloudflare/kv-asset-handler@npm:0.5.0": + version: 0.5.0 + resolution: "@cloudflare/kv-asset-handler@npm:0.5.0" + checksum: 9ea3d7bfdba815fb36ab7516658186c0f844c076e04368216fd1543c3355a2b528ff4fe336370d959d969dff1851139832aa38d98f891917fccba993c254f548 + languageName: node + linkType: hard + +"@cloudflare/unenv-preset@npm:2.16.1": + version: 2.16.1 + resolution: "@cloudflare/unenv-preset@npm:2.16.1" + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: ">1.20260305.0 <2.0.0-0" + peerDependenciesMeta: + workerd: + optional: true + checksum: b068c06814242950edc86e00fb3b0cd64b0d94d2f4da1d06f19a85faa5b11d697c68219a8f9ea0d75c2b90ea894ced626381aa7c3e61cde147312766fc81a6cc + languageName: node + linkType: hard + +"@cloudflare/workerd-darwin-64@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20260801.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-darwin-arm64@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20260801.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-64@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "@cloudflare/workerd-linux-64@npm:1.20260801.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-arm64@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20260801.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-windows-64@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "@cloudflare/workerd-windows-64@npm:1.20260801.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workers-types@npm:^4.20260518.0": + version: 4.20260702.1 + resolution: "@cloudflare/workers-types@npm:4.20260702.1" + checksum: 6233d5c284da49103e795cc7e57ccd0a779416aa5bcb8dfac139aa7e23a70a0f4a946a5149d0ec7bd4b6c76aaede5202bcee9b09aa5c9bc0d553f3fb9be0d6b8 + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:0.8.1": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + checksum: 5718f267085ed8edb3e7ef210137241775e607ee18b77d95aa5bd7514f47f5019aa2d82d96b3bf342ef7aa890a346fa1044532ff7cc3009e7d24fce3ce6200fa + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.11.1": + version: 1.11.3 + resolution: "@emnapi/runtime@npm:1.11.3" + dependencies: + tslib: ^2.4.0 + checksum: 844b828dee5318f3645d7eb673e59e527e552483d8caa42afe420753363c6ff2599718100a456483d589dff4f1f3cf1c66010d80b11154c899ebf68fd40fc359 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/aix-ppc64@npm:0.28.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm64@npm:0.28.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm@npm:0.28.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-x64@npm:0.28.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-arm64@npm:0.28.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-x64@npm:0.28.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-arm64@npm:0.28.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-x64@npm:0.28.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm64@npm:0.28.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm@npm:0.28.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ia32@npm:0.28.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-loong64@npm:0.28.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-mips64el@npm:0.28.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ppc64@npm:0.28.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-riscv64@npm:0.28.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-s390x@npm:0.28.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-x64@npm:0.28.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-arm64@npm:0.28.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-x64@npm:0.28.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-arm64@npm:0.28.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-x64@npm:0.28.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openharmony-arm64@npm:0.28.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/sunos-x64@npm:0.28.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-arm64@npm:0.28.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-ia32@npm:0.28.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-x64@npm:0.28.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@gooddollar/antseed-integration-worker@workspace:.": + version: 0.0.0-use.local + resolution: "@gooddollar/antseed-integration-worker@workspace:." + dependencies: + "@antseed/node": ^0.2.96 + "@cloudflare/workers-types": ^4.20260518.0 + "@types/node": ^22.10.2 + ethers: ^6.13.5 + tsx: ^4.22.4 + typescript: ^5.7.2 + wrangler: ^4.18.0 + zod: ^3.24.1 + languageName: unknown + linkType: soft + +"@img/colour@npm:^1.1.0": + version: 1.1.0 + resolution: "@img/colour@npm:1.1.0" + checksum: 1572b4f154fe5987e02e107c32f64a9f50a18cab4a2015b7e53f48317b20c58e00cc2e09467378a2d4f06a6139cabd259b1b6d224e79a3bd6b66daea70a6613d + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-darwin-arm64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-darwin-arm64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-darwin-x64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-darwin-x64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-freebsd-wasm32@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-freebsd-wasm32@npm:0.35.2" + dependencies: + "@img/sharp-wasm32": 0.35.2 + conditions: os=freebsd + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-arm@npm:1.3.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linux-x64@npm:1.3.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.3.1": + version: 1.3.1 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-arm64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-arm64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-arm@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-arm": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-ppc64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-ppc64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-riscv64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-riscv64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-s390x@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-s390x": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linux-x64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linux-x64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-linuxmusl-x64@npm:0.35.2" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.3.1 + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-wasm32@npm:0.35.2" + dependencies: + "@emnapi/runtime": ^1.11.1 + checksum: e71022251df977c81675308a4eae78ffd2fec3b3320a104e0b2839900103fdf5dc441f0f75af4f10293cc75250d0fff86dade8638ffcb96e3b2a1b65577274e5 + languageName: node + linkType: hard + +"@img/sharp-webcontainers-wasm32@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.2" + dependencies: + "@img/sharp-wasm32": 0.35.2 + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-win32-arm64@npm:0.35.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-win32-ia32@npm:0.35.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.35.2": + version: 0.35.2 + resolution: "@img/sharp-win32-x64@npm:0.35.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: ^7.0.4 + checksum: 5d36d289960e886484362d9eb6a51d1ea28baed5f5d0140bbe62b99bac52eaf06cc01c2bc0d3575977962f84f6b2c4387b043ee632216643d4787b0999465bf2 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 83b85f72c59d1c080b4cbec0fef84528963a1b5db34e4370fa4bd1e3ff64a0d80e0cee7369d11d73c704e0286fb2865b530acac7a871088fbe92b5edf1000870 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: c2e36e67971f719a8a3a85ef5a5f580622437cc723c35d03ebd0c9c0b06418700ef006f58af742791f71f6a4fc68fcfaf1f6a74ec2f9a3332860e9373459dae7 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: d89597752fd88d3f3480845691a05a44bd21faac18e2185b6f436c3b0fd0c5a859fbbd9aaa92050c4052caf325ad3e10e2e1d1b64327517471b7d51babc0ddef + languageName: node + linkType: hard + +"@napi-rs/lzma-linux-x64-gnu@npm:1.5.1": + version: 1.5.1 + resolution: "@napi-rs/lzma-linux-x64-gnu@npm:1.5.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@noble/curves@npm:1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" + dependencies: + "@noble/hashes": 1.3.2 + checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.3.2": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 + languageName: node + linkType: hard + +"@poppinss/colors@npm:^4.1.5": + version: 4.1.6 + resolution: "@poppinss/colors@npm:4.1.6" + dependencies: + kleur: ^4.1.5 + checksum: 66e69e17b0bb0279e76a08c8a2ec785e1a0957e0128ead7c196fe5d63fc42664a7de4e4b8189b20c5162ba0a9110610ce11c19f85b894a9eed01830a41ba9d2e + languageName: node + linkType: hard + +"@poppinss/dumper@npm:^0.6.4": + version: 0.6.5 + resolution: "@poppinss/dumper@npm:0.6.5" + dependencies: + "@poppinss/colors": ^4.1.5 + "@sindresorhus/is": ^7.0.2 + supports-color: ^10.0.0 + checksum: 62cb92b0586ddf9c9e2cc2824b2071c32238350a36f1fb081a75b9a3b0b1f8803864503d6f0b9517917721adacde45e40617b3a2e8321c9a8cdfec5618b27211 + languageName: node + linkType: hard + +"@poppinss/exception@npm:^1.2.2": + version: 1.2.3 + resolution: "@poppinss/exception@npm:1.2.3" + checksum: 414a536a8666b2cb6c8ae5460c107e58de4ce2ac5cae42d2a5ec432119f415bf8b6780703e8f6d34faf36b433c47f2bb821cc48f3d1d5f14ab22b0e36e4175a1 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.4" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-android-arm64@npm:4.62.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-darwin-arm64@npm:4.62.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-darwin-x64@npm:4.62.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.4" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-freebsd-x64@npm:4.62.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.4" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.4" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.4" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.4" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.4" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-openbsd-x64@npm:4.62.4" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.4" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.62.4": + version: 4.62.4 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@silentbot1/nat-api@npm:^0.4.9": + version: 0.4.9 + resolution: "@silentbot1/nat-api@npm:0.4.9" + dependencies: + chrome-dgram: ^3.0.6 + cross-fetch-ponyfill: ^1.0.3 + debug: ^4.4.0 + default-gateway: ^7.2.2 + unordered-array-remove: ^1.0.2 + xml2js: ^0.6.2 + checksum: 301282d8d9b623377f61e6f0f1dfe3a6c061852f0a82333a8e1323950d56bd0339a2e9a21d0f24d0f02ec1dcf6c308d21e57beae834c7a52b7da1464e28eb4d9 + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^7.0.2": + version: 7.2.0 + resolution: "@sindresorhus/is@npm:7.2.0" + checksum: 43d5d20b9457508d8e874695c2868167838b4b10cb888d2a961608843828277a6bffb464c9be8a54ba1d87d17d62dfd78c5ac41cc654516748f6be65ef79b8bc + languageName: node + linkType: hard + +"@speed-highlight/core@npm:^1.2.7": + version: 1.2.23 + resolution: "@speed-highlight/core@npm:1.2.23" + checksum: d5d5ab79b807ae14bc9d9903564500f2027ce963c8f543be78f2bbb398d2b8ad12c2d31c35c78b0cd623d56ba2e2f39038b0be19756c63cdcb005d0e3b284035 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.9": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 752c0afee3ec82b8e24484bf6a27dfa093bbf3de4ef1c20ed0364fb6ad2c0c7971e7504ed9a7aaff103a47e2d945ce7a17f74951743dd944782a0735f53170de + languageName: node + linkType: hard + +"@types/node@npm:22.7.5": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: ~6.19.2 + checksum: 1a8bbb504efaffcef7b8491074a428e5c0b5425b0c0ffb13e7262cb8462c275e8cc5eaf90a38d8fbf52a1eeda7c01ab3b940673c43fc2414140779c973e40ec6 + languageName: node + linkType: hard + +"@types/node@npm:^22.10.2": + version: 22.20.1 + resolution: "@types/node@npm:22.20.1" + dependencies: + undici-types: ~6.21.0 + checksum: 03cc6d9941229b13fa9ab4e477d6550e85ed3e3d5f85734c03c84e0c18346d8a7c9f22b93570bb9cb4a44ffa176279f8a7ab3cd328fbdcf5d4e6ec541ce03f21 + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 40526a57545197f1ee0a5cb369508acbd33dbb8a19967850d6d60d9bb392d4034fc56d2572ad863b6a8da810f32eb5553cd05d5af265ea829e168697d9963cde + languageName: node + linkType: hard + +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + +"aes-js@npm:4.0.0-beta.5": + version: 4.0.0-beta.5 + resolution: "aes-js@npm:4.0.0-beta.5" + checksum: cc2ea969d77df939c32057f7e361b6530aa6cb93cb10617a17a45cd164e6d761002f031ff6330af3e67e58b1f0a3a8fd0b63a720afd591a653b02f649470e15b + languageName: node + linkType: hard + +"b4a@npm:^1.3.1": + version: 1.8.1 + resolution: "b4a@npm:1.8.1" + peerDependencies: + react-native-b4a: "*" + peerDependenciesMeta: + react-native-b4a: + optional: true + checksum: a34131bba0eb004e5537f5ec0b0d74cd2c075832f65b8dc1a2666f895a34ce47eb553b47b4949bbb312cfdf47760da8cde3537c691b5133b0d6b132af8e3b16f + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + +"bencode@npm:^2.0.0": + version: 2.0.3 + resolution: "bencode@npm:2.0.3" + checksum: e0f9268825a014f7b1d7e62d76c71299eafc7aec2dd355acc1131e74f4fd62d96c75925cba796eb4e30c1a5b38d24b348d433a4b7c1e4dd413be7f7aa5e97d1a + languageName: node + linkType: hard + +"bencode@npm:^4.0.1": + version: 4.0.1 + resolution: "bencode@npm:4.0.1" + dependencies: + uint8-util: ^2.2.2 + checksum: dd0f19926fb1ba47b35f6c83ff90a08f224133955bd5ce9e9c4f047815c61fc57ac28a71e5db51d3d75f29951eac4533cd55bd42ed0c48cae50819e0287f5c71 + languageName: node + linkType: hard + +"better-sqlite3@npm:^12.6.2": + version: 12.11.1 + resolution: "better-sqlite3@npm:12.11.1" + dependencies: + bindings: ^1.5.0 + node-gyp: latest + prebuild-install: ^7.1.1 + checksum: 2bc5a7faa2fc2d4b7e0e2cdec2bb683d4df0a77b1deac512d79b93525ef7e06e4a3fcc7c511662c348d64cf9541e7dcc5b65d2f15d88a3a4aa74259f84cb8f0b + languageName: node + linkType: hard + +"bindings@npm:^1.5.0": + version: 1.5.0 + resolution: "bindings@npm:1.5.0" + dependencies: + file-uri-to-path: 1.0.0 + checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7 + languageName: node + linkType: hard + +"bittorrent-dht@npm:^11.0.11": + version: 11.0.12 + resolution: "bittorrent-dht@npm:11.0.12" + dependencies: + bencode: ^4.0.1 + debug: ^4.4.3 + k-bucket: ^5.1.0 + k-rpc: ^5.1.0 + last-one-wins: ^1.0.4 + lru: ^3.1.0 + randombytes: ^2.1.0 + record-cache: ^1.2.0 + checksum: f8d950c24031e8bd1b0e5ebb48b5b2c42f1734c59c17933289a286c0c14c10c2fc453852a0d9954b6e560894e418bb5acb94c0aa4ea586f7340914f45e272c71 + languageName: node + linkType: hard + +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + +"blake3-wasm@npm:2.1.5": + version: 2.1.5 + resolution: "blake3-wasm@npm:2.1.5" + checksum: 5088e929c722b52b9c28701c1760ab850a963692056a417b894c943030e3267f12138ae6409e79069b8d7d0401a411426147e8d812b65a49e303fa432af18871 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: e2cf8429e1c4c7b8cbd30834ac09bd61da46ce35f5c22a78e6c2f04497d6d25541b16881e30a019c6fd3154150650ccee27a308eff3e26229d788bbdeb08ab84 + languageName: node + linkType: hard + +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: fd73a4bab48b79e66903fe1cafbdc208956f41ea4f856df883d0c7277b7ab29fd33ee65f93b2ec9192fc0169238f2f8307b7735d27c155821d886b84aa97aa8d + languageName: node + linkType: hard + +"chrome-dgram@npm:^3.0.2, chrome-dgram@npm:^3.0.6": + version: 3.0.6 + resolution: "chrome-dgram@npm:3.0.6" + dependencies: + inherits: ^2.0.4 + run-series: ^1.1.9 + checksum: 7d4784a2bc4d9edc2be1522d7ca4349e40b7721118361e4e0b288d1a60adb0242ff004a5f5125c2ec0cdd69d4ac3ac22f96c6b6d33bc923defb9028a25f4c34f + languageName: node + linkType: hard + +"chrome-dns@npm:^1.0.0": + version: 1.0.1 + resolution: "chrome-dns@npm:1.0.1" + dependencies: + chrome-net: ^3.3.2 + checksum: 06d6fc3f9f95a9613556177a291383e0ecb90752ccdd0b65b78fca14aeb627ef94adb86409e4f0113ecbbbb3d0035d8e42cea0de66efcca433997bc070f29119 + languageName: node + linkType: hard + +"chrome-net@npm:^3.3.2": + version: 3.3.4 + resolution: "chrome-net@npm:3.3.4" + dependencies: + inherits: ^2.0.1 + checksum: 92aeb060b4e5110c343dec3e3172297cc3176924a811437baa5b4dde927e23ff7a5d664ec441e2939ea66923312473f5f66cd4f166cc48f73493f99d752ca33e + languageName: node + linkType: hard + +"cookie@npm:^1.0.2": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: e40c4ff817a73ec0a41413654ca9a71e6207bd6e511151bb1d307aef2903eb59771127a85474afe0e12a8982804f7b03cce0bda798b55c306aca9608b4b9acab + languageName: node + linkType: hard + +"cross-fetch-ponyfill@npm:^1.0.3": + version: 1.0.3 + resolution: "cross-fetch-ponyfill@npm:1.0.3" + dependencies: + abort-controller: ^3.0.0 + node-fetch: ^3.3.0 + checksum: 57f3ddfa04fbec62b7df064519d0e9f7ff4287b7414737f10d8603765ce2fdd820a88012774874ececcf5bb7e3eca36c6d06549b14f2d51bc621f20b06b1e6ac + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 8d306efacaf6f3f60e0224c287664093fa9185680b2d195852ba9a863f85d02dcc737094c6e512175f8ee0161f9b87c73c6826034c2422e39de7d6569cf4503b + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + +"debug@npm:^4.4.0, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 4805abd570e601acdca85b6aa3757186084a45cff9b2fa6eee1f3b173caa776b45f478b2a71a572d616d2010cea9211d0ac4a02a610e4c18ac4324bde3760834 + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: ^3.1.0 + checksum: d377cf47e02d805e283866c3f50d3d21578b779731e8c5072d6ce8c13cc31493db1c2f6784da9d1d5250822120cefa44f1deab112d5981015f2e17444b763812 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + +"default-gateway@npm:^7.2.2": + version: 7.2.2 + resolution: "default-gateway@npm:7.2.2" + dependencies: + execa: ^7.1.1 + checksum: eec8a2a338677322bcdbf339bbdede61225f6145eedd0c3d4948deadfc929dc0e04b153b33674873d36efa403f76649aaacbfc728a438ee9f538a28723515478 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 471740d52365084c4b2ae359e507b863f2b1d79b08a92835ebdf701918e08fc9cfba175b3db28483ca33b155e1311a91d69dc42c6d192b476f41a9e1f094ce6a + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: ^1.4.0 + checksum: 1e0cfa6e7f49887544e03314f9dfc56a8cb6dde910cbb445983ecc2ff426fc05946df9d75d8a21a3a64f2cecfe1bf88f773952029f46756b2ed64a24e95b1fb8 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"error-stack-parser-es@npm:^1.0.5": + version: 1.0.5 + resolution: "error-stack-parser-es@npm:1.0.5" + checksum: bd60322490c065d90f752f2952abdf196a4589aefd9509be6fd8bbb0b1b7610991522a19a933a9bd60b5021547fed921ccc5bb7e68f740244134c2a82b876f73 + languageName: node + linkType: hard + +"esbuild@npm:0.28.1, esbuild@npm:~0.28.0": + version: 0.28.1 + resolution: "esbuild@npm:0.28.1" + dependencies: + "@esbuild/aix-ppc64": 0.28.1 + "@esbuild/android-arm": 0.28.1 + "@esbuild/android-arm64": 0.28.1 + "@esbuild/android-x64": 0.28.1 + "@esbuild/darwin-arm64": 0.28.1 + "@esbuild/darwin-x64": 0.28.1 + "@esbuild/freebsd-arm64": 0.28.1 + "@esbuild/freebsd-x64": 0.28.1 + "@esbuild/linux-arm": 0.28.1 + "@esbuild/linux-arm64": 0.28.1 + "@esbuild/linux-ia32": 0.28.1 + "@esbuild/linux-loong64": 0.28.1 + "@esbuild/linux-mips64el": 0.28.1 + "@esbuild/linux-ppc64": 0.28.1 + "@esbuild/linux-riscv64": 0.28.1 + "@esbuild/linux-s390x": 0.28.1 + "@esbuild/linux-x64": 0.28.1 + "@esbuild/netbsd-arm64": 0.28.1 + "@esbuild/netbsd-x64": 0.28.1 + "@esbuild/openbsd-arm64": 0.28.1 + "@esbuild/openbsd-x64": 0.28.1 + "@esbuild/openharmony-arm64": 0.28.1 + "@esbuild/sunos-x64": 0.28.1 + "@esbuild/win32-arm64": 0.28.1 + "@esbuild/win32-ia32": 0.28.1 + "@esbuild/win32-x64": 0.28.1 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 2ada56fe421ac0720f71968c0ceb56cb534588ae2fbbe5aef2c9dbe195f0f61773ef7838ad07d48c2141d9e8b7d3de5fd39d0bb880860f2f6f77bf67671ed235 + languageName: node + linkType: hard + +"ethers@npm:^6.13.5": + version: 6.17.0 + resolution: "ethers@npm:6.17.0" + dependencies: + "@adraffy/ens-normalize": 1.11.1 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@types/node": 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.21.0 + checksum: 0ba314af2ae25622c60886caf83d825ce543262685961667d166b825c9dbfc66f905e0d7a2e3d8d4ccfa58b6ac20326e7f9a897b5580879efa758a50067ad082 + languageName: node + linkType: hard + +"ethers@npm:~6.16.0": + version: 6.16.0 + resolution: "ethers@npm:6.16.0" + dependencies: + "@adraffy/ens-normalize": 1.10.1 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@types/node": 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1 + checksum: f96c54d35aa09d6700dbbe732db160d66f2a1acd59f2820e307869be478bb5c4c3fd0f34a5d51014cbea04200e6e9776290f521795492688c8d67052bf8a1e2a + languageName: node + linkType: hard + +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + +"execa@npm:^7.1.1": + version: 7.2.0 + resolution: "execa@npm:7.2.0" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.1 + human-signals: ^4.3.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^3.0.7 + strip-final-newline: ^3.0.0 + checksum: 14fd17ba0ca8c87b277584d93b1d9fc24f2a65e5152b31d5eb159a3b814854283eaae5f51efa9525e304447e2f757c691877f7adff8fde5746aae67eb1edd1cc + languageName: node + linkType: hard + +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 471fdb70fd3d2c08a74a026973bdd4105b7832911f610ca67bbb74e39279411c1eed2f2a110c9d41c2edd89459ba58fdaba1c174beed73e7a42d773882dcff82 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: bd537daa9d3cd53887eed35efa0eab2dbb1ca408790e10e024120e7a36c6e9ae2b33710cb8381e35def01bc9c1d7eaba746f886338413e68ff6ebaee07b9a6e8 + languageName: node + linkType: hard + +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: ^1.0.0 + web-streams-polyfill: ^3.0.3 + checksum: f19bc28a2a0b9626e69fd7cf3a05798706db7f6c7548da657cbf5026a570945f5eeaedff52007ea35c8bcd3d237c58a20bf1543bc568ab2422411d762dd3d5bf + languageName: node + linkType: hard + +"file-uri-to-path@npm:1.0.0": + version: 1.0.0 + resolution: "file-uri-to-path@npm:1.0.0" + checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 + languageName: node + linkType: hard + +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: ^3.1.2 + checksum: 82a34df292afadd82b43d4a740ce387bc08541e0a534358425193017bf9fb3567875dc5f69564984b1da979979b70703aa73dee715a17b6c229752ae736dd9db + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + +"fsevents@npm:2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@2.3.3#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + +"get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad + languageName: node + linkType: hard + +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 14e448192a35c1e42efee94c9d01a10f42fe790375891a24b25261246ce9336ab9df5d274585aedd4568f7922246c2a78b8a8cd2571bfe99c693a9718e7dd0e3 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 + languageName: node + linkType: hard + +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 6f12958df3f21b6fdaf02d90896c271df00636a31e2bbea05bddf817a35c66b38a6fdac5863e2df85bd52f34958997f1f50350ff97249e1dff8452865d5235d1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e + languageName: node + linkType: hard + +"inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 + languageName: node + linkType: hard + +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 + languageName: node + linkType: hard + +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 26bf6c5480dda5161c820c5b5c751ae1e766c587b1f951ea3fcfc973bafb7831ae5b54a31a69bd670220e42e99ec154475025a468eae58ea262f813fdc8d1c62 + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 2ead327ef596042ef9c9ec5f236b316acfaedb87f4bb61b3c3d574fb2e9c8a04b67305e04733bde52c24d9622fdebd3270aadb632adfbf9cadef88fe30f479e5 + languageName: node + linkType: hard + +"k-bucket@npm:^5.0.0, k-bucket@npm:^5.1.0": + version: 5.1.0 + resolution: "k-bucket@npm:5.1.0" + dependencies: + randombytes: ^2.1.0 + checksum: e3110e469a152461796125eb736fdb047eeb528a3b4c4f81ad655ddb4d78efc85029c45d9cc3a89e068b5105dfae493e36f6b4f9ce63bcec0ce5acdefbdeacab + languageName: node + linkType: hard + +"k-rpc-socket@npm:^1.7.2": + version: 1.11.1 + resolution: "k-rpc-socket@npm:1.11.1" + dependencies: + bencode: ^2.0.0 + chrome-dgram: ^3.0.2 + chrome-dns: ^1.0.0 + chrome-net: ^3.3.2 + checksum: 2e0a2a59c949bd1a9d388ddca1916fc80c4585ccd69e5501b9e6a586f185511bac994e84310c2fdd2b29c3926b3e313f33fa103c28d5650418103c757e2a7eb7 + languageName: node + linkType: hard + +"k-rpc@npm:^5.1.0": + version: 5.1.0 + resolution: "k-rpc@npm:5.1.0" + dependencies: + k-bucket: ^5.0.0 + k-rpc-socket: ^1.7.2 + randombytes: ^2.0.5 + checksum: 5789e2f76f664609d59b44462a7499f2e1ff18ee08eca6170472f85ee1cee01790ed45f5af1b3c56a389300302125ac9648b9d5891696c1c073d3c0174f83714 + languageName: node + linkType: hard + +"keytar@npm:^7.9.0": + version: 7.9.0 + resolution: "keytar@npm:7.9.0" + dependencies: + node-addon-api: ^4.3.0 + node-gyp: latest + prebuild-install: ^7.0.1 + checksum: 4dbdd21f69e21a53032cbc949847f57338e42df763c5eec04e1b5d7142a689f95d8c3d74fb3b7dc321b5d678271d8d8d1a0dcaa919673ebc50ef8ce76f354e21 + languageName: node + linkType: hard + +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 + languageName: node + linkType: hard + +"last-one-wins@npm:^1.0.4": + version: 1.0.4 + resolution: "last-one-wins@npm:1.0.4" + checksum: 7915a87e133e2f165021841cd85986673dfed8f5a12ccfd97f7acda8311d0442c0b6928088c06e8cd1cce28665417b9d09af01d361348db2580296589437d39a + languageName: node + linkType: hard + +"lru@npm:^3.1.0": + version: 3.1.0 + resolution: "lru@npm:3.1.0" + dependencies: + inherits: ^2.0.1 + checksum: 816738fd68d806e58b2535f9b38d430a405c6e51a0f3d27620bff671417e91dce8a8c11d72deae9ac1b5b7c80aafb75f3eb2d020c11625154aa1051ce6175308 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56 + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 25739fee32c17f433626bf19f016df9036b75b3d84a3046c7d156e72ec963dd29d7fc8a302f55a3d6c5a4ff24259676b15d915aad6480815a969ff2ec0836867 + languageName: node + linkType: hard + +"miniflare@npm:5.20260801.0-alpha": + version: 5.20260801.0-alpha + resolution: "miniflare@npm:5.20260801.0-alpha" + dependencies: + "@cspotcode/source-map-support": 0.8.1 + sharp: 0.35.2 + undici: 7.28.0 + workerd: 1.20260801.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + checksum: 71039bc02c29de0f14a7cc6a9da9aa7019349d06473c134f5b4831b08f695c254c78dc7769eb1b6bc35d19705c1ab1ad409e308f1a0b1f377cd559d4393b0b7c + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.3": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 2ede17c0bf8fec499be3360fd07f0ec7666189e3907320a9b653f1530cf84af98928c5b12d80bfb75f321833bf2e97785b940540213ebdafe97a5f10327e664d + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: ^7.1.2 + checksum: a15e6f0128f514b7d41a1c68ce531155447f4669e32d279bba1c1c071ef6c2abd7e4d4579bb59ccc2ed1531346749665968fdd7be8d83eb6b6ae2fe1f3d370a7 + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"napi-build-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "napi-build-utils@npm:2.0.0" + checksum: 532121efd2dd2272595580bca48859e404bdd4ed455a72a28432ba44868c38d0e64fac3026a8f82bf8563d2a18b32eb9a1d59e601a9da4e84ba4d45b922297f5 + languageName: node + linkType: hard + +"node-abi@npm:^3.3.0": + version: 3.94.0 + resolution: "node-abi@npm:3.94.0" + dependencies: + semver: ^7.3.5 + checksum: 0d79587a609fb2e4d152b4d648b32a89b42c9963399ca62c88b0f4d91c7bb4dd1f806c5cd771bd925380561923df5e4cd6e30bbc8133b02442136ec4afebc726 + languageName: node + linkType: hard + +"node-addon-api@npm:^4.3.0": + version: 4.3.0 + resolution: "node-addon-api@npm:4.3.0" + dependencies: + node-gyp: latest + checksum: 3de396e23cc209f539c704583e8e99c148850226f6e389a641b92e8967953713228109f919765abc1f4355e801e8f41842f96210b8d61c7dcc10a477002dcf00 + languageName: node + linkType: hard + +"node-datachannel@npm:^0.7.0": + version: 0.7.0 + resolution: "node-datachannel@npm:0.7.0" + dependencies: + node-domexception: ^2.0.1 + node-gyp: latest + prebuild-install: ^7.0.1 + rollup: ^4.14.1 + checksum: 022e7326e3c4ceb2bac9a6665a8631926b99278792415e8ec9906638307ff9444902362bf6062bb29a5e3a9e0110fc23439a7c1bda11f54393258ed10611b4f8 + languageName: node + linkType: hard + +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: ee1d37dd2a4eb26a8a92cd6b64dfc29caec72bff5e1ed9aba80c294f57a31ba4895a60fd48347cf17dd6e766da0ae87d75657dfd1f384ebfa60462c2283f5c7f + languageName: node + linkType: hard + +"node-domexception@npm:^2.0.1": + version: 2.0.2 + resolution: "node-domexception@npm:2.0.2" + checksum: 46b9a3b8a7101442d5dd65ac7540adbfced9083fd9b3f229c5b671378e7e9c36b99142d11dc5fcf729d59588d5eb854afd74e885570882e17331e49512166875 + languageName: node + linkType: hard + +"node-fetch@npm:^3.3.0": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: ^4.0.0 + fetch-blob: ^3.1.4 + formdata-polyfill: ^4.0.10 + checksum: 06a04095a2ddf05b0830a0d5302699704d59bda3102894ea64c7b9d4c865ecdff2d90fd042df7f5bc40337266961cb6183dcc808ea4f3000d024f422b462da92 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.0.1 + resolution: "node-gyp@npm:13.0.1" + dependencies: + env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 + graceful-fs: ^4.2.6 + nopt: ^10.0.0 + proc-log: ^7.0.0 + semver: ^7.3.5 + tar: ^7.5.4 + tinyglobby: ^0.2.12 + undici: ^8.4.1 + which: ^7.0.0 + bin: + node-gyp: bin/node-gyp.js + checksum: 9d4e0eeaa415ca33a85278ecf2bdb61a7c32649e9423ec7743912b97e58169606ef44504f7d3a746cd9179f50c8d37d5bb70ae76fbc4803122f7747e819453f6 + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: ^5.0.0 + bin: + nopt: bin/nopt.js + checksum: c2903b9171a3293b731189ace4eaeacc2ed8191bb8f8f74ebfb61babc0de2ff158d97a215fd561070ed0dac567f3b8741955f81a534009e57eb37fdc419d5d4e + languageName: node + linkType: hard + +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: ^4.0.0 + checksum: ae8e7a89da9594fb9c308f6555c73f618152340dcaae423e5fb3620026fefbec463618a8b761920382d666fa7a2d8d240b6fe320e8a6cdd54dc3687e2b659d25 + languageName: node + linkType: hard + +"once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: ^4.0.0 + checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 + languageName: node + linkType: hard + +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + +"path-to-regexp@npm:6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: eca78602e6434a1b6799d511d375ec044e8d7e28f5a48aa5c28d57d8152fb52f3fc62fb1cfc5dfa2198e1f041c2a82ed14043d75740a2fe60e91b5089a153250 + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 0602bdd4acb54d91044e0c56f1fb63467ae7d44ab3afea1f797947b0eb2b4d1d91cf0d58d065fdb0a8ab0c4acbbd8d3a5b424983eaf10dd5285d37a16f6e3ee9 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4": + version: 4.0.5 + resolution: "picomatch@npm:4.0.5" + checksum: 44f305aa44f33c15d304f2e887409961d1c14eebd465b5553d8aefddcfe7b5e40d208148adef8c21a21183dda2fdba1e83c8a56918e3b5e5d3db6bcaba5b7e12 + languageName: node + linkType: hard + +"prebuild-install@npm:^7.0.1, prebuild-install@npm:^7.1.1": + version: 7.1.3 + resolution: "prebuild-install@npm:7.1.3" + dependencies: + detect-libc: ^2.0.0 + expand-template: ^2.0.3 + github-from-package: 0.0.0 + minimist: ^1.2.3 + mkdirp-classic: ^0.5.3 + napi-build-utils: ^2.0.0 + node-abi: ^3.3.0 + pump: ^3.0.0 + rc: ^1.2.7 + simple-get: ^4.0.0 + tar-fs: ^2.0.0 + tunnel-agent: ^0.6.0 + bin: + prebuild-install: bin.js + checksum: 300740ca415e9ddbf2bd363f1a6d2673cc11dd0665c5ec431bbb5bf024c2f13c56791fb939ce2b2a2c12f2d2a09c91316169e8063a80eb4482a44b8fe5b265e1 + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: ded2e976dbfa428777496158db93efdd27523ce17cf7eae0e87c6dbba0f30bb46bbe6409ccdcf5ecea7bdea864ed409afc3b5c60b79f008d42dff79fdd481c27 + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.4 + resolution: "pump@npm:3.0.4" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: d043c3e710c56ffd280711e98a94e863ab334f79ea43cee0fb70e1349b2355ffd2ff287c7522e4c960a247699d5b7825f00fa090b85d6179c973be13f78a6c49 + languageName: node + linkType: hard + +"randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: d779499376bd4cbb435ef3ab9a957006c8682f343f14089ed5f27764e4645114196e75b7f6abf1cbd84fd247c0cb0651698444df8c9bf30e62120fbbc52269d6 + languageName: node + linkType: hard + +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + +"record-cache@npm:^1.2.0": + version: 1.2.0 + resolution: "record-cache@npm:1.2.0" + dependencies: + b4a: ^1.3.1 + checksum: 8dc293a4133a0f6151b30a2d7d5b9a0608d9cca8ece34030c2631841f15355299af482d28ad6aaf123e08d9fd4dff2575449e6ad55b1c352a67c019d63bf0ec0 + languageName: node + linkType: hard + +"rollup@npm:^4.14.1": + version: 4.62.4 + resolution: "rollup@npm:4.62.4" + dependencies: + "@napi-rs/lzma-linux-x64-gnu": 1.5.1 + "@rollup/rollup-android-arm-eabi": 4.62.4 + "@rollup/rollup-android-arm64": 4.62.4 + "@rollup/rollup-darwin-arm64": 4.62.4 + "@rollup/rollup-darwin-x64": 4.62.4 + "@rollup/rollup-freebsd-arm64": 4.62.4 + "@rollup/rollup-freebsd-x64": 4.62.4 + "@rollup/rollup-linux-arm-gnueabihf": 4.62.4 + "@rollup/rollup-linux-arm-musleabihf": 4.62.4 + "@rollup/rollup-linux-arm64-gnu": 4.62.4 + "@rollup/rollup-linux-arm64-musl": 4.62.4 + "@rollup/rollup-linux-loong64-gnu": 4.62.4 + "@rollup/rollup-linux-loong64-musl": 4.62.4 + "@rollup/rollup-linux-ppc64-gnu": 4.62.4 + "@rollup/rollup-linux-ppc64-musl": 4.62.4 + "@rollup/rollup-linux-riscv64-gnu": 4.62.4 + "@rollup/rollup-linux-riscv64-musl": 4.62.4 + "@rollup/rollup-linux-s390x-gnu": 4.62.4 + "@rollup/rollup-linux-x64-gnu": 4.62.4 + "@rollup/rollup-linux-x64-musl": 4.62.4 + "@rollup/rollup-openbsd-x64": 4.62.4 + "@rollup/rollup-openharmony-arm64": 4.62.4 + "@rollup/rollup-win32-arm64-msvc": 4.62.4 + "@rollup/rollup-win32-ia32-msvc": 4.62.4 + "@rollup/rollup-win32-x64-gnu": 4.62.4 + "@rollup/rollup-win32-x64-msvc": 4.62.4 + "@types/estree": 1.0.9 + fsevents: ~2.3.2 + dependenciesMeta: + "@napi-rs/lzma-linux-x64-gnu": + optional: true + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: bf310620a92b608ea884de9353c974132edd099a547028f8b0ae4fb11705d590539054aa455affece154f259d05fadd11dd0596278a1d04f1af93657bc0eb3d5 + languageName: node + linkType: hard + +"run-series@npm:^1.1.9": + version: 1.1.9 + resolution: "run-series@npm:1.1.9" + checksum: 375a2c8141715f6e10d5de47b140217f0d1b123bbf16f7d5d96bf12eafd7aed47c23dccc4ffd1c0b9d854f5076ef285628a4d21f4c58780ed77012efcfcd9b8c + languageName: node + linkType: hard + +"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + +"sax@npm:>=0.6.0": + version: 1.6.1 + resolution: "sax@npm:1.6.1" + checksum: be78ded08c77db1644a8d32c93266615f51b925c8b6b6f3f689aed10eae57f307f8fbd7bcbf502041903edcfec17ea7d07c7bb4960e076faa92fc6773b621f2a + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.8.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 0c580f17e88e2b45806dc5cf3d824f719c946999d3554bf30307c2b68b3300ab3c8bfcd84d8f489b93b4cbb5362f5fc8de4d2858954f18d834253c4450fc9f6b + languageName: node + linkType: hard + +"sharp@npm:0.35.2": + version: 0.35.2 + resolution: "sharp@npm:0.35.2" + dependencies: + "@img/colour": ^1.1.0 + "@img/sharp-darwin-arm64": 0.35.2 + "@img/sharp-darwin-x64": 0.35.2 + "@img/sharp-freebsd-wasm32": 0.35.2 + "@img/sharp-libvips-darwin-arm64": 1.3.1 + "@img/sharp-libvips-darwin-x64": 1.3.1 + "@img/sharp-libvips-linux-arm": 1.3.1 + "@img/sharp-libvips-linux-arm64": 1.3.1 + "@img/sharp-libvips-linux-ppc64": 1.3.1 + "@img/sharp-libvips-linux-riscv64": 1.3.1 + "@img/sharp-libvips-linux-s390x": 1.3.1 + "@img/sharp-libvips-linux-x64": 1.3.1 + "@img/sharp-libvips-linuxmusl-arm64": 1.3.1 + "@img/sharp-libvips-linuxmusl-x64": 1.3.1 + "@img/sharp-linux-arm": 0.35.2 + "@img/sharp-linux-arm64": 0.35.2 + "@img/sharp-linux-ppc64": 0.35.2 + "@img/sharp-linux-riscv64": 0.35.2 + "@img/sharp-linux-s390x": 0.35.2 + "@img/sharp-linux-x64": 0.35.2 + "@img/sharp-linuxmusl-arm64": 0.35.2 + "@img/sharp-linuxmusl-x64": 0.35.2 + "@img/sharp-webcontainers-wasm32": 0.35.2 + "@img/sharp-win32-arm64": 0.35.2 + "@img/sharp-win32-ia32": 0.35.2 + "@img/sharp-win32-x64": 0.35.2 + detect-libc: ^2.1.2 + semver: ^7.8.4 + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-freebsd-wasm32": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-webcontainers-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: fb286642ed5865cdeaf3f8878f2cab82f7621535f128d43571f73849a371e6196fc647c5562c0ecc7c4869ca6cd36ce942d3e8e21e2d2d8bcfb2d6e2e4258571 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: ^3.0.0 + checksum: 6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: ^6.0.0 + once: ^1.3.1 + simple-concat: ^1.0.0 + checksum: e4132fd27cf7af230d853fa45c1b8ce900cb430dd0a3c6d3829649fe4f2b26574c803698076c4006450efb0fad2ba8c5455fbb5755d4b0a5ec42d4f12b31d27e + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: ~5.2.0 + checksum: 8417646695a66e73aefc4420eb3b84cc9ffd89572861fe004e6aeb13c7bc00e2f616247505d2dbbef24247c372f70268f594af7126f43548565c68c117bdeb56 + languageName: node + linkType: hard + +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + +"supports-color@npm:^10.0.0": + version: 10.2.2 + resolution: "supports-color@npm:10.2.2" + checksum: bd132705fc07213a4024131fb061f0a46a48b49ecaf434bb029c0a5aa0339b969236d496d63969e3c248a90fdcac16d4f9c5bf187e7be0bfb5e804ed13bef6b0 + languageName: node + linkType: hard + +"tar-fs@npm:^2.0.0": + version: 2.1.5 + resolution: "tar-fs@npm:2.1.5" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: c8322c51d676509371273eeb19d65f919a041992e06dee67fe49d7295f16ce9af3ebf528c2480d5aacff97f114faba8bade1ac6c1fd4ca13a74ed8e97e856439 + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: ^4.0.3 + end-of-stream: ^1.4.1 + fs-constants: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^3.1.1 + checksum: 699831a8b97666ef50021c767f84924cfee21c142c2eb0e79c63254e140e6408d6d55a065a2992548e72b06de39237ef2b802b99e3ece93ca3904a37622a66f3 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.22 + resolution: "tar@npm:7.5.22" + dependencies: + "@isaacs/fs-minipass": ^4.0.0 + chownr: ^3.0.0 + minipass: ^7.1.2 + minizlib: ^3.1.0 + yallist: ^5.0.0 + checksum: e1434f40954410d191ab6d89017bfd40c24869cfd319d19dd74f84db13cbea15370a22bc9b8ecd699d05c2777ec6297f35dac318ed4829b6e776a7cb4f1f7c68 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: ^6.5.0 + picomatch: ^4.0.4 + checksum: 041e73eae568152c376551b21b8a27909d474166a8f405cdb0345991c50cf6afd0f878d7a387645d9c05d8ea2c9a55cc2fd2cfe6c5d5a5264770972b1adcad86 + languageName: node + linkType: hard + +"tokenx@npm:^1.3.0": + version: 1.6.0 + resolution: "tokenx@npm:1.6.0" + checksum: 9104986109f65a04bc3c148b442df5095abfd9786b1d8d218545cd1e3718c70f09f947757668c21f4b3318f30a6a3d9963ce60bae80348abf51cb12f9b19185e + languageName: node + linkType: hard + +"tslib@npm:2.7.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 1606d5c89f88d466889def78653f3aab0f88692e80bb2066d090ca6112ae250ec1cfa9dbfaab0d17b60da15a4186e8ec4d893801c67896b277c17374e36e1d28 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a + languageName: node + linkType: hard + +"tsx@npm:^4.22.4": + version: 4.23.9 + resolution: "tsx@npm:4.23.9" + dependencies: + esbuild: ~0.28.0 + fsevents: ~2.3.3 + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 4fd4618783d0f9f4a9f5bcf768efd3fb3fbcef35f0bd1cf125d2e0a119a32036c8713b0adf7787750a8d0c9cde767fcf52001d4f8c535085fad895e3322f8807 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: ^5.0.1 + checksum: 05f6510358f8afc62a057b8b692f05d70c1782b70db86d6a1e0d5e28a32389e52fa6e7707b6c5ecccacc031462e4bc35af85ecfe4bbc341767917b7cf6965711 + languageName: node + linkType: hard + +"typescript@npm:^5.7.2": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 0d0ffb84f2cd072c3e164c79a2e5a1a1f4f168e84cb2882ff8967b92afe1def6c2a91f6838fb58b168428f9458c57a2ba06a6737711fdd87a256bbe83e9a217f + languageName: node + linkType: hard + +"typescript@patch:typescript@^5.7.2#~builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#~builtin::version=5.9.3&hash=14eedb" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 8bb8d86819ac86a498eada254cad7fb69c5f74778506c700c2a712daeaff21d3a6f51fd0d534fe16903cb010d1b74f89437a3d02d4d0ff5ca2ba9a4660de8497 + languageName: node + linkType: hard + +"uint8-util@npm:^2.2.2": + version: 2.3.2 + resolution: "uint8-util@npm:2.3.2" + checksum: 5d13a2cd3b42c29740ff29cbd83281e0ddbe2e520276d349a3c7a2a93261e2fc0365927a18eaa1d7633f2f37bdb59edd1ce246ac4c00298aa93dd9b98c01fa8a + languageName: node + linkType: hard + +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 46331c7d6016bf85b3e8f20c159d62f5ae471aba1eb3dc52fff35a0259d58dcc7d592d4cc4f00c5f9243fa738a11cfa48bd20203040d4a9e6bc25e807fab7ab3 + languageName: node + linkType: hard + +"undici@npm:7.28.0": + version: 7.28.0 + resolution: "undici@npm:7.28.0" + checksum: 57c77c4ee75e166ef361ad238fcd88816fbc0d058aeadf4c0f689194bd17eb6d63ce25b89bd4f4d65a380ebbde784bf4886ec51acc9f8c826ecab01d8f63aaac + languageName: node + linkType: hard + +"undici@npm:^8.4.1": + version: 8.10.0 + resolution: "undici@npm:8.10.0" + checksum: 6b7c5442ec67fb4048a07aba719890904be54fbcff83c58b7133011fead67877c970f8b974f5e05aa0c7f5603c726888d8b3648e891af1493e7e362d848d7e9c + languageName: node + linkType: hard + +"unenv@npm:2.0.0-rc.24": + version: 2.0.0-rc.24 + resolution: "unenv@npm:2.0.0-rc.24" + dependencies: + pathe: ^2.0.3 + checksum: eca620ceed3952a24d22db826f3fdd075435398271383450e9755e477f00d5b832a6e08868b1dea35efb98eac86aa51de7774def8364cad2eed31dcec6c3bd8e + languageName: node + linkType: hard + +"unordered-array-remove@npm:^1.0.2": + version: 1.0.2 + resolution: "unordered-array-remove@npm:1.0.2" + checksum: e355d17b8298d3e6305d96b85b8b30923926c292058fa390da95c90003eac2799888b437953fc79511981596b39babc79f1a80a33277888ef0434b5022ba1830 + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: ^2.0.0 + bin: + node-which: ./bin/node-which + checksum: 1a5c563d3c1b52d5f893c8b61afe11abc3bab4afac492e8da5bde69d550de701cf9806235f20a47b5c8fa8a1d6a9135841de2596535e998027a54589000e66d1 + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: ^4.0.0 + bin: + node-which: bin/which.js + checksum: 913a43ac10df37602ba9795a004dd7ab12ba7dd592aca1f08ec333be1fdd6a49bbf119a88c3f8d0ea70eeb6251726e77069251424d73000299a0a840ed000732 + languageName: node + linkType: hard + +"workerd@npm:1.20260801.1": + version: 1.20260801.1 + resolution: "workerd@npm:1.20260801.1" + dependencies: + "@cloudflare/workerd-darwin-64": 1.20260801.1 + "@cloudflare/workerd-darwin-arm64": 1.20260801.1 + "@cloudflare/workerd-linux-64": 1.20260801.1 + "@cloudflare/workerd-linux-arm64": 1.20260801.1 + "@cloudflare/workerd-windows-64": 1.20260801.1 + dependenciesMeta: + "@cloudflare/workerd-darwin-64": + optional: true + "@cloudflare/workerd-darwin-arm64": + optional: true + "@cloudflare/workerd-linux-64": + optional: true + "@cloudflare/workerd-linux-arm64": + optional: true + "@cloudflare/workerd-windows-64": + optional: true + bin: + workerd: bin/workerd + checksum: caea23a718daaa9822b0e78992514d825fb73662cc56abcba5d0fe3e973cd3061ce3e8ba067e9c431d2f26218d7a7e3847b7162dd264da76138afc6aed74536e + languageName: node + linkType: hard + +"wrangler@npm:^4.18.0": + version: 4.119.0 + resolution: "wrangler@npm:4.119.0" + dependencies: + "@cloudflare/kv-asset-handler": 0.5.0 + "@cloudflare/unenv-preset": 2.16.1 + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + fsevents: 2.3.3 + miniflare: 5.20260801.0-alpha + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260801.1 + peerDependencies: + "@cloudflare/workers-types": ^5.20260801.1 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@cloudflare/workers-types": + optional: true + bin: + cf-wrangler: bin/cf-wrangler.js + wrangler: bin/wrangler.js + wrangler2: bin/wrangler.js + checksum: 98b327c42444672202b2bea23e20c17141228a7575e29fedc8829e20b495bbf5d4d4429f128871710855c69e13944a8dc0f75b123c6843b00e03ea720b734c0b + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard + +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 442badcce1f1178ec87a0b5372ae2e9771e07c4929a3180321901f226127f252441e8689d765aa5cfba5f50ac60dd830954afc5aeae81609aefa11d3ddf5cecf + languageName: node + linkType: hard + +"ws@npm:8.21.0": + version: 8.21.0 + resolution: "ws@npm:8.21.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 83ff89ae011bc5c3c5605a45a0d50e12589143c7500ca4de83a8d43b3cd26e71f422cb3206fd1a9e6d541d666eeb66255c30d095d62d413b3c7afe5d2c5cb928 + languageName: node + linkType: hard + +"xml2js@npm:^0.6.2": + version: 0.6.2 + resolution: "xml2js@npm:0.6.2" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: 458a83806193008edff44562c0bdb982801d61ee7867ae58fd35fab781e69e17f40dfeb8fc05391a4648c9c54012066d3955fe5d993ffbe4dc63399023f32ac2 + languageName: node + linkType: hard + +"xmlbuilder@npm:~11.0.0": + version: 11.0.1 + resolution: "xmlbuilder@npm:11.0.1" + checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: eba51182400b9f35b017daa7f419f434424410691bbc5de4f4240cc830fdef906b504424992700dc047f16b4d99100a6f8b8b11175c193f38008e9c96322b6a5 + languageName: node + linkType: hard + +"youch-core@npm:^0.3.3": + version: 0.3.3 + resolution: "youch-core@npm:0.3.3" + dependencies: + "@poppinss/exception": ^1.2.2 + error-stack-parser-es: ^1.0.5 + checksum: 48f662073d71eae6942128d5adb6c82b6d14e1ab401ec53ee63c93af81ad19af361e50010cd4e96a21b6252ec383e0e6b06b0f47836d0968edeca02c0ac7be89 + languageName: node + linkType: hard + +"youch@npm:4.1.0-beta.10": + version: 4.1.0-beta.10 + resolution: "youch@npm:4.1.0-beta.10" + dependencies: + "@poppinss/colors": ^4.1.5 + "@poppinss/dumper": ^0.6.4 + "@speed-highlight/core": ^1.2.7 + cookie: ^1.0.2 + youch-core: ^0.3.3 + checksum: ea9fa5f1d79dcf048d06242eecfe2ea8f3f7440d1f8a178a3d4a34ca870fbcb2716dd1f5edcc65ae16b1879eea74808cff5408d26fec96a64e3f583da082975f + languageName: node + linkType: hard + +"zod@npm:^3.24.1": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: c9a403a62b329188a5f6bd24d5d935d2bba345f7ab8151d1baa1505b5da9f227fb139354b043711490c798e91f3df75991395e40142e6510a4b16409f302b849 + languageName: node + linkType: hard From 917516c7479ed0dbc1f1f35e2876e696d276b97d Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Aug 2026 17:19:01 +0300 Subject: [PATCH 22/25] add: improve refresh --- backend/src/worker.ts | 6 +++--- backend/wrangler.toml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 808d95e..d852b14 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -219,7 +219,7 @@ export default { }); try { - const summaries = await backFillAnalytics(env.ANTSEED_KV, ANALYTICS_CRON_BACKFILL_DAYS); + const summaries = await backFillAnalytics(env, ANALYTICS_CRON_BACKFILL_DAYS); logInfo("cron.analytics.summary", { runs: summaries.length, summaries: JSON.stringify(summaries) @@ -245,7 +245,7 @@ async function backFillAnalytics(env: Env, maxRunsPerTick = 2) { const summaries: Array<{ currentDate: string; finalizedDates: string[] }> = []; const runAt = new Date(); const todayDate = runAt.toISOString().slice(0, 10); - let cursorDate = await readAnalyticsBackfillCursorDate(kv); + let cursorDate = await readAnalyticsBackfillCursorDate(env.ANTSEED_KV); if (!cursorDate) { cursorDate = dateDaysAgo(runAt, ANALYTICS_CRON_BACKFILL_DAYS); await kv.put(ANALYTICS_CRON_BACKFILL_CURSOR_KEY, cursorDate); @@ -349,7 +349,7 @@ async function route(request: Request, env: Env, _ctx: ExecutionContext): Promis ); } await env.ANTSEED_KV.put(ANALYTICS_REFRESH_LAST_RUN_KEY, String(now)); - const summaries = await backFillAnalytics(env.ANTSEED_KV); + const summaries = await backFillAnalytics(env); return json(summaries); } diff --git a/backend/wrangler.toml b/backend/wrangler.toml index 02f61d8..b36e156 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -29,7 +29,6 @@ ANTSEED_CHANNELS_ADDRESS = "0xba66d3b4fbcf472f6f11d6f9f96aace96516f09d" [[kv_namespaces]] binding = "ANTSEED_KV" id = "0831898ded5048049353498b8fc962f0" -preview_id = "11111111111111111111111111111111" [triggers] crons = ["0 */6 * * *"] From bcf94be57006a84d6b97ef527fe2b5aac585944e Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Aug 2026 19:48:53 +0300 Subject: [PATCH 23/25] fix: base analytics --- .gitignore | 3 + backend/.yarnrc.yml | 1 + backend/package.json | 1 + backend/src/analytics.ts | 384 +++++++---- backend/src/worker.ts | 1 + backend/yarn.lock | 1306 ++++++++++++++++++++++++-------------- 6 files changed, 1103 insertions(+), 593 deletions(-) create mode 100644 backend/.yarnrc.yml diff --git a/.gitignore b/.gitignore index 4d1ba22..cf40f1a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ contracts/broadcast/ # misc .DS_Store coverage/ +backend/package.json +.wrangler +.yarn \ No newline at end of file diff --git a/backend/.yarnrc.yml b/backend/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/backend/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/backend/package.json b/backend/package.json index 5adeab8..bef5681 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "dependencies": { "@antseed/node": "^0.2.96", "ethers": "^6.13.5", + "viem": "^2.38.3", "zod": "^3.24.1" }, "devDependencies": { diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index 831775d..aa694ec 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -1,20 +1,12 @@ import { Interface } from "ethers"; +import { createPublicClient, fallback, http, toHex, type Address, type Chain, type Hex, type Log, type PublicClient } from "viem"; +import { base, celo } from "viem/chains"; import { decodeBuyerFromUserData } from "./celo-events.js"; import { Env } from "./env.js"; import { errorMessage, logError, logInfo, logWarn, redactAddress } from "./logging.js"; type KV = Pick; -type ExplorerLog = { - address: string; - topics: string[]; - data: string; - transactionHash: string; - logIndex: string; - blockNumber: string; - timeStamp: string; -}; - type StreamSnapshot = { sender: string; buyerAddress?: string; @@ -105,7 +97,23 @@ const STATE_KEY = "analytics:state"; const BUYER_REGISTRY_KEY = "analytics:buyers:registry"; const GD_BUYERS_PREFIX = "analytics:buyers:gd:"; const CREDIT_USERS_PREFIX = "analytics:buyers:credits:"; -const EXPLORER_LOG_BATCH_LIMIT = 1000; +const CELO_CHAIN_ID = 42220; +const BASE_CHAIN_ID = 8453; +const RPCS_CACHE_PREFIX = "analytics:rpcs:"; +const RPCS_CACHE_TTL_MS = 24 * 60 * 60 * 1000; +const CHAINLIST_RPCS_URL = "https://chainlist.org/rpcs.json"; +const LOG_BATCH_BLOCKS = 5000n; +const LOG_BATCH_WORKERS = 5; +const LOG_BATCH_DELAY_MS = 500; +const BASE_BLOCKS_PER_SECOND = 0.5; +const TIMESTAMP_SEARCH_WINDOW_SECONDS = 6 * 60 * 60; +const CELO_FALLBACK_RPCS = ["https://forno.celo.org", "https://rpc.ankr.com/celo"]; +const BASE_FALLBACK_RPCS = ["https://mainnet.base.org", "https://rpc.ankr.com/base"]; + +type RpcCacheEntry = { + rpcs: string[]; + fetchedAt: string; +}; const ZERO_DAILY = { gdOneTimeDepositsWei: "0", @@ -119,6 +127,8 @@ const ZERO_DAILY = { export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Promise { const cfg = analyticsConfigFromEnv(env); const store = new KVAnalyticsStore(env.ANTSEED_KV); + const celoClient = await createChainClient(env.ANTSEED_KV, CELO_CHAIN_ID, celo, CELO_FALLBACK_RPCS); + const baseClient = await createChainClient(env.ANTSEED_KV, BASE_CHAIN_ID, base, BASE_FALLBACK_RPCS); const runDay = dayFromDate(runAt); const state = (await store.getState()) ?? { updatedAt: runAt.toISOString() @@ -132,7 +142,7 @@ export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Pro const knownBuyers = await store.getBuyerRegistry(); const discoveredBuyers = new Set(); - const celoMetrics = await collectCeloDayMetrics(cfg, dayWindow, aggregate, discoveredBuyers); + const celoMetrics = await collectCeloDayMetrics(cfg, celoClient, dayWindow, aggregate, discoveredBuyers); const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, windowEnd, discoveredBuyers); if (discoveredBuyers.size > 0) { @@ -140,7 +150,7 @@ export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Pro for (const buyer of discoveredBuyers) knownBuyers.add(buyer); } logInfo("getting base metrics...."); - const baseMetrics = await collectBaseDayMetrics(cfg, dayWindow, aggregate, knownBuyers); + const baseMetrics = await collectBaseDayMetrics(cfg, baseClient, dayWindow, aggregate, knownBuyers); logInfo("got base metrics...."); logInfo("building dialy reocrd...."); @@ -338,6 +348,7 @@ async function finalizeClosedDays(store: KVAnalyticsStore, state: AnalyticsState async function collectCeloDayMetrics( cfg: AnalyticsConfig, + client: PublicClient, dayWindow: UtcDayWindow, aggregate: DailyAggregate, discoveredBuyers: Set @@ -353,11 +364,11 @@ async function collectCeloDayMetrics( const range = await getExplorerBlockRange(cfg.celoBlockscoutUrl, dayWindow); - const logs = await getExplorerEvents(cfg.celoBlockscoutUrl, { - address: cfg.celoVaultAddress, - topic0: getTopicHash(CELO_VAULT_EVENTS, "GdDeposited"), - fromBlock: range.fromBlock, - toBlock: range.toBlock + const logs = await fetchLogsByRange(client, { + address: cfg.celoVaultAddress as Address, + topic0: getTopicHash(CELO_VAULT_EVENTS, "GdDeposited") as Hex, + fromBlock: BigInt(range.fromBlock), + toBlock: BigInt(range.toBlock) }); logInfo("analytics.celo.scan", { range, @@ -366,7 +377,7 @@ async function collectCeloDayMetrics( }); let matched = 0; for (const log of logs) { - const decoded = decodeEventSafe(CELO_VAULT_EVENTS, log); + const decoded = decodeViemEventSafe(CELO_VAULT_EVENTS, log); if (!decoded || decoded.name !== "GdDeposited") continue; matched += 1; aggregate.gdOneTimeDepositsWei += BigInt(decoded.args.gdAmount.toString()); @@ -384,6 +395,7 @@ async function collectCeloDayMetrics( async function collectBaseDayMetrics( cfg: AnalyticsConfig, + client: PublicClient, dayWindow: UtcDayWindow, aggregate: DailyAggregate, knownBuyers: Set @@ -392,25 +404,29 @@ async function collectBaseDayMetrics( let scanned = 0; let matched = 0; - const range = await getExplorerBlockRange(cfg.baseBlockscoutUrl, dayWindow); - logInfo("base range:", range); + const range = await getBlockRangeByTimestamp(client, dayWindow); + const logRange = { + fromBlock: range.fromBlock.toString(), + toBlock: range.toBlock.toString() + }; + logInfo("base range:", logRange); for (const eventName of eventNames) { - const logs = await getExplorerEvents(cfg.baseBlockscoutUrl, { - address: cfg.baseChannelsAddress, - topic0: getTopicHash(BASE_CHANNEL_EVENTS, eventName), + const logs = await fetchLogsByRange(client, { + address: cfg.baseChannelsAddress as Address, + topic0: getTopicHash(BASE_CHANNEL_EVENTS, eventName) as Hex, fromBlock: range.fromBlock, toBlock: range.toBlock }); logInfo("analytics.base.scan", { eventName, - range, + range: logRange, dayWindow, foundLogs: logs.length }); scanned += logs.length; for (const log of logs) { - const decoded = decodeEventSafe(BASE_CHANNEL_EVENTS, log); + const decoded = decodeViemEventSafe(BASE_CHANNEL_EVENTS, log); if (!decoded) continue; const buyer = String(decoded.args.buyer).toLowerCase(); if (!knownBuyers.has(buyer)) continue; @@ -528,7 +544,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni } }; - const response = await retry( + const response = await retryWithBackoff( () => fetch(cfg.superfluidSubgraphUrl, { method: "POST", @@ -607,110 +623,152 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni })); } -async function getExplorerEvents( - apiUrl: string, +async function fetchLogsByRange( + client: PublicClient, params: { - address: string; - topic0: string; - fromBlock: number; - toBlock: number; + address: Address; + topic0: Hex; + fromBlock: bigint; + toBlock: bigint; + } +): Promise { + if (params.toBlock < params.fromBlock) { + return []; } -): Promise { - return getExplorerEventsByRange(apiUrl, params, params.fromBlock, params.toBlock); + + const batches: Array<{ fromBlock: bigint; toBlock: bigint }> = []; + for (let fromBlock = params.fromBlock; fromBlock <= params.toBlock; fromBlock += LOG_BATCH_BLOCKS) { + const toBlock = fromBlock + LOG_BATCH_BLOCKS - 1n; + batches.push({ + fromBlock, + toBlock: toBlock <= params.toBlock ? toBlock : params.toBlock + }); + } + + const results: Log[][] = new Array(batches.length); + let nextIndex = 0; + + const worker = async (): Promise => { + let firstRequest = true; + while (true) { + const index = nextIndex; + nextIndex += 1; + if (index >= batches.length) { + return; + } + + if (!firstRequest) { + await wait(LOG_BATCH_DELAY_MS); + } + firstRequest = false; + + const batch = batches[index]; + results[index] = await retryWithBackoff( + async () => { + const logs = await client.request({ + method: "eth_getLogs", + params: [ + { + address: params.address, + topics: [params.topic0], + fromBlock: toHex(batch.fromBlock), + toBlock: toHex(batch.toBlock) + } + ] + }); + return logs as Log[]; + }, + 3, + 500 + ); + } + }; + + await Promise.all(new Array(LOG_BATCH_WORKERS).fill(null).map(() => worker())); + return results.flat(); } -async function getExplorerEventsByRange( - apiUrl: string, - params: { - address: string; - topic0: string; - fromBlock: number; - toBlock: number; - }, - fromBlock: number, - toBlock: number -): Promise { - const batch = await fetchExplorerLogBatch(apiUrl, { - address: params.address, - topic0: params.topic0, - fromBlock, - toBlock +async function createChainClient(kv: KV, chainId: number, chain: Chain, fallbackRpcs: string[]): Promise { + const rpcUrls = await getChainRpcUrls(kv, chainId, fallbackRpcs); + return createPublicClient({ + chain, + transport: fallback(rpcUrls.map((rpcUrl) => http(rpcUrl))) }); +} - if (batch.length < EXPLORER_LOG_BATCH_LIMIT) { - return batch; +async function getChainRpcUrls(kv: KV, chainId: number, fallbackRpcs: string[]): Promise { + const key = `${RPCS_CACHE_PREFIX}${chainId}`; + const cached = (await kv.get(key, "json")) as RpcCacheEntry | null; + if (cached?.fetchedAt) { + const fetchedAtMs = Date.parse(cached.fetchedAt); + if (!Number.isNaN(fetchedAtMs) && Date.now() - fetchedAtMs < RPCS_CACHE_TTL_MS) { + const cachedUrls = sanitizeRpcUrls(cached.rpcs); + if (cachedUrls.length > 0) { + return cachedUrls; + } + } } - if (fromBlock >= toBlock) { - logWarn("analytics.explorer.truncated", { - apiUrl, - address: redactAddress(params.address), - topic0: params.topic0, - fromBlock, - toBlock, - returned: batch.length + try { + const fetchedUrls = await fetchRpcsFromChainlist(chainId); + if (fetchedUrls.length > 0) { + await kv.put( + key, + JSON.stringify({ + rpcs: fetchedUrls, + fetchedAt: new Date().toISOString() + } satisfies RpcCacheEntry) + ); + return fetchedUrls; + } + } catch (error) { + logWarn("analytics.rpc.chainlist_failed", { + chainId, + message: errorMessage(error) }); - return dedupeExplorerLogs(batch); } - const midpoint = fromBlock + Math.floor((toBlock - fromBlock) / 2); - const left = await getExplorerEventsByRange(apiUrl, params, fromBlock, midpoint); - const right = await getExplorerEventsByRange(apiUrl, params, midpoint + 1, toBlock); - return dedupeExplorerLogs([...left, ...right]); + return sanitizeRpcUrls(fallbackRpcs); } -async function fetchExplorerLogBatch( - apiUrl: string, - params: { - address: string; - topic0: string; - fromBlock: number; - toBlock: number; +async function fetchRpcsFromChainlist(chainId: number): Promise { + const ALLOWED_DOMAINS = ["chainlist.org"]; + const chainlistUrl = new URL(CHAINLIST_RPCS_URL); + if (!ALLOWED_DOMAINS.includes(chainlistUrl.hostname)) { + throw new Error("Chainlist domain not allowed"); } -): Promise { - const all: ExplorerLog[] = []; - const url = new URL(apiUrl); - url.searchParams.set("module", "logs"); - url.searchParams.set("action", "getLogs"); - url.searchParams.set("address", params.address); - url.searchParams.set("topic0", params.topic0); - url.searchParams.set("sort", "asc"); - url.searchParams.set("offset", String(EXPLORER_LOG_BATCH_LIMIT)); - url.searchParams.set("fromBlock", String(params.fromBlock)); - url.searchParams.set("toBlock", String(params.toBlock)); - - const response = await retry(() => fetch(url), 3, 300); - if (!response.ok) { - throw new Error(`Explorer HTTP ${response.status}`); + if (!["http:", "https:"].includes(chainlistUrl.protocol)) { + throw new Error("Chainlist protocol not allowed"); } - const payload = (await response.json()) as { - status?: string; - message?: string; - result?: ExplorerLog[] | string; - }; - if (Array.isArray(payload.result)) { - all.push(...payload.result); - return all; + const response = await retryWithBackoff(() => fetch(chainlistUrl.href), 3, 500); + if (!response.ok) { + throw new Error(`Chainlist HTTP ${response.status}`); } - if (typeof payload.result === "string" && payload.result.toLowerCase().includes("no records")) { - return all; - } + const payload = (await response.json()) as Array<{ + chainId?: number; + rpc?: Array; + }>; - if (payload.status === "0" && payload.message?.toLowerCase().includes("no records")) { - return all; + const chain = payload.find((entry) => entry.chainId === chainId); + if (!chain?.rpc) { + return []; } - logWarn("analytics.explorer.unexpected", { - apiUrl, - address: redactAddress(params.address), - topic0: params.topic0, - fromBlock: params.fromBlock, - toBlock: params.toBlock, - payload - }); - return all; + const urls = chain.rpc.map((entry) => (typeof entry === "string" ? entry : (entry.url ?? ""))).filter((entry) => Boolean(entry)); + + return sanitizeRpcUrls(urls); +} + +function sanitizeRpcUrls(urls: string[]): string[] { + const deduped = new Set(); + for (const url of urls) { + if (!url.startsWith("https://")) continue; + if (url.includes("${")) continue; + deduped.add(url); + } + return [...deduped]; } async function getExplorerBlockRange(apiUrl: string, dayWindow: UtcDayWindow): Promise<{ fromBlock: number; toBlock: number }> { @@ -722,6 +780,76 @@ async function getExplorerBlockRange(apiUrl: string, dayWindow: UtcDayWindow): P }; } +async function getBlockRangeByTimestamp(client: PublicClient, dayWindow: UtcDayWindow): Promise<{ fromBlock: bigint; toBlock: bigint }> { + const fromBlock = await getBlockByTimestampViem(client, dayWindow.startUnix, "after", BASE_BLOCKS_PER_SECOND); + const toBlock = await getBlockByTimestampViem(client, dayWindow.endUnix, "before", BASE_BLOCKS_PER_SECOND); + return { + fromBlock, + toBlock: toBlock >= fromBlock ? toBlock : fromBlock + }; +} + +async function getBlockByTimestampViem(client: PublicClient, timestamp: number, closest: "before" | "after", blocksPerSecond: number): Promise { + const latest = await retryWithBackoff(() => client.getBlock({ blockTag: "latest" }), 3, 300); + const latestNumber = latest.number; + if (latestNumber === null) { + throw new Error("latest block number missing"); + } + + const latestTimestamp = Number(latest.timestamp); + if (timestamp >= latestTimestamp) { + return latestNumber; + } + + const estimatedBlocksAgo = BigInt(Math.max(0, Math.floor((latestTimestamp - timestamp) * blocksPerSecond))); + const estimatedBlock = estimatedBlocksAgo >= latestNumber ? 0n : latestNumber - estimatedBlocksAgo; + const windowBlocks = BigInt(Math.max(1, Math.ceil(blocksPerSecond * TIMESTAMP_SEARCH_WINDOW_SECONDS))); + + let low = estimatedBlock >= windowBlocks ? estimatedBlock - windowBlocks : 0n; + let high = estimatedBlock + windowBlocks <= latestNumber ? estimatedBlock + windowBlocks : latestNumber; + + let lowTimestamp = Number((await retryWithBackoff(() => client.getBlock({ blockNumber: low }), 3, 300)).timestamp); + let highTimestamp = Number((await retryWithBackoff(() => client.getBlock({ blockNumber: high }), 3, 300)).timestamp); + + while (timestamp < lowTimestamp && low > 0n) { + high = low; + low = low > windowBlocks ? low - windowBlocks : 0n; + lowTimestamp = Number((await retryWithBackoff(() => client.getBlock({ blockNumber: low }), 3, 300)).timestamp); + } + + while (timestamp > highTimestamp && high < latestNumber) { + low = high; + high = high + windowBlocks <= latestNumber ? high + windowBlocks : latestNumber; + highTimestamp = Number((await retryWithBackoff(() => client.getBlock({ blockNumber: high }), 3, 300)).timestamp); + } + + let best = closest === "after" ? latestNumber : 0n; + + while (low <= high) { + const mid = (low + high) / 2n; + const block = await retryWithBackoff(() => client.getBlock({ blockNumber: mid }), 3, 300); + const blockTimestamp = Number(block.timestamp); + + if (blockTimestamp === timestamp) { + return mid; + } + + if (blockTimestamp < timestamp) { + if (closest === "before") best = mid; + low = mid + 1n; + continue; + } + + if (closest === "after") best = mid; + if (mid === 0n) { + break; + } + high = mid - 1n; + } + + return best; +} + async function getBlockByTimestamp(apiUrl: string, timestamp: number, closest: "before" | "after"): Promise { const url = new URL(apiUrl); url.searchParams.set("module", "block"); @@ -729,7 +857,7 @@ async function getBlockByTimestamp(apiUrl: string, timestamp: number, closest: " url.searchParams.set("timestamp", String(timestamp)); url.searchParams.set("closest", closest); - const response = await retry(() => fetch(url), 3, 300); + const response = await retryWithBackoff(() => fetch(url.href), 3, 300); if (!response.ok) { throw new Error(`Explorer HTTP ${response.status}`); } @@ -803,14 +931,20 @@ function getTopicHash(iface: Interface, eventName: string): string { return event.topicHash; } -function decodeEventSafe(iface: Interface, log: ExplorerLog) { +function decodeViemEventSafe(iface: Interface, log: Log) { try { - log.topics = log.topics.filter((topic) => topic); - return iface.parseLog(log); + const topics = log.topics.filter((topic): topic is Hex => Boolean(topic)); + if (topics.length === 0) { + return null; + } + return iface.parseLog({ + data: log.data, + topics + }); } catch (error) { logWarn("analytics.decode.failed", { - txHash: log.transactionHash, - logIndex: log.logIndex, + txHash: log.transactionHash ?? "unknown", + logIndex: log.logIndex?.toString() ?? "unknown", message: errorMessage(error) }); return null; @@ -823,21 +957,7 @@ function parseNumberish(value: string | number): number { return Number.parseInt(value, 10); } -function dedupeExplorerLogs(logs: ExplorerLog[]): ExplorerLog[] { - const seen = new Set(); - const deduped: ExplorerLog[] = []; - - for (const log of logs) { - const key = `${log.transactionHash}:${log.logIndex}`.toLowerCase(); - if (seen.has(key)) continue; - seen.add(key); - deduped.push(log); - } - - return deduped; -} - -async function retry(fn: () => Promise, retries: number, waitMs: number): Promise { +async function retryWithBackoff(fn: () => Promise, retries: number, baseWaitMs: number): Promise { let attempt = 0; let lastError: unknown; while (attempt <= retries) { @@ -847,7 +967,7 @@ async function retry(fn: () => Promise, retries: number, waitMs: number): lastError = error; attempt += 1; if (attempt > retries) break; - await wait(waitMs); + await wait(baseWaitMs * 2 ** (attempt - 1)); } } logError("analytics.retry.failed", { diff --git a/backend/src/worker.ts b/backend/src/worker.ts index d852b14..464e890 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -252,6 +252,7 @@ async function backFillAnalytics(env: Env, maxRunsPerTick = 2) { } // TODO: replace bounded loop with persisted backfill cursor when we need broader historical catch-up. for (let i = 0; i < maxRunsPerTick; i += 1) { + console.log(`analytics backfill run ${i + 1} of ${maxRunsPerTick}, cursorDate=${cursorDate}`); const aggregationRunAt = cursorDate === todayDate ? runAt : new Date(`${cursorDate}T23:59:59.999Z`); const analyticsSummary = await runAnalyticsAggregation(env, aggregationRunAt); summaries.push({ diff --git a/backend/yarn.lock b/backend/yarn.lock index 784c9f8..fbab769 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -12,25 +12,25 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.11.1": +"@adraffy/ens-normalize@npm:^1.11.0": version: 1.11.1 resolution: "@adraffy/ens-normalize@npm:1.11.1" checksum: e8b17fcc730ccc45a956e1fbb09edfe42be41c291079512082e9964f8ef4287e67913183cdd02fff71d2e215340d5b98a9bbbd9be32c5d36fad4ba2c1ec33ff2 languageName: node linkType: hard -"@antseed/api-adapter@npm:0.1.42": - version: 0.1.42 - resolution: "@antseed/api-adapter@npm:0.1.42" - checksum: 89ba446cf407f0857e5abea2103b1b44cc30f1c79a7207a38412164ab9fccc87f0627702a10f1bf7d40127775cc5aec4f17b2b43164cb4b4ab96ee733922de05 +"@antseed/api-adapter@npm:0.1.39": + version: 0.1.39 + resolution: "@antseed/api-adapter@npm:0.1.39" + checksum: 4fac33fa12a0d4127ef8a1a536ea7cb3b73c3d9523f6f8b26598e0f20a1e681612d8a74254a03390d6e172659b44156aee80e1c1f0600de69e62d0a427b8f085 languageName: node linkType: hard "@antseed/node@npm:^0.2.96": - version: 0.2.106 - resolution: "@antseed/node@npm:0.2.106" + version: 0.2.96 + resolution: "@antseed/node@npm:0.2.96" dependencies: - "@antseed/api-adapter": 0.1.42 + "@antseed/api-adapter": 0.1.39 "@silentbot1/nat-api": ^0.4.9 better-sqlite3: ^12.6.2 bittorrent-dht: ^11.0.11 @@ -41,7 +41,7 @@ __metadata: dependenciesMeta: keytar: optional: true - checksum: bd68bf9a712598164517c3e4db0e74ee84df93d0758f0f1a13c5ed84567ff9a6f4aad01388ae904e74136570b1f63b5f9f29f8dee52df04c6f821335fca4117c + checksum: 0f85db96fda055bee426b149f337e7e30ad1205bf1eac73617b8c3c35f1a2bb92c6d0891de2c0ecb22d4b15242796742fca040d41027a8a4aa4581e58826a4cc languageName: node linkType: hard @@ -65,45 +65,45 @@ __metadata: languageName: node linkType: hard -"@cloudflare/workerd-darwin-64@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "@cloudflare/workerd-darwin-64@npm:1.20260801.1" +"@cloudflare/workerd-darwin-64@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20260515.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-darwin-arm64@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20260801.1" +"@cloudflare/workerd-darwin-arm64@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20260515.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-linux-64@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "@cloudflare/workerd-linux-64@npm:1.20260801.1" +"@cloudflare/workerd-linux-64@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "@cloudflare/workerd-linux-64@npm:1.20260515.1" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-linux-arm64@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "@cloudflare/workerd-linux-arm64@npm:1.20260801.1" +"@cloudflare/workerd-linux-arm64@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20260515.1" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-windows-64@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "@cloudflare/workerd-windows-64@npm:1.20260801.1" +"@cloudflare/workerd-windows-64@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "@cloudflare/workerd-windows-64@npm:1.20260515.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@cloudflare/workers-types@npm:^4.20260518.0": - version: 4.20260702.1 - resolution: "@cloudflare/workers-types@npm:4.20260702.1" - checksum: 6233d5c284da49103e795cc7e57ccd0a779416aa5bcb8dfac139aa7e23a70a0f4a946a5149d0ec7bd4b6c76aaede5202bcee9b09aa5c9bc0d553f3fb9be0d6b8 + version: 4.20260518.1 + resolution: "@cloudflare/workers-types@npm:4.20260518.1" + checksum: b4a41dfc6e55ceaef1d6bb05cdad25bb1ebb5393a966e8fc0bc5ba380a2455388484e9f831db36741623358fa5fbb2efc8ca458fd134c37391d813ce0b1ae928 languageName: node linkType: hard @@ -116,7 +116,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.11.1": +"@emnapi/runtime@npm:^1.7.0": version: 1.11.3 resolution: "@emnapi/runtime@npm:1.11.3" dependencies: @@ -125,184 +125,366 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/aix-ppc64@npm:0.28.1" +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm64@npm:0.28.1" +"@esbuild/aix-ppc64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/aix-ppc64@npm:0.28.0" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm@npm:0.28.1" +"@esbuild/android-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-arm64@npm:0.28.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-x64@npm:0.28.1" +"@esbuild/android-arm@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-arm@npm:0.28.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-x64@npm:0.28.0" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-arm64@npm:0.28.1" +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-x64@npm:0.28.1" +"@esbuild/darwin-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/darwin-arm64@npm:0.28.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/darwin-x64@npm:0.28.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-arm64@npm:0.28.1" +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/freebsd-arm64@npm:0.28.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-x64@npm:0.28.1" +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/freebsd-x64@npm:0.28.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm64@npm:0.28.1" +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm@npm:0.28.1" +"@esbuild/linux-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-arm64@npm:0.28.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-arm@npm:0.28.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ia32@npm:0.28.1" +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-ia32@npm:0.28.0" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-loong64@npm:0.28.1" +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-loong64@npm:0.28.0" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-mips64el@npm:0.28.1" +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-mips64el@npm:0.28.0" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ppc64@npm:0.28.1" +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-ppc64@npm:0.28.0" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-riscv64@npm:0.28.1" +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-riscv64@npm:0.28.0" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-s390x@npm:0.28.1" +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-s390x@npm:0.28.0" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-x64@npm:0.28.1" +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-arm64@npm:0.28.1" +"@esbuild/linux-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-x64@npm:0.28.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/netbsd-arm64@npm:0.28.0" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-x64@npm:0.28.1" +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/netbsd-x64@npm:0.28.0" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-arm64@npm:0.28.1" +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openbsd-arm64@npm:0.28.0" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-x64@npm:0.28.1" +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openbsd-x64@npm:0.28.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openharmony-arm64@npm:0.28.1" +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openharmony-arm64@npm:0.28.0" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/sunos-x64@npm:0.28.1" +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/sunos-x64@npm:0.28.0" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-arm64@npm:0.28.1" +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-arm64@npm:0.28.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-ia32@npm:0.28.1" +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-ia32@npm:0.28.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-x64@npm:0.28.1" +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-x64@npm:0.28.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -317,23 +499,24 @@ __metadata: ethers: ^6.13.5 tsx: ^4.22.4 typescript: ^5.7.2 + viem: ^2.38.3 wrangler: ^4.18.0 zod: ^3.24.1 languageName: unknown linkType: soft -"@img/colour@npm:^1.1.0": +"@img/colour@npm:^1.0.0": version: 1.1.0 resolution: "@img/colour@npm:1.1.0" checksum: 1572b4f154fe5987e02e107c32f64a9f50a18cab4a2015b7e53f48317b20c58e00cc2e09467378a2d4f06a6139cabd259b1b6d224e79a3bd6b66daea70a6613d languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-darwin-arm64@npm:0.35.2" +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" dependencies: - "@img/sharp-libvips-darwin-arm64": 1.3.1 + "@img/sharp-libvips-darwin-arm64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-darwin-arm64": optional: true @@ -341,11 +524,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-darwin-x64@npm:0.35.2" +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" dependencies: - "@img/sharp-libvips-darwin-x64": 1.3.1 + "@img/sharp-libvips-darwin-x64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-darwin-x64": optional: true @@ -353,90 +536,81 @@ __metadata: languageName: node linkType: hard -"@img/sharp-freebsd-wasm32@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-freebsd-wasm32@npm:0.35.2" - dependencies: - "@img/sharp-wasm32": 0.35.2 - conditions: os=freebsd - languageName: node - linkType: hard - -"@img/sharp-libvips-darwin-arm64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.1" +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.1" +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.1" +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-arm@npm:1.3.1" +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-ppc64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.1" +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-riscv64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.1" +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-s390x@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.1" +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linux-x64@npm:1.3.1" +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.1" +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-x64@npm:1.3.1": - version: 1.3.1 - resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.1" +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-arm64@npm:0.35.2" +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-arm64": 1.3.1 + "@img/sharp-libvips-linux-arm64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-arm64": optional: true @@ -444,11 +618,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-arm@npm:0.35.2" +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-arm": 1.3.1 + "@img/sharp-libvips-linux-arm": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-arm": optional: true @@ -456,11 +630,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-ppc64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-ppc64@npm:0.35.2" +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-ppc64": 1.3.1 + "@img/sharp-libvips-linux-ppc64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-ppc64": optional: true @@ -468,11 +642,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-riscv64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-riscv64@npm:0.35.2" +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-riscv64": 1.3.1 + "@img/sharp-libvips-linux-riscv64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-riscv64": optional: true @@ -480,11 +654,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-s390x@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-s390x@npm:0.35.2" +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-s390x": 1.3.1 + "@img/sharp-libvips-linux-s390x": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-s390x": optional: true @@ -492,11 +666,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linux-x64@npm:0.35.2" +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linux-x64": 1.3.1 + "@img/sharp-libvips-linux-x64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linux-x64": optional: true @@ -504,11 +678,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-arm64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.2" +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linuxmusl-arm64": 1.3.1 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linuxmusl-arm64": optional: true @@ -516,11 +690,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-x64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-linuxmusl-x64@npm:0.35.2" +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" dependencies: - "@img/sharp-libvips-linuxmusl-x64": 1.3.1 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 dependenciesMeta: "@img/sharp-libvips-linuxmusl-x64": optional: true @@ -528,41 +702,32 @@ __metadata: languageName: node linkType: hard -"@img/sharp-wasm32@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-wasm32@npm:0.35.2" - dependencies: - "@emnapi/runtime": ^1.11.1 - checksum: e71022251df977c81675308a4eae78ffd2fec3b3320a104e0b2839900103fdf5dc441f0f75af4f10293cc75250d0fff86dade8638ffcb96e3b2a1b65577274e5 - languageName: node - linkType: hard - -"@img/sharp-webcontainers-wasm32@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.2" +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" dependencies: - "@img/sharp-wasm32": 0.35.2 + "@emnapi/runtime": ^1.7.0 conditions: cpu=wasm32 languageName: node linkType: hard -"@img/sharp-win32-arm64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-win32-arm64@npm:0.35.2" +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@img/sharp-win32-ia32@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-win32-ia32@npm:0.35.2" +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@img/sharp-win32-x64@npm:0.35.2": - version: 0.35.2 - resolution: "@img/sharp-win32-x64@npm:0.35.2" +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -600,10 +765,10 @@ __metadata: languageName: node linkType: hard -"@napi-rs/lzma-linux-x64-gnu@npm:1.5.1": - version: 1.5.1 - resolution: "@napi-rs/lzma-linux-x64-gnu@npm:1.5.1" - conditions: os=linux & cpu=x64 & libc=glibc +"@noble/ciphers@npm:^1.3.0": + version: 1.3.0 + resolution: "@noble/ciphers@npm:1.3.0" + checksum: 19722c35475df9bc78db60d261d0b5ef8a6d722561efc2135453f943eaa421b492195dc666e3e4df2b755bca3739e04f04b9c660198559f5dd05d3cfbf1b9e92 languageName: node linkType: hard @@ -616,6 +781,24 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.9.1": + version: 1.9.1 + resolution: "@noble/curves@npm:1.9.1" + dependencies: + "@noble/hashes": 1.8.0 + checksum: 4f3483a1001538d2f55516cdcb19319d1eaef79550633f670e7d570b989cdbc0129952868b72bb67643329746b8ffefe8e4cd791c8cc35574e05a37f873eef42 + languageName: node + linkType: hard + +"@noble/curves@npm:~1.9.0": + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" + dependencies: + "@noble/hashes": 1.8.0 + checksum: 65acad44ac6944ab96471109087d6cfcbcaa251faad6295961be9a5ace220634f4b7c74a96d1ee2274ad3880ea953d8e8259893ed8c906c831ef29f5c04ec9cc + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.2": version: 1.3.2 resolution: "@noble/hashes@npm:1.3.2" @@ -623,6 +806,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: c94e98b941963676feaba62475b1ccfa8341e3f572adbb3b684ee38b658df44100187fa0ef4220da580b13f8d27e87d5492623c8a02ecc61f23fb9960c7918f5 + languageName: node + linkType: hard + "@poppinss/colors@npm:^4.1.5": version: 4.1.6 resolution: "@poppinss/colors@npm:4.1.6" @@ -650,181 +840,209 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.4" +"@rollup/rollup-android-arm-eabi@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.62.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-android-arm64@npm:4.62.4" +"@rollup/rollup-android-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-android-arm64@npm:4.62.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-darwin-arm64@npm:4.62.4" +"@rollup/rollup-darwin-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.62.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-darwin-x64@npm:4.62.4" +"@rollup/rollup-darwin-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.62.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.4" +"@rollup/rollup-freebsd-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.62.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-freebsd-x64@npm:4.62.4" +"@rollup/rollup-freebsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.62.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.4" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.62.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.4" +"@rollup/rollup-linux-arm-musleabihf@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.62.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.4" +"@rollup/rollup-linux-arm64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.62.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.4" +"@rollup/rollup-linux-arm64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.62.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.4" +"@rollup/rollup-linux-loong64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.62.2" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-loong64-musl@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.4" +"@rollup/rollup-linux-loong64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.62.2" conditions: os=linux & cpu=loong64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.4" +"@rollup/rollup-linux-ppc64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.62.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-musl@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.4" +"@rollup/rollup-linux-ppc64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.62.2" conditions: os=linux & cpu=ppc64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.4" +"@rollup/rollup-linux-riscv64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.62.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.4" +"@rollup/rollup-linux-riscv64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.62.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.4" +"@rollup/rollup-linux-s390x-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.62.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.4" +"@rollup/rollup-linux-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.62.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.4" +"@rollup/rollup-linux-x64-musl@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.62.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openbsd-x64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-openbsd-x64@npm:4.62.4" +"@rollup/rollup-openbsd-x64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openbsd-x64@npm:4.62.2" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.4" +"@rollup/rollup-openharmony-arm64@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.62.2" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.4" +"@rollup/rollup-win32-arm64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.62.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.4" +"@rollup/rollup-win32-ia32-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.62.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.4" +"@rollup/rollup-win32-x64-gnu@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.62.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.62.4": - version: 4.62.4 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.4" +"@rollup/rollup-win32-x64-msvc@npm:4.62.2": + version: 4.62.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.62.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard +"@scure/base@npm:~1.2.5": + version: 1.2.6 + resolution: "@scure/base@npm:1.2.6" + checksum: 1058cb26d5e4c1c46c9cc0ae0b67cc66d306733baf35d6ebdd8ddaba242b80c3807b726e3b48cb0411bb95ec10d37764969063ea62188f86ae9315df8ea6b325 + languageName: node + linkType: hard + +"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.7.0": + version: 1.7.0 + resolution: "@scure/bip32@npm:1.7.0" + dependencies: + "@noble/curves": ~1.9.0 + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: c83adca5a74ec5c4ded8ba93900d0065e4767c4759cf24c2674923aef01d45ba56f171574e3519f2341be99f53a333f01b674eb6cfeb6fa8379607c6d1bc90b5 + languageName: node + linkType: hard + +"@scure/bip39@npm:1.6.0, @scure/bip39@npm:^1.6.0": + version: 1.6.0 + resolution: "@scure/bip39@npm:1.6.0" + dependencies: + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: 96d46420780473d6c6c9700254a0eceec60302f61d7f9d7f29024e90c7acff3e8e40a5ee52dfaf104db539a10462e531996aaf9e69f082b8540b0a25870545fc + languageName: node + linkType: hard + "@silentbot1/nat-api@npm:^0.4.9": version: 0.4.9 resolution: "@silentbot1/nat-api@npm:0.4.9" @@ -847,9 +1065,9 @@ __metadata: linkType: hard "@speed-highlight/core@npm:^1.2.7": - version: 1.2.23 - resolution: "@speed-highlight/core@npm:1.2.23" - checksum: d5d5ab79b807ae14bc9d9903564500f2027ce963c8f543be78f2bbb398d2b8ad12c2d31c35c78b0cd623d56ba2e2f39038b0be19756c63cdcb005d0e3b284035 + version: 1.2.15 + resolution: "@speed-highlight/core@npm:1.2.15" + checksum: 9644fcad2daa0d7cd1832b382ff7f0078a0bb3751e4eba29253612ad874b57658f6c28848fdaab8aa9bd0ada2aec9bb5392935f43c666e4caca4ddad267ac402 languageName: node linkType: hard @@ -870,11 +1088,11 @@ __metadata: linkType: hard "@types/node@npm:^22.10.2": - version: 22.20.1 - resolution: "@types/node@npm:22.20.1" + version: 22.19.19 + resolution: "@types/node@npm:22.19.19" dependencies: undici-types: ~6.21.0 - checksum: 03cc6d9941229b13fa9ab4e477d6550e85ed3e3d5f85734c03c84e0c18346d8a7c9f22b93570bb9cb4a44ffa176279f8a7ab3cd328fbdcf5d4e6ec541ce03f21 + checksum: 01bc720694f817a4f85af43ff6e93e80b9ec2e3bd1d0de2240e117152f61e2a4e6f19fe1a2632c2693d343ba0d157791581b34459b4e0110bca1ba5f9461ada3 languageName: node linkType: hard @@ -885,6 +1103,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.2.3, abitype@npm:^1.2.3": + version: 1.2.3 + resolution: "abitype@npm:1.2.3" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: b5b5620f8e55a6dd7ae829630c0ded02b30f589f0f8f5ca931cdfcf6d7daa8154e30e3fe3593b3f6c4872a955ac55d447ccc2f801fd6a6aa698bdad966e3fe2e + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -913,6 +1146,13 @@ __metadata: languageName: node linkType: hard +"base64-arraybuffer@npm:^1.0.2": + version: 1.0.2 + resolution: "base64-arraybuffer@npm:1.0.2" + checksum: 15e6400d2d028bf18be4ed97702b11418f8f8779fb8c743251c863b726638d52f69571d4cc1843224da7838abef0949c670bde46936663c45ad078e89fee5c62 + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -1144,36 +1384,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.28.1, esbuild@npm:~0.28.0": - version: 0.28.1 - resolution: "esbuild@npm:0.28.1" - dependencies: - "@esbuild/aix-ppc64": 0.28.1 - "@esbuild/android-arm": 0.28.1 - "@esbuild/android-arm64": 0.28.1 - "@esbuild/android-x64": 0.28.1 - "@esbuild/darwin-arm64": 0.28.1 - "@esbuild/darwin-x64": 0.28.1 - "@esbuild/freebsd-arm64": 0.28.1 - "@esbuild/freebsd-x64": 0.28.1 - "@esbuild/linux-arm": 0.28.1 - "@esbuild/linux-arm64": 0.28.1 - "@esbuild/linux-ia32": 0.28.1 - "@esbuild/linux-loong64": 0.28.1 - "@esbuild/linux-mips64el": 0.28.1 - "@esbuild/linux-ppc64": 0.28.1 - "@esbuild/linux-riscv64": 0.28.1 - "@esbuild/linux-s390x": 0.28.1 - "@esbuild/linux-x64": 0.28.1 - "@esbuild/netbsd-arm64": 0.28.1 - "@esbuild/netbsd-x64": 0.28.1 - "@esbuild/openbsd-arm64": 0.28.1 - "@esbuild/openbsd-x64": 0.28.1 - "@esbuild/openharmony-arm64": 0.28.1 - "@esbuild/sunos-x64": 0.28.1 - "@esbuild/win32-arm64": 0.28.1 - "@esbuild/win32-ia32": 0.28.1 - "@esbuild/win32-x64": 0.28.1 +"esbuild@npm:0.27.3": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": 0.27.3 + "@esbuild/android-arm": 0.27.3 + "@esbuild/android-arm64": 0.27.3 + "@esbuild/android-x64": 0.27.3 + "@esbuild/darwin-arm64": 0.27.3 + "@esbuild/darwin-x64": 0.27.3 + "@esbuild/freebsd-arm64": 0.27.3 + "@esbuild/freebsd-x64": 0.27.3 + "@esbuild/linux-arm": 0.27.3 + "@esbuild/linux-arm64": 0.27.3 + "@esbuild/linux-ia32": 0.27.3 + "@esbuild/linux-loong64": 0.27.3 + "@esbuild/linux-mips64el": 0.27.3 + "@esbuild/linux-ppc64": 0.27.3 + "@esbuild/linux-riscv64": 0.27.3 + "@esbuild/linux-s390x": 0.27.3 + "@esbuild/linux-x64": 0.27.3 + "@esbuild/netbsd-arm64": 0.27.3 + "@esbuild/netbsd-x64": 0.27.3 + "@esbuild/openbsd-arm64": 0.27.3 + "@esbuild/openbsd-x64": 0.27.3 + "@esbuild/openharmony-arm64": 0.27.3 + "@esbuild/sunos-x64": 0.27.3 + "@esbuild/win32-arm64": 0.27.3 + "@esbuild/win32-ia32": 0.27.3 + "@esbuild/win32-x64": 0.27.3 dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -1229,26 +1469,100 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 2ada56fe421ac0720f71968c0ceb56cb534588ae2fbbe5aef2c9dbe195f0f61773ef7838ad07d48c2141d9e8b7d3de5fd39d0bb880860f2f6f77bf67671ed235 - languageName: node - linkType: hard - -"ethers@npm:^6.13.5": - version: 6.17.0 - resolution: "ethers@npm:6.17.0" - dependencies: - "@adraffy/ens-normalize": 1.11.1 - "@noble/curves": 1.2.0 - "@noble/hashes": 1.3.2 - "@types/node": 22.7.5 - aes-js: 4.0.0-beta.5 - tslib: 2.7.0 - ws: 8.21.0 - checksum: 0ba314af2ae25622c60886caf83d825ce543262685961667d166b825c9dbfc66f905e0d7a2e3d8d4ccfa58b6ac20326e7f9a897b5580879efa758a50067ad082 + checksum: 3a45334b00ca235ad96843738c4698970aacde92058aee28ab1fa57ad823c29667070af90b6071cc876e492bebdeed79f593a273c39b419097afd2a772296085 + languageName: node + linkType: hard + +"esbuild@npm:~0.28.0": + version: 0.28.0 + resolution: "esbuild@npm:0.28.0" + dependencies: + "@esbuild/aix-ppc64": 0.28.0 + "@esbuild/android-arm": 0.28.0 + "@esbuild/android-arm64": 0.28.0 + "@esbuild/android-x64": 0.28.0 + "@esbuild/darwin-arm64": 0.28.0 + "@esbuild/darwin-x64": 0.28.0 + "@esbuild/freebsd-arm64": 0.28.0 + "@esbuild/freebsd-x64": 0.28.0 + "@esbuild/linux-arm": 0.28.0 + "@esbuild/linux-arm64": 0.28.0 + "@esbuild/linux-ia32": 0.28.0 + "@esbuild/linux-loong64": 0.28.0 + "@esbuild/linux-mips64el": 0.28.0 + "@esbuild/linux-ppc64": 0.28.0 + "@esbuild/linux-riscv64": 0.28.0 + "@esbuild/linux-s390x": 0.28.0 + "@esbuild/linux-x64": 0.28.0 + "@esbuild/netbsd-arm64": 0.28.0 + "@esbuild/netbsd-x64": 0.28.0 + "@esbuild/openbsd-arm64": 0.28.0 + "@esbuild/openbsd-x64": 0.28.0 + "@esbuild/openharmony-arm64": 0.28.0 + "@esbuild/sunos-x64": 0.28.0 + "@esbuild/win32-arm64": 0.28.0 + "@esbuild/win32-ia32": 0.28.0 + "@esbuild/win32-x64": 0.28.0 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: eeedcd8631138525908c185a6f987ca502ab2a523054f0305f458814caeb5ead8f51481e23b995b56326d8f232f09a7663182e0041a13f65d7f589d8a4ee76a4 languageName: node linkType: hard -"ethers@npm:~6.16.0": +"ethers@npm:^6.13.5, ethers@npm:~6.16.0": version: 6.16.0 resolution: "ethers@npm:6.16.0" dependencies: @@ -1270,6 +1584,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 + languageName: node + linkType: hard + "execa@npm:^7.1.1": version: 7.2.0 resolution: "execa@npm:7.2.0" @@ -1346,7 +1667,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -1356,7 +1677,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@2.3.3#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": +"fsevents@patch:fsevents@~2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -1435,6 +1756,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.7": + version: 1.0.7 + resolution: "isows@npm:1.0.7" + peerDependencies: + ws: "*" + checksum: 044b949b369872882af07b60b613b5801ae01b01a23b5b72b78af80c8103bbeed38352c3e8ceff13a7834bc91fd2eb41cf91ec01d59a041d8705680e6b0ec546 + languageName: node + linkType: hard + "k-bucket@npm:^5.0.0, k-bucket@npm:^5.1.0": version: 5.1.0 resolution: "k-bucket@npm:5.1.0" @@ -1522,17 +1852,19 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:5.20260801.0-alpha": - version: 5.20260801.0-alpha - resolution: "miniflare@npm:5.20260801.0-alpha" +"miniflare@npm:4.20260515.0": + version: 4.20260515.0 + resolution: "miniflare@npm:4.20260515.0" dependencies: "@cspotcode/source-map-support": 0.8.1 - sharp: 0.35.2 - undici: 7.28.0 - workerd: 1.20260801.1 - ws: 8.21.0 + sharp: ^0.34.5 + undici: 7.24.8 + workerd: 1.20260515.1 + ws: 8.18.0 youch: 4.1.0-beta.10 - checksum: 71039bc02c29de0f14a7cc6a9da9aa7019349d06473c134f5b4831b08f695c254c78dc7769eb1b6bc35d19705c1ab1ad409e308f1a0b1f377cd559d4393b0b7c + bin: + miniflare: bootstrap.js + checksum: 7293667957d634d776c5f7792589e224450b362c497b2d87dd096bd47096716c69427a0ac42bccbb6faf427ab0b25eb1eaa9bca093504a1ae2ae36f77cab92ab languageName: node linkType: hard @@ -1581,11 +1913,11 @@ __metadata: linkType: hard "node-abi@npm:^3.3.0": - version: 3.94.0 - resolution: "node-abi@npm:3.94.0" + version: 3.93.0 + resolution: "node-abi@npm:3.93.0" dependencies: semver: ^7.3.5 - checksum: 0d79587a609fb2e4d152b4d648b32a89b42c9963399ca62c88b0f4d91c7bb4dd1f806c5cd771bd925380561923df5e4cd6e30bbc8133b02442136ec4afebc726 + checksum: b0f4c97e418419893a93a019033559d6f2add72c7e92460ca166907b86afaa907365178587ee787da44e960d97a3d0c9744e0107613465a407543a38a64ce845 languageName: node linkType: hard @@ -1693,6 +2025,27 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.14.33": + version: 0.14.33 + resolution: "ox@npm:0.14.33" + dependencies: + "@adraffy/ens-normalize": ^1.11.0 + "@noble/ciphers": ^1.3.0 + "@noble/curves": 1.9.1 + "@noble/hashes": ^1.8.0 + "@scure/bip32": ^1.7.0 + "@scure/bip39": ^1.6.0 + abitype: ^1.2.3 + eventemitter3: 5.0.1 + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: bfc5bc8aa28f39b8ce715125bff46e86c73142a7aed1a585893c01326fbb5b0f9474d96815c80647b42e92ef1e28fd9e8dec275d93cf4526f5ce4c38f4a231f5 + languageName: node + linkType: hard + "path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -1811,40 +2164,37 @@ __metadata: linkType: hard "rollup@npm:^4.14.1": - version: 4.62.4 - resolution: "rollup@npm:4.62.4" - dependencies: - "@napi-rs/lzma-linux-x64-gnu": 1.5.1 - "@rollup/rollup-android-arm-eabi": 4.62.4 - "@rollup/rollup-android-arm64": 4.62.4 - "@rollup/rollup-darwin-arm64": 4.62.4 - "@rollup/rollup-darwin-x64": 4.62.4 - "@rollup/rollup-freebsd-arm64": 4.62.4 - "@rollup/rollup-freebsd-x64": 4.62.4 - "@rollup/rollup-linux-arm-gnueabihf": 4.62.4 - "@rollup/rollup-linux-arm-musleabihf": 4.62.4 - "@rollup/rollup-linux-arm64-gnu": 4.62.4 - "@rollup/rollup-linux-arm64-musl": 4.62.4 - "@rollup/rollup-linux-loong64-gnu": 4.62.4 - "@rollup/rollup-linux-loong64-musl": 4.62.4 - "@rollup/rollup-linux-ppc64-gnu": 4.62.4 - "@rollup/rollup-linux-ppc64-musl": 4.62.4 - "@rollup/rollup-linux-riscv64-gnu": 4.62.4 - "@rollup/rollup-linux-riscv64-musl": 4.62.4 - "@rollup/rollup-linux-s390x-gnu": 4.62.4 - "@rollup/rollup-linux-x64-gnu": 4.62.4 - "@rollup/rollup-linux-x64-musl": 4.62.4 - "@rollup/rollup-openbsd-x64": 4.62.4 - "@rollup/rollup-openharmony-arm64": 4.62.4 - "@rollup/rollup-win32-arm64-msvc": 4.62.4 - "@rollup/rollup-win32-ia32-msvc": 4.62.4 - "@rollup/rollup-win32-x64-gnu": 4.62.4 - "@rollup/rollup-win32-x64-msvc": 4.62.4 + version: 4.62.2 + resolution: "rollup@npm:4.62.2" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.62.2 + "@rollup/rollup-android-arm64": 4.62.2 + "@rollup/rollup-darwin-arm64": 4.62.2 + "@rollup/rollup-darwin-x64": 4.62.2 + "@rollup/rollup-freebsd-arm64": 4.62.2 + "@rollup/rollup-freebsd-x64": 4.62.2 + "@rollup/rollup-linux-arm-gnueabihf": 4.62.2 + "@rollup/rollup-linux-arm-musleabihf": 4.62.2 + "@rollup/rollup-linux-arm64-gnu": 4.62.2 + "@rollup/rollup-linux-arm64-musl": 4.62.2 + "@rollup/rollup-linux-loong64-gnu": 4.62.2 + "@rollup/rollup-linux-loong64-musl": 4.62.2 + "@rollup/rollup-linux-ppc64-gnu": 4.62.2 + "@rollup/rollup-linux-ppc64-musl": 4.62.2 + "@rollup/rollup-linux-riscv64-gnu": 4.62.2 + "@rollup/rollup-linux-riscv64-musl": 4.62.2 + "@rollup/rollup-linux-s390x-gnu": 4.62.2 + "@rollup/rollup-linux-x64-gnu": 4.62.2 + "@rollup/rollup-linux-x64-musl": 4.62.2 + "@rollup/rollup-openbsd-x64": 4.62.2 + "@rollup/rollup-openharmony-arm64": 4.62.2 + "@rollup/rollup-win32-arm64-msvc": 4.62.2 + "@rollup/rollup-win32-ia32-msvc": 4.62.2 + "@rollup/rollup-win32-x64-gnu": 4.62.2 + "@rollup/rollup-win32-x64-msvc": 4.62.2 "@types/estree": 1.0.9 fsevents: ~2.3.2 dependenciesMeta: - "@napi-rs/lzma-linux-x64-gnu": - optional: true "@rollup/rollup-android-arm-eabi": optional: true "@rollup/rollup-android-arm64": @@ -1899,7 +2249,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: bf310620a92b608ea884de9353c974132edd099a547028f8b0ae4fb11705d590539054aa455affece154f259d05fadd11dd0596278a1d04f1af93657bc0eb3d5 + checksum: 9fb4b08e135db5968e1763a3f95ece6f78e64f135159a59bfc678d3f8ab80a7a0080022611a9ccec73346a71b82661d36f55ea7ccdc638bdad0255d96ff8e7ba languageName: node linkType: hard @@ -1918,60 +2268,57 @@ __metadata: linkType: hard "sax@npm:>=0.6.0": - version: 1.6.1 - resolution: "sax@npm:1.6.1" - checksum: be78ded08c77db1644a8d32c93266615f51b925c8b6b6f3f689aed10eae57f307f8fbd7bcbf502041903edcfec17ea7d07c7bb4960e076faa92fc6773b621f2a + version: 1.6.0 + resolution: "sax@npm:1.6.0" + checksum: 83ae2a17f524bd35b1b7d1c867700b1fab41e4cbb4f7635b7e66398421e06ff2cd43ec651c151cb99c67c3681ec7d0493cb6a98fd2e7799ea15b5d0a4615f870 languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.8.4": - version: 7.8.5 - resolution: "semver@npm:7.8.5" +"semver@npm:^7.3.5, semver@npm:^7.7.3": + version: 7.8.0 + resolution: "semver@npm:7.8.0" bin: semver: bin/semver.js - checksum: 0c580f17e88e2b45806dc5cf3d824f719c946999d3554bf30307c2b68b3300ab3c8bfcd84d8f489b93b4cbb5362f5fc8de4d2858954f18d834253c4450fc9f6b - languageName: node - linkType: hard - -"sharp@npm:0.35.2": - version: 0.35.2 - resolution: "sharp@npm:0.35.2" - dependencies: - "@img/colour": ^1.1.0 - "@img/sharp-darwin-arm64": 0.35.2 - "@img/sharp-darwin-x64": 0.35.2 - "@img/sharp-freebsd-wasm32": 0.35.2 - "@img/sharp-libvips-darwin-arm64": 1.3.1 - "@img/sharp-libvips-darwin-x64": 1.3.1 - "@img/sharp-libvips-linux-arm": 1.3.1 - "@img/sharp-libvips-linux-arm64": 1.3.1 - "@img/sharp-libvips-linux-ppc64": 1.3.1 - "@img/sharp-libvips-linux-riscv64": 1.3.1 - "@img/sharp-libvips-linux-s390x": 1.3.1 - "@img/sharp-libvips-linux-x64": 1.3.1 - "@img/sharp-libvips-linuxmusl-arm64": 1.3.1 - "@img/sharp-libvips-linuxmusl-x64": 1.3.1 - "@img/sharp-linux-arm": 0.35.2 - "@img/sharp-linux-arm64": 0.35.2 - "@img/sharp-linux-ppc64": 0.35.2 - "@img/sharp-linux-riscv64": 0.35.2 - "@img/sharp-linux-s390x": 0.35.2 - "@img/sharp-linux-x64": 0.35.2 - "@img/sharp-linuxmusl-arm64": 0.35.2 - "@img/sharp-linuxmusl-x64": 0.35.2 - "@img/sharp-webcontainers-wasm32": 0.35.2 - "@img/sharp-win32-arm64": 0.35.2 - "@img/sharp-win32-ia32": 0.35.2 - "@img/sharp-win32-x64": 0.35.2 + checksum: 68e38bc26ed1191d7c78d2b711bdffc2f8b1d05a1caadda41a1d7e1a9d32e1da5ae5b645de5c5f2b27bde830d7e9c1cbeeafcb8fda091830411df7d40be405b1 + languageName: node + linkType: hard + +"sharp@npm:^0.34.5": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": ^1.0.0 + "@img/sharp-darwin-arm64": 0.34.5 + "@img/sharp-darwin-x64": 0.34.5 + "@img/sharp-libvips-darwin-arm64": 1.2.4 + "@img/sharp-libvips-darwin-x64": 1.2.4 + "@img/sharp-libvips-linux-arm": 1.2.4 + "@img/sharp-libvips-linux-arm64": 1.2.4 + "@img/sharp-libvips-linux-ppc64": 1.2.4 + "@img/sharp-libvips-linux-riscv64": 1.2.4 + "@img/sharp-libvips-linux-s390x": 1.2.4 + "@img/sharp-libvips-linux-x64": 1.2.4 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + "@img/sharp-linux-arm": 0.34.5 + "@img/sharp-linux-arm64": 0.34.5 + "@img/sharp-linux-ppc64": 0.34.5 + "@img/sharp-linux-riscv64": 0.34.5 + "@img/sharp-linux-s390x": 0.34.5 + "@img/sharp-linux-x64": 0.34.5 + "@img/sharp-linuxmusl-arm64": 0.34.5 + "@img/sharp-linuxmusl-x64": 0.34.5 + "@img/sharp-wasm32": 0.34.5 + "@img/sharp-win32-arm64": 0.34.5 + "@img/sharp-win32-ia32": 0.34.5 + "@img/sharp-win32-x64": 0.34.5 detect-libc: ^2.1.2 - semver: ^7.8.4 + semver: ^7.7.3 dependenciesMeta: "@img/sharp-darwin-arm64": optional: true "@img/sharp-darwin-x64": optional: true - "@img/sharp-freebsd-wasm32": - optional: true "@img/sharp-libvips-darwin-arm64": optional: true "@img/sharp-libvips-darwin-x64": @@ -2008,7 +2355,7 @@ __metadata: optional: true "@img/sharp-linuxmusl-x64": optional: true - "@img/sharp-webcontainers-wasm32": + "@img/sharp-wasm32": optional: true "@img/sharp-win32-arm64": optional: true @@ -2016,7 +2363,7 @@ __metadata: optional: true "@img/sharp-win32-x64": optional: true - checksum: fb286642ed5865cdeaf3f8878f2cab82f7621535f128d43571f73849a371e6196fc647c5562c0ecc7c4869ca6cd36ce942d3e8e21e2d2d8bcfb2d6e2e4258571 + checksum: b86972729697af7e37c96714cd9c5c2470c6b503a79d5b38f6fd3eb4d5a46b20d7c15dae1a73db3d0e0aa605d517f2f66d4f52de7496bfb037dd7feb930c1899 languageName: node linkType: hard @@ -2140,9 +2487,9 @@ __metadata: linkType: hard "tokenx@npm:^1.3.0": - version: 1.6.0 - resolution: "tokenx@npm:1.6.0" - checksum: 9104986109f65a04bc3c148b442df5095abfd9786b1d8d218545cd1e3718c70f09f947757668c21f4b3318f30a6a3d9963ce60bae80348abf51cb12f9b19185e + version: 1.3.0 + resolution: "tokenx@npm:1.3.0" + checksum: b89896b25de631bcc6f0e2215ba7a8a726bb6f654a19ad578ebc897880811c279c5824644447ff2c95c1f6a6164a3dd1109c38a046883e24bccf3a80e3bcdaf9 languageName: node linkType: hard @@ -2161,8 +2508,8 @@ __metadata: linkType: hard "tsx@npm:^4.22.4": - version: 4.23.9 - resolution: "tsx@npm:4.23.9" + version: 4.22.4 + resolution: "tsx@npm:4.22.4" dependencies: esbuild: ~0.28.0 fsevents: ~2.3.3 @@ -2171,7 +2518,7 @@ __metadata: optional: true bin: tsx: dist/cli.mjs - checksum: 4fd4618783d0f9f4a9f5bcf768efd3fb3fbcef35f0bd1cf125d2e0a119a32036c8713b0adf7787750a8d0c9cde767fcf52001d4f8c535085fad895e3322f8807 + checksum: 21848c642032680c525d322d7e6b82fdf2bebfaa8b2911c413eb98a834fce89295ee315fb42de5597f4c2ba1ea06c4b71c2321ca47ecf1de065604324266c554 languageName: node linkType: hard @@ -2205,9 +2552,11 @@ __metadata: linkType: hard "uint8-util@npm:^2.2.2": - version: 2.3.2 - resolution: "uint8-util@npm:2.3.2" - checksum: 5d13a2cd3b42c29740ff29cbd83281e0ddbe2e520276d349a3c7a2a93261e2fc0365927a18eaa1d7633f2f37bdb59edd1ce246ac4c00298aa93dd9b98c01fa8a + version: 2.2.6 + resolution: "uint8-util@npm:2.2.6" + dependencies: + base64-arraybuffer: ^1.0.2 + checksum: 77147e22051c25eca6822a0c3e79800bdd11324bd0611a3741d832b16bb4e37252d064bacae165ae0eff574d3ab68bb230a65007de5965362dda06a30f437499 languageName: node linkType: hard @@ -2225,10 +2574,10 @@ __metadata: languageName: node linkType: hard -"undici@npm:7.28.0": - version: 7.28.0 - resolution: "undici@npm:7.28.0" - checksum: 57c77c4ee75e166ef361ad238fcd88816fbc0d058aeadf4c0f689194bd17eb6d63ce25b89bd4f4d65a380ebbde784bf4886ec51acc9f8c826ecab01d8f63aaac +"undici@npm:7.24.8": + version: 7.24.8 + resolution: "undici@npm:7.24.8" + checksum: f691321cad3589e069bb0282b3862f581e4b692efde54857c4492f701380372fbd4f77f6a579674a133c5ca5c20efb44c3461803d16886beef489ac8690bbea4 languageName: node linkType: hard @@ -2262,6 +2611,27 @@ __metadata: languageName: node linkType: hard +"viem@npm:^2.38.3": + version: 2.55.11 + resolution: "viem@npm:2.55.11" + dependencies: + "@noble/curves": 1.9.1 + "@noble/hashes": 1.8.0 + "@scure/bip32": 1.7.0 + "@scure/bip39": 1.6.0 + abitype: 1.2.3 + isows: 1.0.7 + ox: 0.14.33 + ws: 8.21.0 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 8afa0167e8238fa1cb11dba3de2a69fb308fa2b11378ab69ef81ab4b7cda65d9f2c2aabc67861aad39091ee819f6701cd2f60dc46adda877bb5b17eb98cac105 + languageName: node + linkType: hard + "web-streams-polyfill@npm:^3.0.3": version: 3.3.3 resolution: "web-streams-polyfill@npm:3.3.3" @@ -2291,15 +2661,15 @@ __metadata: languageName: node linkType: hard -"workerd@npm:1.20260801.1": - version: 1.20260801.1 - resolution: "workerd@npm:1.20260801.1" +"workerd@npm:1.20260515.1": + version: 1.20260515.1 + resolution: "workerd@npm:1.20260515.1" dependencies: - "@cloudflare/workerd-darwin-64": 1.20260801.1 - "@cloudflare/workerd-darwin-arm64": 1.20260801.1 - "@cloudflare/workerd-linux-64": 1.20260801.1 - "@cloudflare/workerd-linux-arm64": 1.20260801.1 - "@cloudflare/workerd-windows-64": 1.20260801.1 + "@cloudflare/workerd-darwin-64": 1.20260515.1 + "@cloudflare/workerd-darwin-arm64": 1.20260515.1 + "@cloudflare/workerd-linux-64": 1.20260515.1 + "@cloudflare/workerd-linux-arm64": 1.20260515.1 + "@cloudflare/workerd-windows-64": 1.20260515.1 dependenciesMeta: "@cloudflare/workerd-darwin-64": optional: true @@ -2313,25 +2683,25 @@ __metadata: optional: true bin: workerd: bin/workerd - checksum: caea23a718daaa9822b0e78992514d825fb73662cc56abcba5d0fe3e973cd3061ce3e8ba067e9c431d2f26218d7a7e3847b7162dd264da76138afc6aed74536e + checksum: d8dcb084b74c3eea7841af3fcf32d95fc979d1c951b1a560711a0f4729ad4e234883d09395e937b6310726cfe14493106ed917381f7e9f98cc068bbeda69fb67 languageName: node linkType: hard "wrangler@npm:^4.18.0": - version: 4.119.0 - resolution: "wrangler@npm:4.119.0" + version: 4.92.0 + resolution: "wrangler@npm:4.92.0" dependencies: "@cloudflare/kv-asset-handler": 0.5.0 "@cloudflare/unenv-preset": 2.16.1 blake3-wasm: 2.1.5 - esbuild: 0.28.1 - fsevents: 2.3.3 - miniflare: 5.20260801.0-alpha + esbuild: 0.27.3 + fsevents: ~2.3.2 + miniflare: 4.20260515.0 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260801.1 + workerd: 1.20260515.1 peerDependencies: - "@cloudflare/workers-types": ^5.20260801.1 + "@cloudflare/workers-types": ^4.20260515.1 dependenciesMeta: fsevents: optional: true @@ -2339,10 +2709,9 @@ __metadata: "@cloudflare/workers-types": optional: true bin: - cf-wrangler: bin/cf-wrangler.js wrangler: bin/wrangler.js wrangler2: bin/wrangler.js - checksum: 98b327c42444672202b2bea23e20c17141228a7575e29fedc8829e20b495bbf5d4d4429f128871710855c69e13944a8dc0f75b123c6843b00e03ea720b734c0b + checksum: a6fb117186700f0530645dcab917947b1bc3d27ee0fcd33fd7c5a2578f05ae3617799233750feca82906fe0440ca6c86d4869ce6e79c325d2d3172cd809480d8 languageName: node linkType: hard @@ -2368,6 +2737,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975 + languageName: node + linkType: hard + "ws@npm:8.21.0": version: 8.21.0 resolution: "ws@npm:8.21.0" From 1c98baea6300f2d7cee42696871e662d21ef578f Mon Sep 17 00:00:00 2001 From: sirpy Date: Fri, 7 Aug 2026 11:57:08 +0300 Subject: [PATCH 24/25] fix: daily streams --- backend/src/analytics.ts | 22 +++++++++------------- backend/src/worker.ts | 3 ++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index aa694ec..aa8617d 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -142,8 +142,9 @@ export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Pro const knownBuyers = await store.getBuyerRegistry(); const discoveredBuyers = new Set(); + console.log("Collecting celo metrics..."); const celoMetrics = await collectCeloDayMetrics(cfg, celoClient, dayWindow, aggregate, discoveredBuyers); - + console.log("Collecting stream metrics..."); const streamMetrics = await collectStreamDayMetrics(cfg, dayWindow, aggregate, windowEnd, discoveredBuyers); if (discoveredBuyers.size > 0) { await store.addBuyersToRegistry([...discoveredBuyers]); @@ -151,7 +152,6 @@ export async function runAnalyticsAggregation(env: Env, runAt = new Date()): Pro } logInfo("getting base metrics...."); const baseMetrics = await collectBaseDayMetrics(cfg, baseClient, dayWindow, aggregate, knownBuyers); - logInfo("got base metrics...."); logInfo("building dialy reocrd...."); const dailyRecord = buildDailyRecord(currentDate, aggregate, windowEnd); @@ -453,7 +453,7 @@ async function collectStreamDayMetrics( now: Date, discoveredBuyers: Set ): Promise<{ senders: number; totalFlowRateWeiPerSecond: string }> { - const snapshots = await fetchStreamSnapshots(cfg, now, dayWindow.startUnix); + const snapshots = await fetchStreamSnapshots(cfg, now, dayWindow.startUnix, dayWindow.endUnix); for (const snapshot of snapshots) { if (snapshot.buyerAddress) { @@ -495,7 +495,7 @@ function addDailyToGlobal(global: AnalyticsGlobalTotals, day: AnalyticsDailyReco }; } -async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUnix: number): Promise { +async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUnix: number, dayEndUnix: number): Promise { if (!cfg.celoSuperTokenAddress || !cfg.celoStreamReceiverAddress) { logWarn("analytics.streams.skipped", { reason: "missing_config", @@ -518,8 +518,8 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni streamPeriods( where: { or: [ - { receiver: $receiver, token: $token, stoppedAtTimestamp: null } - { receiver: $receiver, token: $token, stoppedAtTimestamp_gt: $daysago } + { receiver: $receiver, token: $token, stoppedAtTimestamp: null, startedAtTimestamp_lte: $until }, + { receiver: $receiver, token: $token, stoppedAtTimestamp_gt: $daysago, startedAtTimestamp_lte: $until } ] } first: $first @@ -539,6 +539,7 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni receiver: cfg.celoStreamReceiverAddress, token: cfg.celoSuperTokenAddress, daysago: String(dayStartUnix), + until: String(dayEndUnix), first: pageSize, skip } @@ -732,14 +733,7 @@ async function getChainRpcUrls(kv: KV, chainId: number, fallbackRpcs: string[]): } async function fetchRpcsFromChainlist(chainId: number): Promise { - const ALLOWED_DOMAINS = ["chainlist.org"]; const chainlistUrl = new URL(CHAINLIST_RPCS_URL); - if (!ALLOWED_DOMAINS.includes(chainlistUrl.hostname)) { - throw new Error("Chainlist domain not allowed"); - } - if (!["http:", "https:"].includes(chainlistUrl.protocol)) { - throw new Error("Chainlist protocol not allowed"); - } const response = await retryWithBackoff(() => fetch(chainlistUrl.href), 3, 500); if (!response.ok) { @@ -752,6 +746,8 @@ async function fetchRpcsFromChainlist(chainId: number): Promise { }>; const chain = payload.find((entry) => entry.chainId === chainId); + console.log("got rpcs from chainlist", { chainId }); + if (!chain?.rpc) { return []; } diff --git a/backend/src/worker.ts b/backend/src/worker.ts index 464e890..1af2a1b 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -9,7 +9,7 @@ import { getAnalyticsWindow, runAnalyticsAggregation, KVAnalyticsStore } from ". const ANALYTICS_REFRESH_COOLDOWN_MS = 60 * 60 * 1000; const ANALYTICS_REFRESH_LAST_RUN_KEY = "analytics:refresh:last-run-at"; -const ANALYTICS_CRON_BACKFILL_DAYS = 30; +const ANALYTICS_CRON_BACKFILL_DAYS = 40; const ANALYTICS_CRON_BACKFILL_CURSOR_KEY = "analytics:cron:backfill-cursor"; const CeloEventsRecordSchema = z @@ -255,6 +255,7 @@ async function backFillAnalytics(env: Env, maxRunsPerTick = 2) { console.log(`analytics backfill run ${i + 1} of ${maxRunsPerTick}, cursorDate=${cursorDate}`); const aggregationRunAt = cursorDate === todayDate ? runAt : new Date(`${cursorDate}T23:59:59.999Z`); const analyticsSummary = await runAnalyticsAggregation(env, aggregationRunAt); + console.log(`analytics backfill run ${i + 1} of ${maxRunsPerTick}, cursorDate=${cursorDate} completed`, analyticsSummary); summaries.push({ currentDate: analyticsSummary.currentDate, finalizedDates: analyticsSummary.finalizedDates From 8e989b28621252be19edfa79935b88fca5a37659 Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 9 Aug 2026 10:07:46 +0300 Subject: [PATCH 25/25] fix: subgraph query --- backend/src/analytics.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/analytics.ts b/backend/src/analytics.ts index aa8617d..a5659bd 100644 --- a/backend/src/analytics.ts +++ b/backend/src/analytics.ts @@ -511,10 +511,11 @@ async function fetchStreamSnapshots(cfg: AnalyticsConfig, now: Date, dayStartUni const nowUnix = Math.floor(now.getTime() / 1000); // get streams that are either active or stopped within the day window + let body = {}; while (true) { - const body = { + body = { query: ` - query StreamPeriodsPage($receiver: String!, $token: String!, $daysago: BigInt!, $first: Int!, $skip: Int!) { + query StreamPeriodsPage($receiver: String!, $token: String!, $daysago: BigInt!,$until:BigInt!, $first: Int!, $skip: Int!) { streamPeriods( where: { or: [