hood-alerts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +369 -0
- package/dist/bot/index.cjs +1008 -0
- package/dist/bot/index.cjs.map +1 -0
- package/dist/bot/index.d.cts +241 -0
- package/dist/bot/index.d.ts +241 -0
- package/dist/bot/index.js +30 -0
- package/dist/bot/index.js.map +1 -0
- package/dist/chunk-32GS6XVE.js +732 -0
- package/dist/chunk-32GS6XVE.js.map +1 -0
- package/dist/chunk-3LHCQA4Z.js +76 -0
- package/dist/chunk-3LHCQA4Z.js.map +1 -0
- package/dist/chunk-3NHLSKZE.js +1 -0
- package/dist/chunk-3NHLSKZE.js.map +1 -0
- package/dist/chunk-6JQK5G3Z.js +72 -0
- package/dist/chunk-6JQK5G3Z.js.map +1 -0
- package/dist/chunk-CBNZ6AZT.js +598 -0
- package/dist/chunk-CBNZ6AZT.js.map +1 -0
- package/dist/chunk-CIURPPOW.js +1 -0
- package/dist/chunk-CIURPPOW.js.map +1 -0
- package/dist/chunk-IUOEGBF6.js +49 -0
- package/dist/chunk-IUOEGBF6.js.map +1 -0
- package/dist/chunk-NBMINJ5E.js +230 -0
- package/dist/chunk-NBMINJ5E.js.map +1 -0
- package/dist/chunk-O2RY2SWN.js +1 -0
- package/dist/chunk-O2RY2SWN.js.map +1 -0
- package/dist/chunk-PLMVKTQM.js +1 -0
- package/dist/chunk-PLMVKTQM.js.map +1 -0
- package/dist/chunk-RSTLW7XH.js +130 -0
- package/dist/chunk-RSTLW7XH.js.map +1 -0
- package/dist/chunk-SOBZ2FPP.js +355 -0
- package/dist/chunk-SOBZ2FPP.js.map +1 -0
- package/dist/chunk-VC5MKEY2.js +170 -0
- package/dist/chunk-VC5MKEY2.js.map +1 -0
- package/dist/chunk-W2GUAWUR.js +147 -0
- package/dist/chunk-W2GUAWUR.js.map +1 -0
- package/dist/chunk-Y2M3USWZ.js +355 -0
- package/dist/chunk-Y2M3USWZ.js.map +1 -0
- package/dist/chunk-Z4DJPESQ.js +1216 -0
- package/dist/chunk-Z4DJPESQ.js.map +1 -0
- package/dist/chunk-ZDM5VNWB.js +1 -0
- package/dist/chunk-ZDM5VNWB.js.map +1 -0
- package/dist/events/index.cjs +820 -0
- package/dist/events/index.cjs.map +1 -0
- package/dist/events/index.d.cts +58 -0
- package/dist/events/index.d.ts +58 -0
- package/dist/events/index.js +69 -0
- package/dist/events/index.js.map +1 -0
- package/dist/format-6RG4OcyE.d.ts +57 -0
- package/dist/format-B7d40f65.d.cts +57 -0
- package/dist/index.cjs +4122 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-3phvV_fl.d.cts +185 -0
- package/dist/logger-CYza_0eU.d.ts +185 -0
- package/dist/logs-D7sYBvSy.d.cts +66 -0
- package/dist/logs-D7sYBvSy.d.ts +66 -0
- package/dist/notifiers/index.cjs +620 -0
- package/dist/notifiers/index.cjs.map +1 -0
- package/dist/notifiers/index.d.cts +186 -0
- package/dist/notifiers/index.d.ts +186 -0
- package/dist/notifiers/index.js +70 -0
- package/dist/notifiers/index.js.map +1 -0
- package/dist/pricing-BumgLpvB.d.cts +79 -0
- package/dist/pricing-BumgLpvB.d.ts +79 -0
- package/dist/ratelimit-BKVQG2oY.d.cts +42 -0
- package/dist/ratelimit-CGLlXNIS.d.ts +42 -0
- package/dist/registry-D3We8Lg6.d.ts +50 -0
- package/dist/registry-DePA3IyS.d.cts +50 -0
- package/dist/rules/index.cjs +614 -0
- package/dist/rules/index.cjs.map +1 -0
- package/dist/rules/index.d.cts +166 -0
- package/dist/rules/index.d.ts +166 -0
- package/dist/rules/index.js +60 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/schema-DuDMPqpq.d.ts +183 -0
- package/dist/schema-NnIdKFl2.d.cts +183 -0
- package/dist/service/index.cjs +3774 -0
- package/dist/service/index.cjs.map +1 -0
- package/dist/service/index.d.cts +262 -0
- package/dist/service/index.d.ts +262 -0
- package/dist/service/index.js +45 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/main.cjs +3759 -0
- package/dist/service/main.cjs.map +1 -0
- package/dist/service/main.d.cts +1 -0
- package/dist/service/main.d.ts +1 -0
- package/dist/service/main.js +60 -0
- package/dist/service/main.js.map +1 -0
- package/dist/sources-CAaQR6-N.d.ts +159 -0
- package/dist/sources-CpDOUlzx.d.cts +159 -0
- package/dist/telegram-CpodYJrP.d.cts +64 -0
- package/dist/telegram-pvphmBwn.d.ts +64 -0
- package/dist/tiers/index.cjs +471 -0
- package/dist/tiers/index.cjs.map +1 -0
- package/dist/tiers/index.d.cts +231 -0
- package/dist/tiers/index.d.ts +231 -0
- package/dist/tiers/index.js +34 -0
- package/dist/tiers/index.js.map +1 -0
- package/dist/types-C0ETog04.d.ts +109 -0
- package/dist/types-CcoKpStA.d.cts +109 -0
- package/dist/types-DFPpTMWo.d.cts +186 -0
- package/dist/types-DFPpTMWo.d.ts +186 -0
- package/package.json +114 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/service/config.ts","../src/service/codec.ts","../src/service/store.ts","../src/service/dispatcher.ts","../src/service/http.ts","../src/service/runner.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { LogLevel } from './logger.js'\n\n/**\n * Service configuration, validated at startup.\n *\n * Every value is checked before anything connects: a malformed\n * `WHALE_MIN_USD` should fail in the first second with a clear message, not\n * three hours later as a `NaN` comparison that silently matched every swap.\n */\n\nconst bool = (defaultValue: boolean) =>\n z\n .string()\n .optional()\n .transform((value) => {\n if (value === undefined || value.trim() === '') return defaultValue\n return /^(1|true|yes|on)$/i.test(value.trim())\n })\n\nconst int = (defaultValue: number, min: number, max: number) =>\n z\n .string()\n .optional()\n .transform((value) => (value === undefined || value.trim() === '' ? defaultValue : Number(value)))\n .refine((value) => Number.isFinite(value) && value >= min && value <= max, {\n message: `must be a number between ${min} and ${max}`,\n })\n\nconst bigintValue = (defaultValue: bigint) =>\n z\n .string()\n .optional()\n .transform((value) => (value === undefined || value.trim() === '' ? defaultValue : BigInt(value.trim())))\n\nconst csv = z\n .string()\n .optional()\n .transform((value) =>\n (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean),\n )\n\nconst addressValue = z\n .string()\n .regex(/^0x[0-9a-fA-F]{40}$/, 'must be a 0x-prefixed address')\n .optional()\n\nconst envSchema = z.object({\n NETWORK: z.enum(['mainnet', 'testnet']).optional().default('mainnet'),\n ROBINHOOD_RPC_URL: z.string().url().optional(),\n DB_PATH: z.string().optional().default('./data/hood-alerts.sqlite'),\n PORT: int(8080, 1, 65535),\n LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),\n DRY_RUN: bool(false),\n\n TELEGRAM_BOT_TOKEN: z.string().optional(),\n DISCORD_BOT_TOKEN: z.string().optional(),\n DISCORD_APPLICATION_ID: z.string().optional(),\n DISCORD_PUBLIC_KEY: z.string().optional(),\n DISCORD_INTERACTIONS_PATH: z.string().optional().default('/discord/interactions'),\n\n POLL_INTERVAL_MS: int(3_000, 250, 600_000),\n FLUSH_INTERVAL_MS: int(1_000, 100, 600_000),\n CONFIRMATIONS: bigintValue(2n),\n CHUNK_SIZE: bigintValue(2_000n),\n INITIAL_LOOKBACK_BLOCKS: bigintValue(5_000n),\n\n WHALE_MIN_USD: int(1_000, 0, 1_000_000_000),\n WHALE_POOL_LIMIT: int(400, 1, 5_000),\n\n FLUSH_BATCH_SIZE: int(50, 1, 1_000),\n MAX_DELIVERY_ATTEMPTS: int(6, 1, 50),\n RETRY_BACKOFF_MS: int(30_000, 100, 3_600_000),\n OUTBOX_RETENTION_HOURS: int(72, 1, 8_760),\n\n ENTITLEMENTS: z.enum(['static', 'usdg', 'both']).optional().default('static'),\n PREMIUM_SUBSCRIBERS: csv,\n ALL_PREMIUM: bool(false),\n USDG_RECEIVER: addressValue,\n PREMIUM_PRICE_USDG: int(25, 1, 1_000_000),\n PREMIUM_PERIOD_DAYS: int(30, 1, 3_650),\n PAYMENTS_FROM_BLOCK: bigintValue(0n),\n UPGRADE_INSTRUCTIONS: z.string().optional(),\n})\n\n/** The validated service configuration. */\nexport interface ServiceConfig {\n network: 'mainnet' | 'testnet'\n rpcUrl: string | undefined\n dbPath: string\n port: number\n logLevel: LogLevel\n dryRun: boolean\n\n telegramBotToken: string | undefined\n discordBotToken: string | undefined\n discordApplicationId: string | undefined\n discordPublicKey: string | undefined\n discordInteractionsPath: string\n\n pollIntervalMs: number\n flushIntervalMs: number\n confirmations: bigint\n chunkSize: bigint\n initialLookbackBlocks: bigint\n\n whaleMinUsd: number\n whalePoolLimit: number\n\n flushBatchSize: number\n maxDeliveryAttempts: number\n retryBackoffMs: number\n outboxRetentionHours: number\n\n entitlements: 'static' | 'usdg' | 'both'\n premiumSubscribers: string[]\n allPremium: boolean\n usdgReceiver: `0x${string}` | undefined\n premiumPriceUsdg: number\n premiumPeriodDays: number\n paymentsFromBlock: bigint\n upgradeInstructions: string | undefined\n}\n\n/**\n * Load and validate configuration from the environment.\n *\n * @throws with every problem listed at once, so a misconfigured deployment\n * needs one fix round rather than one per variable.\n *\n * @example\n * ```ts\n * const config = loadConfig()\n * console.log(`watching ${config.network} with ${config.confirmations} confirmations`)\n * ```\n */\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): ServiceConfig {\n const parsed = envSchema.safeParse(env)\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((issue) => `${issue.path.join('.') || 'env'}: ${issue.message}`)\n .join('\\n ')\n throw new Error(`hood-alerts: invalid configuration\\n ${detail}`)\n }\n const value = parsed.data\n\n if (value.ENTITLEMENTS !== 'static' && !value.USDG_RECEIVER) {\n throw new Error(\n 'hood-alerts: ENTITLEMENTS=usdg (or both) needs USDG_RECEIVER, the address subscribers pay.',\n )\n }\n if (value.DISCORD_APPLICATION_ID && !value.DISCORD_PUBLIC_KEY) {\n throw new Error(\n 'hood-alerts: DISCORD_APPLICATION_ID is set but DISCORD_PUBLIC_KEY is not. Interactions cannot be verified without it.',\n )\n }\n\n return {\n network: value.NETWORK,\n rpcUrl: value.ROBINHOOD_RPC_URL,\n dbPath: value.DB_PATH,\n port: value.PORT,\n logLevel: value.LOG_LEVEL,\n dryRun: value.DRY_RUN,\n\n telegramBotToken: value.TELEGRAM_BOT_TOKEN,\n discordBotToken: value.DISCORD_BOT_TOKEN,\n discordApplicationId: value.DISCORD_APPLICATION_ID,\n discordPublicKey: value.DISCORD_PUBLIC_KEY,\n discordInteractionsPath: value.DISCORD_INTERACTIONS_PATH,\n\n pollIntervalMs: value.POLL_INTERVAL_MS,\n flushIntervalMs: value.FLUSH_INTERVAL_MS,\n confirmations: value.CONFIRMATIONS,\n chunkSize: value.CHUNK_SIZE,\n initialLookbackBlocks: value.INITIAL_LOOKBACK_BLOCKS,\n\n whaleMinUsd: value.WHALE_MIN_USD,\n whalePoolLimit: value.WHALE_POOL_LIMIT,\n\n flushBatchSize: value.FLUSH_BATCH_SIZE,\n maxDeliveryAttempts: value.MAX_DELIVERY_ATTEMPTS,\n retryBackoffMs: value.RETRY_BACKOFF_MS,\n outboxRetentionHours: value.OUTBOX_RETENTION_HOURS,\n\n entitlements: value.ENTITLEMENTS,\n premiumSubscribers: value.PREMIUM_SUBSCRIBERS,\n allPremium: value.ALL_PREMIUM,\n usdgReceiver: value.USDG_RECEIVER as `0x${string}` | undefined,\n premiumPriceUsdg: value.PREMIUM_PRICE_USDG,\n premiumPeriodDays: value.PREMIUM_PERIOD_DAYS,\n paymentsFromBlock: value.PAYMENTS_FROM_BLOCK,\n upgradeInstructions: value.UPGRADE_INSTRUCTIONS,\n }\n}\n","import type { AlertEvent } from '../events/types.js'\n\n/**\n * JSON codec for alert events.\n *\n * Events carry `bigint` block numbers and raw token amounts, which\n * `JSON.stringify` refuses to serialize. Coercing them to `number` would\n * silently lose precision on any amount above 2^53 (routine for an 18-decimal\n * token), so bigints are tagged and restored exactly.\n */\n\nconst BIGINT_TAG = '$bigint'\n\n/** Serialize any value containing bigints. */\nexport function encode(value: unknown): string {\n return JSON.stringify(value, (_key, raw: unknown) =>\n typeof raw === 'bigint' ? { [BIGINT_TAG]: raw.toString() } : raw,\n )\n}\n\n/** Restore a value serialized by {@link encode}. */\nexport function decode<T>(text: string): T {\n return JSON.parse(text, (_key, raw: unknown) => {\n if (\n typeof raw === 'object' &&\n raw !== null &&\n Object.keys(raw).length === 1 &&\n typeof (raw as Record<string, unknown>)[BIGINT_TAG] === 'string'\n ) {\n return BigInt((raw as Record<string, string>)[BIGINT_TAG] as string)\n }\n return raw\n }) as T\n}\n\n/** Serialize an alert event for the outbox. */\nexport function encodeEvent(event: AlertEvent): string {\n return encode(event)\n}\n\n/** Restore an alert event from the outbox. */\nexport function decodeEvent(text: string): AlertEvent {\n return decode<AlertEvent>(text)\n}\n","import { createRequire } from 'node:module'\nimport { mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { getAddress, type Address } from 'viem'\nimport type { DatabaseSync as DatabaseSyncType } from 'node:sqlite'\nimport type { PoolEntry, PoolRegistry } from '../events/registry.js'\nimport type { Launchpad } from '../events/types.js'\nimport type { RateLimitStore } from '../rules/ratelimit.js'\nimport { parseSubscription, type Platform, type Subscription } from '../rules/schema.js'\nimport type { WalletLinkStore } from '../tiers/entitlements.js'\nimport { decode, encode } from './codec.js'\n\n// `node:sqlite` is a core module but is not in vite's static builtin list, so a\n// plain `import` gets mis-resolved under vitest (vite tries, and fails, to\n// bundle it as a bare package). `require()` is opaque to vite's ESM import\n// analysis, which sidesteps the issue; Node natively supports requiring core\n// modules from ESM through `createRequire`.\n//\n// The resolution base needs the `??`: in the CJS build there is no\n// `import.meta`, and the bundler's shim leaves `url` undefined, which\n// `createRequire` rejects outright. Any absolute path is a valid base here\n// because a core module never resolves relative to it.\nconst requireFromHere = createRequire(\n (import.meta.url as string | undefined) ?? `${process.cwd()}/`,\n)\nconst { DatabaseSync } = requireFromHere('node:sqlite') as typeof import('node:sqlite')\n\n/**\n * All durable service state, in one SQLite file, with no native dependency.\n *\n * The three invariants this schema exists to guarantee, in order of how badly\n * they bite when they are missing:\n *\n * 1. **A restart never double-sends.** Every potential delivery has a\n * deterministic primary key (`eventId:subscriptionId:ruleId`), and\n * `enqueue` is an `INSERT` that reports a uniqueness violation as \"already\n * queued\". Re-processing a block range after a crash regenerates the exact\n * same keys, so the second pass inserts nothing.\n * 2. **A restart never silently skips a range.** The block cursor advances\n * only after every event in a chunk has been enqueued and committed. A\n * crash mid-chunk leaves the cursor pointing at the start of that chunk, so\n * the range is simply re-read.\n * 3. **A crash between enqueue and send does not lose the alert.** Enqueued\n * rows live in the outbox until they are delivered or dead-lettered. On\n * startup, rows left in `sending` (a crash mid-flight) are returned to\n * `pending` and retried.\n *\n * The one honest caveat: if the process dies after the platform accepted a\n * message but before the row was marked `sent`, that alert is delivered twice.\n * Neither the Telegram nor the Discord send API exposes a client-supplied\n * idempotency key, so this window cannot be closed from here. Everything\n * outside it is exactly once.\n */\n\n/** Delivery lifecycle. */\nexport type OutboxStatus = 'pending' | 'sending' | 'sent' | 'failed' | 'dead'\n\n/** A queued delivery. */\nexport interface OutboxRow {\n id: string\n subscriptionId: string\n subscriberId: string\n ruleId: string\n eventId: string\n eventJson: string\n platform: Platform\n target: string\n status: OutboxStatus\n /** Earliest delivery time, ms since epoch. Carries the free-tier delay. */\n notBeforeMs: number\n attempts: number\n lastError: string | null\n createdAtMs: number\n updatedAtMs: number\n}\n\n/** Aggregate counters for `/metrics`. */\nexport interface StoreMetrics {\n subscriptions: number\n enabledSubscriptions: number\n outbox: Record<OutboxStatus, number>\n pools: number\n deliveriesLastHour: number\n cursors: { sourceId: string; nextBlock: bigint }[]\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS subscriptions (\n id TEXT PRIMARY KEY,\n subscriber_id TEXT NOT NULL,\n platform TEXT NOT NULL,\n target TEXT NOT NULL,\n enabled INTEGER NOT NULL DEFAULT 1,\n rules TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_subscriptions_subscriber ON subscriptions(subscriber_id);\n\nCREATE TABLE IF NOT EXISTS cursors (\n source_id TEXT PRIMARY KEY,\n next_block TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS outbox (\n id TEXT PRIMARY KEY,\n subscription_id TEXT NOT NULL,\n subscriber_id TEXT NOT NULL,\n rule_id TEXT NOT NULL,\n event_id TEXT NOT NULL,\n event_json TEXT NOT NULL,\n platform TEXT NOT NULL,\n target TEXT NOT NULL,\n status TEXT NOT NULL,\n not_before INTEGER NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_outbox_due ON outbox(status, not_before);\nCREATE INDEX IF NOT EXISTS idx_outbox_event ON outbox(event_id);\n\nCREATE TABLE IF NOT EXISTS deliveries (\n key TEXT NOT NULL,\n subscriber_id TEXT NOT NULL,\n at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_deliveries_key ON deliveries(key, at);\nCREATE INDEX IF NOT EXISTS idx_deliveries_subscriber ON deliveries(subscriber_id, at);\n\nCREATE TABLE IF NOT EXISTS pools (\n pool TEXT PRIMARY KEY,\n token TEXT NOT NULL,\n quote_token TEXT NOT NULL,\n launchpad TEXT NOT NULL,\n created_block TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_pools_token ON pools(token);\nCREATE INDEX IF NOT EXISTS idx_pools_created ON pools(created_block);\n\nCREATE TABLE IF NOT EXISTS wallet_links (\n subscriber_id TEXT PRIMARY KEY,\n address TEXT NOT NULL,\n linked_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS link_nonces (\n subscriber_id TEXT PRIMARY KEY,\n nonce TEXT NOT NULL,\n issued_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);\n`\n\ninterface SubscriptionRow {\n id: string\n subscriber_id: string\n platform: string\n target: string\n enabled: number\n rules: string\n created_at: number\n}\n\ninterface RawOutboxRow {\n id: string\n subscription_id: string\n subscriber_id: string\n rule_id: string\n event_id: string\n event_json: string\n platform: string\n target: string\n status: string\n not_before: number\n attempts: number\n last_error: string | null\n created_at: number\n updated_at: number\n}\n\n/** The service's durable state. */\nexport class AlertStore {\n private readonly db: DatabaseSyncType\n private readonly clock: () => number\n\n constructor(path: string, clock: () => number = () => Date.now()) {\n if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true })\n this.db = new DatabaseSync(path)\n this.clock = clock\n this.db.exec('PRAGMA journal_mode = WAL;')\n this.db.exec('PRAGMA foreign_keys = ON;')\n this.db.exec(SCHEMA)\n }\n\n /** Deterministic outbox key. The whole dedupe guarantee rests on this. */\n static deliveryKey(eventId: string, subscriptionId: string, ruleId: string): string {\n return `${eventId}|${subscriptionId}|${ruleId}`\n }\n\n // ---- cursors -----------------------------------------------------------\n\n /** The next unprocessed block for a source, or `null` when never persisted. */\n getCursor(sourceId: string): bigint | null {\n const row = this.db.prepare('SELECT next_block FROM cursors WHERE source_id = ?').get(sourceId) as\n | { next_block: string }\n | undefined\n return row ? BigInt(row.next_block) : null\n }\n\n /** Persist the next unprocessed block. Call only after the range is enqueued. */\n setCursor(sourceId: string, nextBlock: bigint): void {\n this.db\n .prepare(\n `INSERT INTO cursors (source_id, next_block, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(source_id) DO UPDATE SET next_block = excluded.next_block, updated_at = excluded.updated_at`,\n )\n .run(sourceId, nextBlock.toString(), this.clock())\n }\n\n /** Every persisted cursor, for `/metrics` and for operator inspection. */\n listCursors(): { sourceId: string; nextBlock: bigint }[] {\n const rows = this.db.prepare('SELECT source_id, next_block FROM cursors ORDER BY source_id').all() as Array<{\n source_id: string\n next_block: string\n }>\n return rows.map((row) => ({ sourceId: row.source_id, nextBlock: BigInt(row.next_block) }))\n }\n\n // ---- subscriptions -----------------------------------------------------\n\n /** Insert or replace a subscription. */\n saveSubscription(subscription: Subscription): void {\n const now = this.clock()\n this.db\n .prepare(\n `INSERT INTO subscriptions (id, subscriber_id, platform, target, enabled, rules, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n subscriber_id = excluded.subscriber_id,\n platform = excluded.platform,\n target = excluded.target,\n enabled = excluded.enabled,\n rules = excluded.rules,\n updated_at = excluded.updated_at`,\n )\n .run(\n subscription.id,\n subscription.subscriberId,\n subscription.platform,\n subscription.target,\n subscription.enabled ? 1 : 0,\n encode(subscription.rules),\n subscription.createdAtMs,\n now,\n )\n }\n\n private hydrate(row: SubscriptionRow): Subscription {\n return parseSubscription({\n id: row.id,\n subscriberId: row.subscriber_id,\n platform: row.platform,\n target: row.target,\n enabled: row.enabled === 1,\n rules: decode(row.rules),\n createdAtMs: row.created_at,\n })\n }\n\n /** One subscription by id. */\n getSubscription(id: string): Subscription | null {\n const row = this.db.prepare('SELECT * FROM subscriptions WHERE id = ?').get(id) as\n | SubscriptionRow\n | undefined\n return row ? this.hydrate(row) : null\n }\n\n /** Every subscription owned by a subscriber. */\n listSubscriptionsFor(subscriberId: string): Subscription[] {\n const rows = this.db\n .prepare('SELECT * FROM subscriptions WHERE subscriber_id = ? ORDER BY created_at')\n .all(subscriberId) as unknown as SubscriptionRow[]\n return rows.map((row) => this.hydrate(row))\n }\n\n /** Every enabled subscription, for the dispatch fan-out. */\n listEnabledSubscriptions(): Subscription[] {\n const rows = this.db\n .prepare('SELECT * FROM subscriptions WHERE enabled = 1 ORDER BY created_at')\n .all() as unknown as SubscriptionRow[]\n return rows.map((row) => this.hydrate(row))\n }\n\n /** Remove a subscription. Returns `true` when a row was deleted. */\n deleteSubscription(id: string): boolean {\n const result = this.db.prepare('DELETE FROM subscriptions WHERE id = ?').run(id)\n return Number(result.changes) > 0\n }\n\n /** Every token on any enabled subscription's watchlist, deduped. */\n watchlistTokens(): Address[] {\n const seen = new Set<string>()\n for (const subscription of this.listEnabledSubscriptions()) {\n for (const rule of subscription.rules) {\n for (const token of rule.tokens) seen.add(token.toLowerCase())\n }\n }\n return [...seen].map((token) => getAddress(token as Address))\n }\n\n // ---- outbox ------------------------------------------------------------\n\n /**\n * Queue a delivery.\n *\n * @returns `true` when this call created the row, `false` when it already\n * existed (a replayed block range, a duplicate log). The boolean is the\n * dedupe signal the dispatcher counts.\n */\n enqueue(row: Omit<OutboxRow, 'status' | 'attempts' | 'lastError' | 'createdAtMs' | 'updatedAtMs'>): boolean {\n const now = this.clock()\n try {\n this.db\n .prepare(\n `INSERT INTO outbox (id, subscription_id, subscriber_id, rule_id, event_id, event_json,\n platform, target, status, not_before, attempts, last_error, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, 0, NULL, ?, ?)`,\n )\n .run(\n row.id,\n row.subscriptionId,\n row.subscriberId,\n row.ruleId,\n row.eventId,\n row.eventJson,\n row.platform,\n row.target,\n row.notBeforeMs,\n now,\n now,\n )\n return true\n } catch (error) {\n // A UNIQUE violation is the expected path for a replay. Anything else is\n // a real storage failure and must not be swallowed.\n const message = error instanceof Error ? error.message : String(error)\n if (/UNIQUE|PRIMARY KEY/i.test(message)) return false\n throw error\n }\n }\n\n private static toOutboxRow(raw: RawOutboxRow): OutboxRow {\n return {\n id: raw.id,\n subscriptionId: raw.subscription_id,\n subscriberId: raw.subscriber_id,\n ruleId: raw.rule_id,\n eventId: raw.event_id,\n eventJson: raw.event_json,\n platform: raw.platform as Platform,\n target: raw.target,\n status: raw.status as OutboxStatus,\n notBeforeMs: raw.not_before,\n attempts: raw.attempts,\n lastError: raw.last_error,\n createdAtMs: raw.created_at,\n updatedAtMs: raw.updated_at,\n }\n }\n\n /**\n * Claim due deliveries, moving them to `sending` in one transaction so a\n * second flush pass cannot pick up the same rows.\n */\n claimDue(nowMs: number, limit: number): OutboxRow[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM outbox\n WHERE status IN ('pending', 'failed') AND not_before <= ?\n ORDER BY not_before, created_at\n LIMIT ?`,\n )\n .all(nowMs, limit) as unknown as RawOutboxRow[]\n if (rows.length === 0) return []\n\n const update = this.db.prepare(`UPDATE outbox SET status = 'sending', updated_at = ? WHERE id = ?`)\n this.db.exec('BEGIN')\n try {\n for (const row of rows) update.run(nowMs, row.id)\n this.db.exec('COMMIT')\n } catch (error) {\n this.db.exec('ROLLBACK')\n throw error\n }\n return rows.map((row) => AlertStore.toOutboxRow(row))\n }\n\n /** Mark a delivery sent. */\n markSent(id: string): void {\n this.db\n .prepare(`UPDATE outbox SET status = 'sent', last_error = NULL, updated_at = ? WHERE id = ?`)\n .run(this.clock(), id)\n }\n\n /** Record a retryable failure and schedule the next attempt. */\n markFailed(id: string, attempts: number, error: string, nextAttemptAtMs: number): void {\n this.db\n .prepare(\n `UPDATE outbox SET status = 'failed', attempts = ?, last_error = ?, not_before = ?, updated_at = ? WHERE id = ?`,\n )\n .run(attempts, error, nextAttemptAtMs, this.clock(), id)\n }\n\n /** Give up on a delivery (permanent rejection, or attempts exhausted). */\n markDead(id: string, attempts: number, error: string): void {\n this.db\n .prepare(`UPDATE outbox SET status = 'dead', attempts = ?, last_error = ?, updated_at = ? WHERE id = ?`)\n .run(attempts, error, this.clock(), id)\n }\n\n /**\n * Return rows abandoned mid-flight by a crash to the queue.\n *\n * @returns how many rows were recovered.\n */\n recoverInFlight(): number {\n const result = this.db\n .prepare(`UPDATE outbox SET status = 'pending', updated_at = ? WHERE status = 'sending'`)\n .run(this.clock())\n return Number(result.changes)\n }\n\n /** Delete delivered rows older than `beforeMs`, keeping the file bounded. */\n pruneOutbox(beforeMs: number): number {\n const result = this.db\n .prepare(`DELETE FROM outbox WHERE status = 'sent' AND updated_at < ?`)\n .run(beforeMs)\n return Number(result.changes)\n }\n\n /** Delete delivery history older than `beforeMs`. */\n pruneDeliveries(beforeMs: number): number {\n const result = this.db.prepare('DELETE FROM deliveries WHERE at < ?').run(beforeMs)\n return Number(result.changes)\n }\n\n // ---- rate limiting and throughput --------------------------------------\n\n /** A {@link RateLimitStore} backed by the `deliveries` table. */\n rateLimitStore(): RateLimitStore {\n return {\n countSince: async (key: string, sinceMs: number): Promise<number> => {\n const row = this.db\n .prepare('SELECT COUNT(*) AS n FROM deliveries WHERE key = ? AND at >= ?')\n .get(key, sinceMs) as { n: number }\n return Number(row.n)\n },\n lastAt: async (key: string): Promise<number | null> => {\n const row = this.db\n .prepare('SELECT MAX(at) AS last FROM deliveries WHERE key = ?')\n .get(key) as { last: number | null }\n return row.last ?? null\n },\n record: async (key: string, subscriberId: string, atMs: number): Promise<void> => {\n this.recordDelivery(key, subscriberId, atMs)\n },\n countForSubscriberSince: async (subscriberId: string, sinceMs: number): Promise<number> => {\n return this.countDeliveriesForSubscriber(subscriberId, sinceMs)\n },\n }\n }\n\n /** Record a delivery for both rate limiting and throughput accounting. */\n recordDelivery(key: string, subscriberId: string, atMs: number): void {\n this.db.prepare('INSERT INTO deliveries (key, subscriber_id, at) VALUES (?, ?, ?)').run(key, subscriberId, atMs)\n }\n\n /** How many alerts a subscriber received since `sinceMs`. */\n countDeliveriesForSubscriber(subscriberId: string, sinceMs: number): number {\n const row = this.db\n .prepare('SELECT COUNT(*) AS n FROM deliveries WHERE subscriber_id = ? AND at >= ?')\n .get(subscriberId, sinceMs) as { n: number }\n return Number(row.n)\n }\n\n // ---- pool registry -----------------------------------------------------\n\n /** A {@link PoolRegistry} backed by the `pools` table. */\n poolRegistry(): PoolRegistry {\n return {\n record: async (entry: PoolEntry): Promise<void> => {\n this.db\n .prepare(\n `INSERT INTO pools (pool, token, quote_token, launchpad, created_block) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(pool) DO UPDATE SET\n token = excluded.token, quote_token = excluded.quote_token,\n launchpad = excluded.launchpad, created_block = excluded.created_block`,\n )\n .run(\n getAddress(entry.pool),\n getAddress(entry.token),\n getAddress(entry.quoteToken),\n entry.launchpad,\n entry.createdBlock.toString(),\n )\n },\n active: async (limit: number, pinned: readonly Address[] = []): Promise<PoolEntry[]> => {\n const picked = new Map<string, PoolEntry>()\n if (pinned.length > 0) {\n const placeholders = pinned.map(() => '?').join(',')\n const rows = this.db\n .prepare(`SELECT * FROM pools WHERE lower(token) IN (${placeholders})`)\n .all(...pinned.map((token) => token.toLowerCase())) as Array<Record<string, string>>\n for (const row of rows) {\n const entry = AlertStore.toPoolEntry(row)\n picked.set(entry.pool.toLowerCase(), entry)\n }\n }\n const rows = this.db\n .prepare(\n // created_block is stored as text to keep bigint precision, so it\n // is ordered numerically by length first, then lexically.\n 'SELECT * FROM pools ORDER BY length(created_block) DESC, created_block DESC LIMIT ?',\n )\n .all(limit) as Array<Record<string, string>>\n for (const row of rows) {\n if (picked.size >= limit + pinned.length) break\n const entry = AlertStore.toPoolEntry(row)\n if (!picked.has(entry.pool.toLowerCase())) picked.set(entry.pool.toLowerCase(), entry)\n }\n return [...picked.values()]\n },\n originOf: async (token: Address): Promise<Launchpad | null> => {\n const row = this.db\n .prepare('SELECT launchpad FROM pools WHERE lower(token) = ? LIMIT 1')\n .get(token.toLowerCase()) as { launchpad: string } | undefined\n return row ? (row.launchpad as Launchpad) : null\n },\n poolsFor: async (token: Address): Promise<PoolEntry[]> => {\n const rows = this.db\n .prepare('SELECT * FROM pools WHERE lower(token) = ?')\n .all(token.toLowerCase()) as Array<Record<string, string>>\n return rows.map((row) => AlertStore.toPoolEntry(row))\n },\n size: async (): Promise<number> => {\n const row = this.db.prepare('SELECT COUNT(*) AS n FROM pools').get() as { n: number }\n return Number(row.n)\n },\n }\n }\n\n private static toPoolEntry(row: Record<string, string>): PoolEntry {\n return {\n pool: getAddress(row['pool'] as Address),\n token: getAddress(row['token'] as Address),\n quoteToken: getAddress(row['quote_token'] as Address),\n launchpad: row['launchpad'] as Launchpad,\n createdBlock: BigInt(row['created_block'] as string),\n }\n }\n\n // ---- wallet links ------------------------------------------------------\n\n /** Store a signature-verified wallet link. */\n linkWallet(subscriberId: string, address: Address): void {\n this.db\n .prepare(\n `INSERT INTO wallet_links (subscriber_id, address, linked_at) VALUES (?, ?, ?)\n ON CONFLICT(subscriber_id) DO UPDATE SET address = excluded.address, linked_at = excluded.linked_at`,\n )\n .run(subscriberId, getAddress(address), this.clock())\n }\n\n /** A {@link WalletLinkStore} view of the links table. */\n walletLinks(): WalletLinkStore {\n return {\n walletOf: async (subscriberId: string): Promise<Address | null> => {\n const row = this.db\n .prepare('SELECT address FROM wallet_links WHERE subscriber_id = ?')\n .get(subscriberId) as { address: string } | undefined\n return row ? getAddress(row.address as Address) : null\n },\n }\n }\n\n /** Issue (or reissue) the nonce a subscriber must sign to link a wallet. */\n issueLinkNonce(subscriberId: string, nonce: string): string {\n this.db\n .prepare(\n `INSERT INTO link_nonces (subscriber_id, nonce, issued_at) VALUES (?, ?, ?)\n ON CONFLICT(subscriber_id) DO UPDATE SET nonce = excluded.nonce, issued_at = excluded.issued_at`,\n )\n .run(subscriberId, nonce, this.clock())\n return nonce\n }\n\n /** The outstanding link nonce for a subscriber, or `null`. */\n getLinkNonce(subscriberId: string): string | null {\n const row = this.db\n .prepare('SELECT nonce FROM link_nonces WHERE subscriber_id = ?')\n .get(subscriberId) as { nonce: string } | undefined\n return row?.nonce ?? null\n }\n\n /** Consume a link nonce so a signature cannot be replayed. */\n clearLinkNonce(subscriberId: string): void {\n this.db.prepare('DELETE FROM link_nonces WHERE subscriber_id = ?').run(subscriberId)\n }\n\n // ---- meta and metrics --------------------------------------------------\n\n /** Read a free-form service setting. */\n getMeta(key: string): string | null {\n const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as\n | { value: string }\n | undefined\n return row?.value ?? null\n }\n\n /** Write a free-form service setting. */\n setMeta(key: string, value: string): void {\n this.db\n .prepare(\n `INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`,\n )\n .run(key, value)\n }\n\n /** Aggregate counters for `/metrics`. */\n metrics(): StoreMetrics {\n const outbox: Record<OutboxStatus, number> = { pending: 0, sending: 0, sent: 0, failed: 0, dead: 0 }\n const statusRows = this.db\n .prepare('SELECT status, COUNT(*) AS n FROM outbox GROUP BY status')\n .all() as Array<{ status: OutboxStatus; n: number }>\n for (const row of statusRows) outbox[row.status] = Number(row.n)\n\n const subs = this.db\n .prepare('SELECT COUNT(*) AS total, SUM(enabled) AS enabled FROM subscriptions')\n .get() as { total: number; enabled: number | null }\n const pools = this.db.prepare('SELECT COUNT(*) AS n FROM pools').get() as { n: number }\n const deliveries = this.db\n .prepare('SELECT COUNT(*) AS n FROM deliveries WHERE at >= ?')\n .get(this.clock() - 3_600_000) as { n: number }\n\n return {\n subscriptions: Number(subs.total),\n enabledSubscriptions: Number(subs.enabled ?? 0),\n outbox,\n pools: Number(pools.n),\n deliveriesLastHour: Number(deliveries.n),\n cursors: this.listCursors(),\n }\n }\n\n /** Close the database. Safe to call twice. */\n close(): void {\n try {\n this.db.close()\n } catch {\n // Already closed.\n }\n }\n}\n","import type { EventSource } from '../events/sources.js'\nimport type { AlertEvent } from '../events/types.js'\nimport { matchRules, type EvaluationContext } from '../rules/engine.js'\nimport { checkRateLimit, rateLimitKey, type RateLimitStore } from '../rules/ratelimit.js'\nimport type { Subscription } from '../rules/schema.js'\nimport { renderAlert, type RenderOptions } from '../notifiers/format.js'\nimport type { Notifier } from '../notifiers/types.js'\nimport type { EntitlementGate } from '../tiers/enforce.js'\nimport { decodeEvent, encodeEvent } from './codec.js'\nimport type { Logger } from './logger.js'\nimport { AlertStore, type OutboxRow } from './store.js'\n\n/**\n * The dispatcher: chain to inbox, in two independent halves.\n *\n * **`pollOnce`** reads confirmed block ranges, fans events out across\n * subscriptions and rules, and writes matched deliveries into the outbox. It\n * advances a source's cursor only after every event in the range has been\n * enqueued and committed, which is the property that makes a crash safe:\n * re-reading a range regenerates identical delivery ids and inserts nothing.\n *\n * **`flushOnce`** drains the outbox: claim due rows, render, send, mark the\n * outcome, retry retryable failures with backoff, dead-letter permanent ones.\n *\n * Splitting them is what lets the free tier have a real delivery delay (the\n * row carries `not_before`), lets a Telegram outage stall delivery without\n * stalling ingestion, and keeps a slow platform from pushing the block cursor\n * behind the chain.\n */\n\n/** What one `pollOnce` did. */\nexport interface PollStats {\n /** Blocks covered per source. */\n ranges: { sourceId: string; from: bigint; to: bigint }[]\n eventsDecoded: number\n /** Deliveries newly written to the outbox. */\n enqueued: number\n /** Matches that were already in the outbox (a replayed range). */\n deduped: number\n /** Matches suppressed by a rule rate limit or a tier throughput cap. */\n suppressed: number\n /** Sources that failed this pass. Their cursors did not advance. */\n errors: { sourceId: string; error: string }[]\n}\n\n/** What one `flushOnce` did. */\nexport interface FlushStats {\n claimed: number\n sent: number\n retried: number\n dead: number\n}\n\n/** Options for {@link createDispatcher}. */\nexport interface DispatcherOptions {\n store: AlertStore\n sources: readonly EventSource[]\n gate: EntitlementGate\n rateLimits: RateLimitStore\n /** Resolve the adapter for a subscription. */\n notifierFor: (subscription: Subscription) => Notifier | null\n /** Head block source. */\n getBlockNumber: () => Promise<bigint>\n logger: Logger\n /** Lazy lookups for liquidity and reputation filters. */\n evaluation?: EvaluationContext\n /** Telegram parse mode and footer options. */\n render?: RenderOptions\n /** Confirmations before a block is processed. @defaultValue `2n` */\n confirmations?: bigint\n /** Max blocks per poll per source. @defaultValue `2_000n` */\n chunkSize?: bigint\n /**\n * Where a source starts when it has no persisted cursor: this many blocks\n * behind the head. A fresh deployment should not replay months of history\n * into a subscriber's chat. @defaultValue `5_000n`\n */\n initialLookbackBlocks?: bigint\n /** Max outbox rows drained per flush. @defaultValue `50` */\n flushBatchSize?: number\n /** Attempts before a delivery is dead-lettered. @defaultValue `6` */\n maxDeliveryAttempts?: number\n /** Base retry backoff in ms, doubled per attempt. @defaultValue `30_000` */\n retryBackoffMs?: number\n /** Clock injection point for tests. @defaultValue `Date.now` */\n now?: () => number\n}\n\n/** The dispatcher. */\nexport interface Dispatcher {\n /** Ingest one confirmed range per source. */\n pollOnce(): Promise<PollStats>\n /** Deliver one batch of due outbox rows. */\n flushOnce(): Promise<FlushStats>\n /** Return rows abandoned by a crash to the queue. Call once at startup. */\n recover(): number\n}\n\n/** Build the dispatcher. */\nexport function createDispatcher(options: DispatcherOptions): Dispatcher {\n const {\n store,\n sources,\n gate,\n rateLimits,\n notifierFor,\n getBlockNumber,\n logger,\n } = options\n const confirmations = options.confirmations ?? 2n\n const chunkSize = options.chunkSize ?? 2_000n\n const initialLookback = options.initialLookbackBlocks ?? 5_000n\n const flushBatchSize = options.flushBatchSize ?? 50\n const maxAttempts = options.maxDeliveryAttempts ?? 6\n const retryBackoffMs = options.retryBackoffMs ?? 30_000\n const now = options.now ?? Date.now\n const evaluation = options.evaluation ?? {}\n\n /**\n * Fan one event out to every enabled subscription and write the matches.\n * Returns the counters this event contributed.\n */\n async function fanOut(\n event: AlertEvent,\n subscriptions: readonly Subscription[],\n ): Promise<{ enqueued: number; deduped: number; suppressed: number }> {\n let enqueued = 0\n let deduped = 0\n let suppressed = 0\n\n for (const subscription of subscriptions) {\n const matches = await matchRules(subscription.rules, event, evaluation)\n if (matches.length === 0) continue\n\n const { policy } = await gate.resolve(subscription.subscriberId)\n for (const { rule } of matches) {\n // Tier gating happens here rather than at rule-creation time only,\n // because an entitlement can lapse after a rule was accepted.\n if (!policy.allowedKinds.includes(event.kind)) {\n suppressed += 1\n continue\n }\n\n const key = rateLimitKey(subscription.id, rule.id)\n const limit = await checkRateLimit(rateLimits, key, rule.rateLimit, now())\n if (!limit.allowed) {\n suppressed += 1\n logger.debug('rate limited', { subscription: subscription.id, rule: rule.id, reason: limit.reason })\n continue\n }\n\n const usedThisHour = await rateLimits.countForSubscriberSince(\n subscription.subscriberId,\n now() - 3_600_000,\n )\n const throughput = await gate.checkThroughput(subscription.subscriberId, usedThisHour)\n if (!throughput.allowed) {\n suppressed += 1\n logger.debug('throughput capped', {\n subscriber: subscription.subscriberId,\n reason: throughput.reason,\n })\n continue\n }\n\n const id = AlertStore.deliveryKey(event.id, subscription.id, rule.id)\n const created = store.enqueue({\n id,\n subscriptionId: subscription.id,\n subscriberId: subscription.subscriberId,\n ruleId: rule.id,\n eventId: event.id,\n eventJson: encodeEvent(event),\n platform: subscription.platform,\n target: subscription.target,\n notBeforeMs: now() + policy.deliveryDelayMs,\n })\n if (!created) {\n deduped += 1\n continue\n }\n enqueued += 1\n // An enqueued alert is committed to being sent, so it consumes quota\n // now. Counting at send time instead would let one block's worth of\n // events blow straight through every cap before the first delivery.\n await rateLimits.record(key, subscription.subscriberId, now())\n }\n }\n return { enqueued, deduped, suppressed }\n }\n\n return {\n recover(): number {\n const recovered = store.recoverInFlight()\n if (recovered > 0) logger.warn('recovered in-flight deliveries after restart', { recovered })\n return recovered\n },\n\n async pollOnce(): Promise<PollStats> {\n const stats: PollStats = {\n ranges: [],\n eventsDecoded: 0,\n enqueued: 0,\n deduped: 0,\n suppressed: 0,\n errors: [],\n }\n const head = await getBlockNumber()\n const safeHead = head > confirmations ? head - confirmations : 0n\n const subscriptions = store.listEnabledSubscriptions()\n\n for (const source of sources) {\n const persisted = store.getCursor(source.id)\n const fallback = safeHead > initialLookback ? safeHead - initialLookback : source.startBlock\n const from = persisted ?? (fallback > source.startBlock ? fallback : source.startBlock)\n if (from > safeHead) continue\n const to = from + chunkSize - 1n > safeHead ? safeHead : from + chunkSize - 1n\n\n try {\n const events = await source.poll(from, to)\n stats.eventsDecoded += events.length\n for (const event of events) {\n const result = await fanOut(event, subscriptions)\n stats.enqueued += result.enqueued\n stats.deduped += result.deduped\n stats.suppressed += result.suppressed\n }\n // Committed only after every event in the range is in the outbox.\n store.setCursor(source.id, to + 1n)\n stats.ranges.push({ sourceId: source.id, from, to })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n stats.errors.push({ sourceId: source.id, error: message })\n logger.error('source poll failed, cursor not advanced', {\n source: source.id,\n from,\n to,\n error: message,\n })\n }\n }\n return stats\n },\n\n async flushOnce(): Promise<FlushStats> {\n const stats: FlushStats = { claimed: 0, sent: 0, retried: 0, dead: 0 }\n const rows = store.claimDue(now(), flushBatchSize)\n stats.claimed = rows.length\n\n for (const row of rows) {\n const outcome = await deliver(row)\n if (outcome === 'sent') stats.sent += 1\n else if (outcome === 'retry') stats.retried += 1\n else stats.dead += 1\n }\n return stats\n },\n }\n\n async function deliver(row: OutboxRow): Promise<'sent' | 'retry' | 'dead'> {\n const subscription = store.getSubscription(row.subscriptionId)\n if (!subscription) {\n store.markDead(row.id, row.attempts + 1, 'subscription no longer exists')\n return 'dead'\n }\n const notifier = notifierFor(subscription)\n if (!notifier) {\n store.markDead(row.id, row.attempts + 1, `no notifier configured for platform ${row.platform}`)\n return 'dead'\n }\n\n let event: AlertEvent\n try {\n event = decodeEvent(row.eventJson)\n } catch (error) {\n // An undecodable row can never succeed. Dead-letter it rather than\n // retrying a corrupt payload forever.\n store.markDead(row.id, row.attempts + 1, `corrupt event payload: ${String(error)}`)\n return 'dead'\n }\n\n const alert = renderAlert(event, {\n ...options.render,\n footer: options.render?.footer ?? `rule: ${row.ruleId}`,\n })\n const result = await notifier.send(row.target, alert)\n const attempts = row.attempts + 1\n\n if (result.ok) {\n store.markSent(row.id)\n logger.info('alert delivered', {\n id: row.id,\n platform: notifier.platform,\n kind: event.kind,\n token: event.token,\n attempts: result.attempts,\n })\n return 'sent'\n }\n\n if (!result.retryable || attempts >= maxAttempts) {\n store.markDead(row.id, attempts, result.error ?? 'delivery failed')\n logger.error('alert dead-lettered', {\n id: row.id,\n platform: notifier.platform,\n attempts,\n retryable: result.retryable,\n error: result.error,\n })\n return 'dead'\n }\n\n const nextAttemptAt = now() + retryBackoffMs * 2 ** (attempts - 1)\n store.markFailed(row.id, attempts, result.error ?? 'delivery failed', nextAttemptAt)\n logger.warn('alert delivery failed, will retry', {\n id: row.id,\n attempts,\n nextAttemptAt,\n error: result.error,\n })\n return 'retry'\n }\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport type { AlertStore } from './store.js'\nimport type { Logger } from './logger.js'\n\n/**\n * The service's HTTP surface: `/health`, `/ready`, `/metrics`, and the Discord\n * interactions endpoint when Discord is configured.\n *\n * Kept on `node:http` deliberately. The whole surface is four routes, and a\n * framework would add a dependency, a middleware stack and a body parser that\n * mangles the raw request body, which Discord's signature verification needs\n * byte for byte.\n */\n\n/** Liveness and readiness detail. */\nexport interface HealthState {\n /** Head block the poller last saw. `null` before the first successful poll. */\n headBlock: bigint | null\n /** When the poller last completed a pass, ms since epoch. */\n lastPollAtMs: number | null\n /** Last poll error, if the most recent pass failed. */\n lastError: string | null\n /** Which notifier platforms are configured. */\n platforms: string[]\n /** Version reported by `/health`. */\n version: string\n /** Process start time, ms since epoch. */\n startedAtMs: number\n}\n\n/** Options for {@link createHttpServer}. */\nexport interface HttpServerOptions {\n store: AlertStore\n logger: Logger\n /** Live health snapshot, read per request. */\n health: () => HealthState\n /**\n * Discord interactions handler, mounted at `interactionsPath`. Omitted when\n * Discord is not configured, in which case the path 404s.\n */\n discord?: {\n path: string\n handle(request: {\n signature: string | null\n timestamp: string | null\n rawBody: string\n }): Promise<{ status: number; body: unknown }>\n }\n /**\n * How stale the last successful poll may be before `/ready` reports 503, in\n * ms. @defaultValue `120_000`\n */\n readinessStalenessMs?: number\n /** Clock injection point for tests. @defaultValue `Date.now` */\n now?: () => number\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body, (_key, value: unknown) =>\n typeof value === 'bigint' ? value.toString() : value,\n )\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(payload)\n}\n\nasync function readBody(req: IncomingMessage, limitBytes: number): Promise<string | null> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))\n size += buffer.length\n if (size > limitBytes) return null\n chunks.push(buffer)\n }\n return Buffer.concat(chunks).toString('utf8')\n}\n\n/** Render Prometheus text-format metrics. */\nexport function renderMetrics(store: AlertStore, health: HealthState, nowMs: number): string {\n const metrics = store.metrics()\n const lines: string[] = [\n '# HELP hood_alerts_subscriptions Subscriptions by state.',\n '# TYPE hood_alerts_subscriptions gauge',\n `hood_alerts_subscriptions{state=\"total\"} ${metrics.subscriptions}`,\n `hood_alerts_subscriptions{state=\"enabled\"} ${metrics.enabledSubscriptions}`,\n '# HELP hood_alerts_outbox Deliveries by outbox status.',\n '# TYPE hood_alerts_outbox gauge',\n ]\n for (const [status, count] of Object.entries(metrics.outbox)) {\n lines.push(`hood_alerts_outbox{status=\"${status}\"} ${count}`)\n }\n lines.push(\n '# HELP hood_alerts_deliveries_last_hour Alerts delivered in the last rolling hour.',\n '# TYPE hood_alerts_deliveries_last_hour gauge',\n `hood_alerts_deliveries_last_hour ${metrics.deliveriesLastHour}`,\n '# HELP hood_alerts_pools Memecoin pools tracked for whale trades.',\n '# TYPE hood_alerts_pools gauge',\n `hood_alerts_pools ${metrics.pools}`,\n '# HELP hood_alerts_cursor_block Next unprocessed block per source.',\n '# TYPE hood_alerts_cursor_block gauge',\n )\n for (const cursor of metrics.cursors) {\n lines.push(`hood_alerts_cursor_block{source=\"${cursor.sourceId}\"} ${cursor.nextBlock}`)\n }\n lines.push(\n '# HELP hood_alerts_head_block Chain head block last observed.',\n '# TYPE hood_alerts_head_block gauge',\n `hood_alerts_head_block ${health.headBlock ?? 0}`,\n '# HELP hood_alerts_lag_blocks How far each source trails the chain head.',\n '# TYPE hood_alerts_lag_blocks gauge',\n )\n if (health.headBlock !== null) {\n for (const cursor of metrics.cursors) {\n const lag = health.headBlock > cursor.nextBlock ? health.headBlock - cursor.nextBlock : 0n\n lines.push(`hood_alerts_lag_blocks{source=\"${cursor.sourceId}\"} ${lag}`)\n }\n }\n lines.push(\n '# HELP hood_alerts_last_poll_age_seconds Seconds since the last successful poll.',\n '# TYPE hood_alerts_last_poll_age_seconds gauge',\n `hood_alerts_last_poll_age_seconds ${\n health.lastPollAtMs === null ? -1 : Math.round((nowMs - health.lastPollAtMs) / 1000)\n }`,\n '# HELP hood_alerts_uptime_seconds Process uptime.',\n '# TYPE hood_alerts_uptime_seconds counter',\n `hood_alerts_uptime_seconds ${Math.round((nowMs - health.startedAtMs) / 1000)}`,\n '',\n )\n return lines.join('\\n')\n}\n\n/** Build the HTTP server. Call `listen` yourself so startup order stays explicit. */\nexport function createHttpServer(options: HttpServerOptions): Server {\n const now = options.now ?? Date.now\n const stalenessMs = options.readinessStalenessMs ?? 120_000\n\n return createServer((req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n const path = url.pathname\n\n try {\n if (req.method === 'GET' && (path === '/health' || path === '/healthz')) {\n const health = options.health()\n json(res, 200, {\n ok: true,\n version: health.version,\n uptimeSeconds: Math.round((now() - health.startedAtMs) / 1000),\n headBlock: health.headBlock,\n lastPollAtMs: health.lastPollAtMs,\n lastError: health.lastError,\n platforms: health.platforms,\n })\n return\n }\n\n if (req.method === 'GET' && path === '/ready') {\n const health = options.health()\n // Ready means \"ingesting\": a service whose poller died is alive but\n // useless, and reporting it ready would hide the outage from an\n // orchestrator that could restart it.\n const fresh =\n health.lastPollAtMs !== null && now() - health.lastPollAtMs <= stalenessMs\n json(res, fresh ? 200 : 503, {\n ready: fresh,\n lastPollAtMs: health.lastPollAtMs,\n lastError: health.lastError,\n })\n return\n }\n\n if (req.method === 'GET' && path === '/metrics') {\n const body = renderMetrics(options.store, options.health(), now())\n res.writeHead(200, { 'content-type': 'text/plain; version=0.0.4; charset=utf-8' })\n res.end(body)\n return\n }\n\n if (options.discord && req.method === 'POST' && path === options.discord.path) {\n const rawBody = await readBody(req, 256 * 1024)\n if (rawBody === null) {\n json(res, 413, { error: 'request body too large' })\n return\n }\n const answer = await options.discord.handle({\n signature: req.headers['x-signature-ed25519'] as string | undefined ?? null,\n timestamp: req.headers['x-signature-timestamp'] as string | undefined ?? null,\n rawBody,\n })\n json(res, answer.status, answer.body)\n return\n }\n\n json(res, 404, { error: `no route for ${req.method ?? 'GET'} ${path}` })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n options.logger.error('http handler failed', { path, error: message })\n if (!res.headersSent) json(res, 500, { error: 'internal error' })\n else res.end()\n }\n })()\n })\n}\n","import type { Server } from 'node:http'\nimport { createHoodClient, type HoodClient } from 'hoodchain'\nimport { createEventSources } from '../events/sources.js'\nimport { createLiquidityReader } from '../events/liquidity.js'\nimport { createPriceOracle } from '../events/pricing.js'\nimport { createTokenMetaReader } from '../events/tokens.js'\nimport { createCommandRouter } from '../bot/commands.js'\nimport { createDiscordInteractionHandler } from '../bot/discord-bot.js'\nimport { createTelegramBot, type TelegramBot } from '../bot/telegram-bot.js'\nimport { createCaptureNotifier } from '../notifiers/capture.js'\nimport { createDiscordBotNotifier, createDiscordWebhookNotifier } from '../notifiers/discord.js'\nimport { createTelegramClient, createTelegramNotifier } from '../notifiers/telegram.js'\nimport type { Notifier } from '../notifiers/types.js'\nimport { createRpcReputationProvider } from '../rules/reputation.js'\nimport type { Subscription } from '../rules/schema.js'\nimport { createEntitlementGate } from '../tiers/enforce.js'\nimport {\n chainEntitlementProviders,\n createStaticEntitlementProvider,\n createUsdgEntitlementProvider,\n type EntitlementProvider,\n} from '../tiers/entitlements.js'\nimport type { ServiceConfig } from './config.js'\nimport { createDispatcher, type Dispatcher } from './dispatcher.js'\nimport { createHttpServer, type HealthState } from './http.js'\nimport { createLogger, type Logger } from './logger.js'\nimport { AlertStore } from './store.js'\n\n/** Package version reported by `/health`. Kept in step with package.json. */\nexport const VERSION = '0.1.0'\n\n/** A wired, runnable service. */\nexport interface AlertService {\n readonly config: ServiceConfig\n readonly store: AlertStore\n readonly dispatcher: Dispatcher\n readonly logger: Logger\n /** Current health snapshot. */\n health(): HealthState\n /** Start the HTTP server, the poll loop, the flush loop and the bots. */\n start(): Promise<void>\n /** Stop everything and close the database. Safe to call twice. */\n stop(): Promise<void>\n}\n\n/** Options for {@link createService}, all optional except the config. */\nexport interface CreateServiceOptions {\n config: ServiceConfig\n /** Substitute the chain client (a test double, or a pre-configured client). */\n client?: HoodClient\n /** Substitute the logger. */\n logger?: Logger\n /** Substitute the store (`:memory:` in tests). */\n store?: AlertStore\n}\n\nfunction buildEntitlements(\n config: ServiceConfig,\n client: HoodClient,\n store: AlertStore,\n): EntitlementProvider {\n const staticProvider = createStaticEntitlementProvider({\n premiumSubscribers: config.premiumSubscribers,\n allPremium: config.allPremium,\n })\n if (config.entitlements === 'static') return staticProvider\n\n const usdgProvider = createUsdgEntitlementProvider({\n client,\n receiver: config.usdgReceiver as `0x${string}`,\n pricePerPeriodUsdg: config.premiumPriceUsdg,\n periodDays: config.premiumPeriodDays,\n links: store.walletLinks(),\n fromBlock: config.paymentsFromBlock,\n })\n if (config.entitlements === 'usdg') return usdgProvider\n // 'both': configuration grants comps, the chain sells subscriptions.\n return chainEntitlementProviders(staticProvider, usdgProvider)\n}\n\n/**\n * Wire the whole service from a validated configuration.\n *\n * @example\n * ```ts\n * import { createService, loadConfig } from 'hood-alerts/service'\n *\n * const service = createService({ config: loadConfig() })\n * await service.start()\n * ```\n */\nexport function createService(options: CreateServiceOptions): AlertService {\n const config = options.config\n const logger =\n options.logger ?? createLogger({ level: config.logLevel, base: { service: 'hood-alerts' } })\n const client =\n options.client ??\n createHoodClient({\n chain: config.network,\n ...(config.rpcUrl ? { rpcUrl: config.rpcUrl } : {}),\n })\n const store = options.store ?? new AlertStore(config.dbPath)\n\n const oracle = createPriceOracle(client)\n const tokens = createTokenMetaReader(client)\n const registry = store.poolRegistry()\n const liquidity = createLiquidityReader(client, oracle, registry)\n const reputation = createRpcReputationProvider(client, oracle)\n\n const sources = createEventSources({\n client,\n oracle,\n tokens,\n registry,\n pinnedTokens: async () => store.watchlistTokens(),\n whaleMinUsd: config.whaleMinUsd,\n whalePoolLimit: config.whalePoolLimit,\n })\n\n const entitlements = buildEntitlements(config, client, store)\n const gate = createEntitlementGate({ provider: entitlements })\n\n // ---- notifiers ---------------------------------------------------------\n const platforms: string[] = []\n let telegramNotifier: Notifier | null = null\n let discordWebhookNotifier: Notifier | null = null\n let discordBotNotifier: Notifier | null = null\n const telegramClient = config.telegramBotToken\n ? createTelegramClient({ botToken: config.telegramBotToken })\n : null\n\n if (config.dryRun) {\n telegramNotifier = createCaptureNotifier('telegram', (delivery) =>\n logger.info('dry run: telegram alert', { target: delivery.target, text: delivery.alert.plain }),\n )\n discordWebhookNotifier = createCaptureNotifier('discord', (delivery) =>\n logger.info('dry run: discord alert', { target: delivery.target, text: delivery.alert.plain }),\n )\n discordBotNotifier = discordWebhookNotifier\n platforms.push('dry-run')\n } else {\n if (telegramClient) {\n telegramNotifier = createTelegramNotifier({\n botToken: config.telegramBotToken as string,\n client: telegramClient,\n })\n platforms.push('telegram')\n }\n // The webhook adapter needs no credential, so Discord webhook delivery is\n // always available even without a bot token.\n discordWebhookNotifier = createDiscordWebhookNotifier({})\n platforms.push('discord-webhook')\n if (config.discordBotToken) {\n discordBotNotifier = createDiscordBotNotifier({ botToken: config.discordBotToken })\n platforms.push('discord-bot')\n }\n }\n\n function notifierFor(subscription: Subscription): Notifier | null {\n if (subscription.platform === 'telegram') return telegramNotifier\n if (subscription.target.startsWith('https://')) return discordWebhookNotifier\n return discordBotNotifier\n }\n\n const dispatcher = createDispatcher({\n store,\n sources,\n gate,\n rateLimits: store.rateLimitStore(),\n notifierFor,\n getBlockNumber: () => client.public.getBlockNumber(),\n logger,\n evaluation: {\n liquidityUsd: (event) => liquidity.tokenLiquidityUsd(event.token),\n reputation: (deployer) => reputation.get(deployer),\n isLpLocked: (positionId) => reputation.isLpLocked(positionId),\n },\n confirmations: config.confirmations,\n chunkSize: config.chunkSize,\n initialLookbackBlocks: config.initialLookbackBlocks,\n flushBatchSize: config.flushBatchSize,\n maxDeliveryAttempts: config.maxDeliveryAttempts,\n retryBackoffMs: config.retryBackoffMs,\n })\n\n // ---- bot ---------------------------------------------------------------\n const router = createCommandRouter({\n store,\n gate,\n ...(config.upgradeInstructions ? { upgradeInstructions: config.upgradeInstructions } : {}),\n })\n\n let telegramBot: TelegramBot | null = null\n if (telegramClient && !config.dryRun) {\n telegramBot = createTelegramBot({\n botToken: config.telegramBotToken as string,\n client: telegramClient,\n router,\n logger: logger.child({ component: 'telegram-bot' }),\n offsetStore: {\n get: () => {\n const raw = store.getMeta('telegram:offset')\n return raw === null ? null : Number(raw)\n },\n set: (offset: number) => store.setMeta('telegram:offset', String(offset)),\n },\n })\n }\n\n const discordHandler = config.discordPublicKey\n ? createDiscordInteractionHandler({ router, publicKey: config.discordPublicKey })\n : null\n\n // ---- health ------------------------------------------------------------\n const startedAtMs = Date.now()\n let headBlock: bigint | null = null\n let lastPollAtMs: number | null = null\n let lastError: string | null = null\n\n const health = (): HealthState => ({\n headBlock,\n lastPollAtMs,\n lastError,\n platforms,\n version: VERSION,\n startedAtMs,\n })\n\n const server = createHttpServer({\n store,\n logger: logger.child({ component: 'http' }),\n health,\n ...(discordHandler\n ? { discord: { path: config.discordInteractionsPath, handle: discordHandler.handle } }\n : {}),\n })\n\n // ---- loops -------------------------------------------------------------\n let running = false\n const loops: Promise<void>[] = []\n let stopping: Promise<void> | null = null\n const waiters = new Set<() => void>()\n\n /** Sleep that resolves early when the service is stopping. */\n function delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n waiters.delete(wake)\n resolve()\n }, ms)\n const wake = (): void => {\n clearTimeout(timer)\n waiters.delete(wake)\n resolve()\n }\n waiters.add(wake)\n })\n }\n\n async function pollLoop(): Promise<void> {\n while (running) {\n try {\n headBlock = await client.public.getBlockNumber()\n const stats = await dispatcher.pollOnce()\n lastPollAtMs = Date.now()\n lastError = stats.errors.length > 0 ? (stats.errors[0]?.error ?? null) : null\n if (stats.eventsDecoded > 0 || stats.enqueued > 0) {\n logger.info('poll complete', {\n head: headBlock,\n events: stats.eventsDecoded,\n enqueued: stats.enqueued,\n deduped: stats.deduped,\n suppressed: stats.suppressed,\n errors: stats.errors.length,\n })\n }\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n logger.error('poll loop error', { error: lastError })\n }\n await delay(config.pollIntervalMs)\n }\n }\n\n async function flushLoop(): Promise<void> {\n while (running) {\n try {\n const stats = await dispatcher.flushOnce()\n if (stats.claimed > 0) logger.debug('flush complete', { ...stats })\n } catch (error) {\n logger.error('flush loop error', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n await delay(config.flushIntervalMs)\n }\n }\n\n async function pruneLoop(): Promise<void> {\n while (running) {\n await delay(3_600_000)\n if (!running) break\n try {\n const cutoff = Date.now() - config.outboxRetentionHours * 3_600_000\n const outbox = store.pruneOutbox(cutoff)\n // Delivery history only needs to outlive the longest rate-limit window.\n const deliveries = store.pruneDeliveries(Date.now() - 25 * 3_600_000)\n if (outbox > 0 || deliveries > 0) logger.info('pruned', { outbox, deliveries })\n } catch (error) {\n logger.error('prune failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n }\n\n return {\n config,\n store,\n dispatcher,\n logger,\n health,\n\n async start(): Promise<void> {\n if (running) return\n running = true\n dispatcher.recover()\n\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => reject(error)\n server.once('error', onError)\n server.listen(config.port, () => {\n server.removeListener('error', onError)\n resolve()\n })\n })\n\n logger.info('service started', {\n port: config.port,\n network: config.network,\n dryRun: config.dryRun,\n platforms,\n entitlements: config.entitlements,\n sources: sources.map((source) => source.id),\n })\n\n loops.push(pollLoop(), flushLoop(), pruneLoop())\n if (telegramBot) {\n await telegramBot.publishCommands()\n loops.push(telegramBot.start())\n }\n },\n\n async stop(): Promise<void> {\n stopping ??= (async () => {\n logger.info('service stopping')\n running = false\n telegramBot?.stop()\n for (const wake of [...waiters]) wake()\n await new Promise<void>((resolve) => {\n server.close(() => resolve())\n // Keep-alive sockets would otherwise hold the close open for a full\n // idle timeout on a shutdown that should take milliseconds.\n server.closeAllConnections?.()\n })\n await Promise.allSettled(loops)\n store.close()\n logger.info('service stopped')\n })()\n return stopping\n },\n }\n}\n\n/** A server handle, exported for tests that need the raw `node:http` server. */\nexport type { Server }\n"],"mappings":"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","names":["rows"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-ZDM5VNWB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|