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,262 @@
|
|
|
1
|
+
import { b as LogLevel, A as AlertStore, c as Logger } from '../logger-3phvV_fl.cjs';
|
|
2
|
+
export { L as LEVELS, a as LogFields, d as LoggerOptions, O as OutboxRow, e as OutboxStatus, S as StoreMetrics, f as createLogger, g as createSilentLogger } from '../logger-3phvV_fl.cjs';
|
|
3
|
+
import { E as EventSource } from '../sources-CpDOUlzx.cjs';
|
|
4
|
+
import { EvaluationContext } from '../rules/index.cjs';
|
|
5
|
+
import { a as RateLimitStore } from '../ratelimit-BKVQG2oY.cjs';
|
|
6
|
+
import { S as Subscription } from '../schema-NnIdKFl2.cjs';
|
|
7
|
+
import { R as RenderOptions } from '../format-B7d40f65.cjs';
|
|
8
|
+
import { N as Notifier } from '../types-CcoKpStA.cjs';
|
|
9
|
+
import { EntitlementGate } from '../tiers/index.cjs';
|
|
10
|
+
import { Server } from 'node:http';
|
|
11
|
+
import { HoodClient } from 'hoodchain';
|
|
12
|
+
import { A as AlertEvent } from '../types-DFPpTMWo.cjs';
|
|
13
|
+
import 'viem';
|
|
14
|
+
import '../registry-DePA3IyS.cjs';
|
|
15
|
+
import '../logs-D7sYBvSy.cjs';
|
|
16
|
+
import '../pricing-BumgLpvB.cjs';
|
|
17
|
+
import 'zod';
|
|
18
|
+
|
|
19
|
+
/** The validated service configuration. */
|
|
20
|
+
interface ServiceConfig {
|
|
21
|
+
network: 'mainnet' | 'testnet';
|
|
22
|
+
rpcUrl: string | undefined;
|
|
23
|
+
dbPath: string;
|
|
24
|
+
port: number;
|
|
25
|
+
logLevel: LogLevel;
|
|
26
|
+
dryRun: boolean;
|
|
27
|
+
telegramBotToken: string | undefined;
|
|
28
|
+
discordBotToken: string | undefined;
|
|
29
|
+
discordApplicationId: string | undefined;
|
|
30
|
+
discordPublicKey: string | undefined;
|
|
31
|
+
discordInteractionsPath: string;
|
|
32
|
+
pollIntervalMs: number;
|
|
33
|
+
flushIntervalMs: number;
|
|
34
|
+
confirmations: bigint;
|
|
35
|
+
chunkSize: bigint;
|
|
36
|
+
initialLookbackBlocks: bigint;
|
|
37
|
+
whaleMinUsd: number;
|
|
38
|
+
whalePoolLimit: number;
|
|
39
|
+
flushBatchSize: number;
|
|
40
|
+
maxDeliveryAttempts: number;
|
|
41
|
+
retryBackoffMs: number;
|
|
42
|
+
outboxRetentionHours: number;
|
|
43
|
+
entitlements: 'static' | 'usdg' | 'both';
|
|
44
|
+
premiumSubscribers: string[];
|
|
45
|
+
allPremium: boolean;
|
|
46
|
+
usdgReceiver: `0x${string}` | undefined;
|
|
47
|
+
premiumPriceUsdg: number;
|
|
48
|
+
premiumPeriodDays: number;
|
|
49
|
+
paymentsFromBlock: bigint;
|
|
50
|
+
upgradeInstructions: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load and validate configuration from the environment.
|
|
54
|
+
*
|
|
55
|
+
* @throws with every problem listed at once, so a misconfigured deployment
|
|
56
|
+
* needs one fix round rather than one per variable.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const config = loadConfig()
|
|
61
|
+
* console.log(`watching ${config.network} with ${config.confirmations} confirmations`)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function loadConfig(env?: NodeJS.ProcessEnv): ServiceConfig;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The dispatcher: chain to inbox, in two independent halves.
|
|
68
|
+
*
|
|
69
|
+
* **`pollOnce`** reads confirmed block ranges, fans events out across
|
|
70
|
+
* subscriptions and rules, and writes matched deliveries into the outbox. It
|
|
71
|
+
* advances a source's cursor only after every event in the range has been
|
|
72
|
+
* enqueued and committed, which is the property that makes a crash safe:
|
|
73
|
+
* re-reading a range regenerates identical delivery ids and inserts nothing.
|
|
74
|
+
*
|
|
75
|
+
* **`flushOnce`** drains the outbox: claim due rows, render, send, mark the
|
|
76
|
+
* outcome, retry retryable failures with backoff, dead-letter permanent ones.
|
|
77
|
+
*
|
|
78
|
+
* Splitting them is what lets the free tier have a real delivery delay (the
|
|
79
|
+
* row carries `not_before`), lets a Telegram outage stall delivery without
|
|
80
|
+
* stalling ingestion, and keeps a slow platform from pushing the block cursor
|
|
81
|
+
* behind the chain.
|
|
82
|
+
*/
|
|
83
|
+
/** What one `pollOnce` did. */
|
|
84
|
+
interface PollStats {
|
|
85
|
+
/** Blocks covered per source. */
|
|
86
|
+
ranges: {
|
|
87
|
+
sourceId: string;
|
|
88
|
+
from: bigint;
|
|
89
|
+
to: bigint;
|
|
90
|
+
}[];
|
|
91
|
+
eventsDecoded: number;
|
|
92
|
+
/** Deliveries newly written to the outbox. */
|
|
93
|
+
enqueued: number;
|
|
94
|
+
/** Matches that were already in the outbox (a replayed range). */
|
|
95
|
+
deduped: number;
|
|
96
|
+
/** Matches suppressed by a rule rate limit or a tier throughput cap. */
|
|
97
|
+
suppressed: number;
|
|
98
|
+
/** Sources that failed this pass. Their cursors did not advance. */
|
|
99
|
+
errors: {
|
|
100
|
+
sourceId: string;
|
|
101
|
+
error: string;
|
|
102
|
+
}[];
|
|
103
|
+
}
|
|
104
|
+
/** What one `flushOnce` did. */
|
|
105
|
+
interface FlushStats {
|
|
106
|
+
claimed: number;
|
|
107
|
+
sent: number;
|
|
108
|
+
retried: number;
|
|
109
|
+
dead: number;
|
|
110
|
+
}
|
|
111
|
+
/** Options for {@link createDispatcher}. */
|
|
112
|
+
interface DispatcherOptions {
|
|
113
|
+
store: AlertStore;
|
|
114
|
+
sources: readonly EventSource[];
|
|
115
|
+
gate: EntitlementGate;
|
|
116
|
+
rateLimits: RateLimitStore;
|
|
117
|
+
/** Resolve the adapter for a subscription. */
|
|
118
|
+
notifierFor: (subscription: Subscription) => Notifier | null;
|
|
119
|
+
/** Head block source. */
|
|
120
|
+
getBlockNumber: () => Promise<bigint>;
|
|
121
|
+
logger: Logger;
|
|
122
|
+
/** Lazy lookups for liquidity and reputation filters. */
|
|
123
|
+
evaluation?: EvaluationContext;
|
|
124
|
+
/** Telegram parse mode and footer options. */
|
|
125
|
+
render?: RenderOptions;
|
|
126
|
+
/** Confirmations before a block is processed. @defaultValue `2n` */
|
|
127
|
+
confirmations?: bigint;
|
|
128
|
+
/** Max blocks per poll per source. @defaultValue `2_000n` */
|
|
129
|
+
chunkSize?: bigint;
|
|
130
|
+
/**
|
|
131
|
+
* Where a source starts when it has no persisted cursor: this many blocks
|
|
132
|
+
* behind the head. A fresh deployment should not replay months of history
|
|
133
|
+
* into a subscriber's chat. @defaultValue `5_000n`
|
|
134
|
+
*/
|
|
135
|
+
initialLookbackBlocks?: bigint;
|
|
136
|
+
/** Max outbox rows drained per flush. @defaultValue `50` */
|
|
137
|
+
flushBatchSize?: number;
|
|
138
|
+
/** Attempts before a delivery is dead-lettered. @defaultValue `6` */
|
|
139
|
+
maxDeliveryAttempts?: number;
|
|
140
|
+
/** Base retry backoff in ms, doubled per attempt. @defaultValue `30_000` */
|
|
141
|
+
retryBackoffMs?: number;
|
|
142
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
143
|
+
now?: () => number;
|
|
144
|
+
}
|
|
145
|
+
/** The dispatcher. */
|
|
146
|
+
interface Dispatcher {
|
|
147
|
+
/** Ingest one confirmed range per source. */
|
|
148
|
+
pollOnce(): Promise<PollStats>;
|
|
149
|
+
/** Deliver one batch of due outbox rows. */
|
|
150
|
+
flushOnce(): Promise<FlushStats>;
|
|
151
|
+
/** Return rows abandoned by a crash to the queue. Call once at startup. */
|
|
152
|
+
recover(): number;
|
|
153
|
+
}
|
|
154
|
+
/** Build the dispatcher. */
|
|
155
|
+
declare function createDispatcher(options: DispatcherOptions): Dispatcher;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The service's HTTP surface: `/health`, `/ready`, `/metrics`, and the Discord
|
|
159
|
+
* interactions endpoint when Discord is configured.
|
|
160
|
+
*
|
|
161
|
+
* Kept on `node:http` deliberately. The whole surface is four routes, and a
|
|
162
|
+
* framework would add a dependency, a middleware stack and a body parser that
|
|
163
|
+
* mangles the raw request body, which Discord's signature verification needs
|
|
164
|
+
* byte for byte.
|
|
165
|
+
*/
|
|
166
|
+
/** Liveness and readiness detail. */
|
|
167
|
+
interface HealthState {
|
|
168
|
+
/** Head block the poller last saw. `null` before the first successful poll. */
|
|
169
|
+
headBlock: bigint | null;
|
|
170
|
+
/** When the poller last completed a pass, ms since epoch. */
|
|
171
|
+
lastPollAtMs: number | null;
|
|
172
|
+
/** Last poll error, if the most recent pass failed. */
|
|
173
|
+
lastError: string | null;
|
|
174
|
+
/** Which notifier platforms are configured. */
|
|
175
|
+
platforms: string[];
|
|
176
|
+
/** Version reported by `/health`. */
|
|
177
|
+
version: string;
|
|
178
|
+
/** Process start time, ms since epoch. */
|
|
179
|
+
startedAtMs: number;
|
|
180
|
+
}
|
|
181
|
+
/** Options for {@link createHttpServer}. */
|
|
182
|
+
interface HttpServerOptions {
|
|
183
|
+
store: AlertStore;
|
|
184
|
+
logger: Logger;
|
|
185
|
+
/** Live health snapshot, read per request. */
|
|
186
|
+
health: () => HealthState;
|
|
187
|
+
/**
|
|
188
|
+
* Discord interactions handler, mounted at `interactionsPath`. Omitted when
|
|
189
|
+
* Discord is not configured, in which case the path 404s.
|
|
190
|
+
*/
|
|
191
|
+
discord?: {
|
|
192
|
+
path: string;
|
|
193
|
+
handle(request: {
|
|
194
|
+
signature: string | null;
|
|
195
|
+
timestamp: string | null;
|
|
196
|
+
rawBody: string;
|
|
197
|
+
}): Promise<{
|
|
198
|
+
status: number;
|
|
199
|
+
body: unknown;
|
|
200
|
+
}>;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* How stale the last successful poll may be before `/ready` reports 503, in
|
|
204
|
+
* ms. @defaultValue `120_000`
|
|
205
|
+
*/
|
|
206
|
+
readinessStalenessMs?: number;
|
|
207
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
208
|
+
now?: () => number;
|
|
209
|
+
}
|
|
210
|
+
/** Render Prometheus text-format metrics. */
|
|
211
|
+
declare function renderMetrics(store: AlertStore, health: HealthState, nowMs: number): string;
|
|
212
|
+
/** Build the HTTP server. Call `listen` yourself so startup order stays explicit. */
|
|
213
|
+
declare function createHttpServer(options: HttpServerOptions): Server;
|
|
214
|
+
|
|
215
|
+
/** Package version reported by `/health`. Kept in step with package.json. */
|
|
216
|
+
declare const VERSION = "0.1.0";
|
|
217
|
+
/** A wired, runnable service. */
|
|
218
|
+
interface AlertService {
|
|
219
|
+
readonly config: ServiceConfig;
|
|
220
|
+
readonly store: AlertStore;
|
|
221
|
+
readonly dispatcher: Dispatcher;
|
|
222
|
+
readonly logger: Logger;
|
|
223
|
+
/** Current health snapshot. */
|
|
224
|
+
health(): HealthState;
|
|
225
|
+
/** Start the HTTP server, the poll loop, the flush loop and the bots. */
|
|
226
|
+
start(): Promise<void>;
|
|
227
|
+
/** Stop everything and close the database. Safe to call twice. */
|
|
228
|
+
stop(): Promise<void>;
|
|
229
|
+
}
|
|
230
|
+
/** Options for {@link createService}, all optional except the config. */
|
|
231
|
+
interface CreateServiceOptions {
|
|
232
|
+
config: ServiceConfig;
|
|
233
|
+
/** Substitute the chain client (a test double, or a pre-configured client). */
|
|
234
|
+
client?: HoodClient;
|
|
235
|
+
/** Substitute the logger. */
|
|
236
|
+
logger?: Logger;
|
|
237
|
+
/** Substitute the store (`:memory:` in tests). */
|
|
238
|
+
store?: AlertStore;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Wire the whole service from a validated configuration.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* import { createService, loadConfig } from 'hood-alerts/service'
|
|
246
|
+
*
|
|
247
|
+
* const service = createService({ config: loadConfig() })
|
|
248
|
+
* await service.start()
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function createService(options: CreateServiceOptions): AlertService;
|
|
252
|
+
|
|
253
|
+
/** Serialize any value containing bigints. */
|
|
254
|
+
declare function encode(value: unknown): string;
|
|
255
|
+
/** Restore a value serialized by {@link encode}. */
|
|
256
|
+
declare function decode<T>(text: string): T;
|
|
257
|
+
/** Serialize an alert event for the outbox. */
|
|
258
|
+
declare function encodeEvent(event: AlertEvent): string;
|
|
259
|
+
/** Restore an alert event from the outbox. */
|
|
260
|
+
declare function decodeEvent(text: string): AlertEvent;
|
|
261
|
+
|
|
262
|
+
export { type AlertService, AlertStore, type CreateServiceOptions, type Dispatcher, type DispatcherOptions, type FlushStats, type HealthState, type HttpServerOptions, LogLevel, Logger, type PollStats, type ServiceConfig, VERSION, createDispatcher, createHttpServer, createService, decode, decodeEvent, encode, encodeEvent, loadConfig, renderMetrics };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { b as LogLevel, A as AlertStore, c as Logger } from '../logger-CYza_0eU.js';
|
|
2
|
+
export { L as LEVELS, a as LogFields, d as LoggerOptions, O as OutboxRow, e as OutboxStatus, S as StoreMetrics, f as createLogger, g as createSilentLogger } from '../logger-CYza_0eU.js';
|
|
3
|
+
import { E as EventSource } from '../sources-CAaQR6-N.js';
|
|
4
|
+
import { EvaluationContext } from '../rules/index.js';
|
|
5
|
+
import { a as RateLimitStore } from '../ratelimit-CGLlXNIS.js';
|
|
6
|
+
import { S as Subscription } from '../schema-DuDMPqpq.js';
|
|
7
|
+
import { R as RenderOptions } from '../format-6RG4OcyE.js';
|
|
8
|
+
import { N as Notifier } from '../types-C0ETog04.js';
|
|
9
|
+
import { EntitlementGate } from '../tiers/index.js';
|
|
10
|
+
import { Server } from 'node:http';
|
|
11
|
+
import { HoodClient } from 'hoodchain';
|
|
12
|
+
import { A as AlertEvent } from '../types-DFPpTMWo.js';
|
|
13
|
+
import 'viem';
|
|
14
|
+
import '../registry-D3We8Lg6.js';
|
|
15
|
+
import '../logs-D7sYBvSy.js';
|
|
16
|
+
import '../pricing-BumgLpvB.js';
|
|
17
|
+
import 'zod';
|
|
18
|
+
|
|
19
|
+
/** The validated service configuration. */
|
|
20
|
+
interface ServiceConfig {
|
|
21
|
+
network: 'mainnet' | 'testnet';
|
|
22
|
+
rpcUrl: string | undefined;
|
|
23
|
+
dbPath: string;
|
|
24
|
+
port: number;
|
|
25
|
+
logLevel: LogLevel;
|
|
26
|
+
dryRun: boolean;
|
|
27
|
+
telegramBotToken: string | undefined;
|
|
28
|
+
discordBotToken: string | undefined;
|
|
29
|
+
discordApplicationId: string | undefined;
|
|
30
|
+
discordPublicKey: string | undefined;
|
|
31
|
+
discordInteractionsPath: string;
|
|
32
|
+
pollIntervalMs: number;
|
|
33
|
+
flushIntervalMs: number;
|
|
34
|
+
confirmations: bigint;
|
|
35
|
+
chunkSize: bigint;
|
|
36
|
+
initialLookbackBlocks: bigint;
|
|
37
|
+
whaleMinUsd: number;
|
|
38
|
+
whalePoolLimit: number;
|
|
39
|
+
flushBatchSize: number;
|
|
40
|
+
maxDeliveryAttempts: number;
|
|
41
|
+
retryBackoffMs: number;
|
|
42
|
+
outboxRetentionHours: number;
|
|
43
|
+
entitlements: 'static' | 'usdg' | 'both';
|
|
44
|
+
premiumSubscribers: string[];
|
|
45
|
+
allPremium: boolean;
|
|
46
|
+
usdgReceiver: `0x${string}` | undefined;
|
|
47
|
+
premiumPriceUsdg: number;
|
|
48
|
+
premiumPeriodDays: number;
|
|
49
|
+
paymentsFromBlock: bigint;
|
|
50
|
+
upgradeInstructions: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load and validate configuration from the environment.
|
|
54
|
+
*
|
|
55
|
+
* @throws with every problem listed at once, so a misconfigured deployment
|
|
56
|
+
* needs one fix round rather than one per variable.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const config = loadConfig()
|
|
61
|
+
* console.log(`watching ${config.network} with ${config.confirmations} confirmations`)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function loadConfig(env?: NodeJS.ProcessEnv): ServiceConfig;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The dispatcher: chain to inbox, in two independent halves.
|
|
68
|
+
*
|
|
69
|
+
* **`pollOnce`** reads confirmed block ranges, fans events out across
|
|
70
|
+
* subscriptions and rules, and writes matched deliveries into the outbox. It
|
|
71
|
+
* advances a source's cursor only after every event in the range has been
|
|
72
|
+
* enqueued and committed, which is the property that makes a crash safe:
|
|
73
|
+
* re-reading a range regenerates identical delivery ids and inserts nothing.
|
|
74
|
+
*
|
|
75
|
+
* **`flushOnce`** drains the outbox: claim due rows, render, send, mark the
|
|
76
|
+
* outcome, retry retryable failures with backoff, dead-letter permanent ones.
|
|
77
|
+
*
|
|
78
|
+
* Splitting them is what lets the free tier have a real delivery delay (the
|
|
79
|
+
* row carries `not_before`), lets a Telegram outage stall delivery without
|
|
80
|
+
* stalling ingestion, and keeps a slow platform from pushing the block cursor
|
|
81
|
+
* behind the chain.
|
|
82
|
+
*/
|
|
83
|
+
/** What one `pollOnce` did. */
|
|
84
|
+
interface PollStats {
|
|
85
|
+
/** Blocks covered per source. */
|
|
86
|
+
ranges: {
|
|
87
|
+
sourceId: string;
|
|
88
|
+
from: bigint;
|
|
89
|
+
to: bigint;
|
|
90
|
+
}[];
|
|
91
|
+
eventsDecoded: number;
|
|
92
|
+
/** Deliveries newly written to the outbox. */
|
|
93
|
+
enqueued: number;
|
|
94
|
+
/** Matches that were already in the outbox (a replayed range). */
|
|
95
|
+
deduped: number;
|
|
96
|
+
/** Matches suppressed by a rule rate limit or a tier throughput cap. */
|
|
97
|
+
suppressed: number;
|
|
98
|
+
/** Sources that failed this pass. Their cursors did not advance. */
|
|
99
|
+
errors: {
|
|
100
|
+
sourceId: string;
|
|
101
|
+
error: string;
|
|
102
|
+
}[];
|
|
103
|
+
}
|
|
104
|
+
/** What one `flushOnce` did. */
|
|
105
|
+
interface FlushStats {
|
|
106
|
+
claimed: number;
|
|
107
|
+
sent: number;
|
|
108
|
+
retried: number;
|
|
109
|
+
dead: number;
|
|
110
|
+
}
|
|
111
|
+
/** Options for {@link createDispatcher}. */
|
|
112
|
+
interface DispatcherOptions {
|
|
113
|
+
store: AlertStore;
|
|
114
|
+
sources: readonly EventSource[];
|
|
115
|
+
gate: EntitlementGate;
|
|
116
|
+
rateLimits: RateLimitStore;
|
|
117
|
+
/** Resolve the adapter for a subscription. */
|
|
118
|
+
notifierFor: (subscription: Subscription) => Notifier | null;
|
|
119
|
+
/** Head block source. */
|
|
120
|
+
getBlockNumber: () => Promise<bigint>;
|
|
121
|
+
logger: Logger;
|
|
122
|
+
/** Lazy lookups for liquidity and reputation filters. */
|
|
123
|
+
evaluation?: EvaluationContext;
|
|
124
|
+
/** Telegram parse mode and footer options. */
|
|
125
|
+
render?: RenderOptions;
|
|
126
|
+
/** Confirmations before a block is processed. @defaultValue `2n` */
|
|
127
|
+
confirmations?: bigint;
|
|
128
|
+
/** Max blocks per poll per source. @defaultValue `2_000n` */
|
|
129
|
+
chunkSize?: bigint;
|
|
130
|
+
/**
|
|
131
|
+
* Where a source starts when it has no persisted cursor: this many blocks
|
|
132
|
+
* behind the head. A fresh deployment should not replay months of history
|
|
133
|
+
* into a subscriber's chat. @defaultValue `5_000n`
|
|
134
|
+
*/
|
|
135
|
+
initialLookbackBlocks?: bigint;
|
|
136
|
+
/** Max outbox rows drained per flush. @defaultValue `50` */
|
|
137
|
+
flushBatchSize?: number;
|
|
138
|
+
/** Attempts before a delivery is dead-lettered. @defaultValue `6` */
|
|
139
|
+
maxDeliveryAttempts?: number;
|
|
140
|
+
/** Base retry backoff in ms, doubled per attempt. @defaultValue `30_000` */
|
|
141
|
+
retryBackoffMs?: number;
|
|
142
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
143
|
+
now?: () => number;
|
|
144
|
+
}
|
|
145
|
+
/** The dispatcher. */
|
|
146
|
+
interface Dispatcher {
|
|
147
|
+
/** Ingest one confirmed range per source. */
|
|
148
|
+
pollOnce(): Promise<PollStats>;
|
|
149
|
+
/** Deliver one batch of due outbox rows. */
|
|
150
|
+
flushOnce(): Promise<FlushStats>;
|
|
151
|
+
/** Return rows abandoned by a crash to the queue. Call once at startup. */
|
|
152
|
+
recover(): number;
|
|
153
|
+
}
|
|
154
|
+
/** Build the dispatcher. */
|
|
155
|
+
declare function createDispatcher(options: DispatcherOptions): Dispatcher;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The service's HTTP surface: `/health`, `/ready`, `/metrics`, and the Discord
|
|
159
|
+
* interactions endpoint when Discord is configured.
|
|
160
|
+
*
|
|
161
|
+
* Kept on `node:http` deliberately. The whole surface is four routes, and a
|
|
162
|
+
* framework would add a dependency, a middleware stack and a body parser that
|
|
163
|
+
* mangles the raw request body, which Discord's signature verification needs
|
|
164
|
+
* byte for byte.
|
|
165
|
+
*/
|
|
166
|
+
/** Liveness and readiness detail. */
|
|
167
|
+
interface HealthState {
|
|
168
|
+
/** Head block the poller last saw. `null` before the first successful poll. */
|
|
169
|
+
headBlock: bigint | null;
|
|
170
|
+
/** When the poller last completed a pass, ms since epoch. */
|
|
171
|
+
lastPollAtMs: number | null;
|
|
172
|
+
/** Last poll error, if the most recent pass failed. */
|
|
173
|
+
lastError: string | null;
|
|
174
|
+
/** Which notifier platforms are configured. */
|
|
175
|
+
platforms: string[];
|
|
176
|
+
/** Version reported by `/health`. */
|
|
177
|
+
version: string;
|
|
178
|
+
/** Process start time, ms since epoch. */
|
|
179
|
+
startedAtMs: number;
|
|
180
|
+
}
|
|
181
|
+
/** Options for {@link createHttpServer}. */
|
|
182
|
+
interface HttpServerOptions {
|
|
183
|
+
store: AlertStore;
|
|
184
|
+
logger: Logger;
|
|
185
|
+
/** Live health snapshot, read per request. */
|
|
186
|
+
health: () => HealthState;
|
|
187
|
+
/**
|
|
188
|
+
* Discord interactions handler, mounted at `interactionsPath`. Omitted when
|
|
189
|
+
* Discord is not configured, in which case the path 404s.
|
|
190
|
+
*/
|
|
191
|
+
discord?: {
|
|
192
|
+
path: string;
|
|
193
|
+
handle(request: {
|
|
194
|
+
signature: string | null;
|
|
195
|
+
timestamp: string | null;
|
|
196
|
+
rawBody: string;
|
|
197
|
+
}): Promise<{
|
|
198
|
+
status: number;
|
|
199
|
+
body: unknown;
|
|
200
|
+
}>;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* How stale the last successful poll may be before `/ready` reports 503, in
|
|
204
|
+
* ms. @defaultValue `120_000`
|
|
205
|
+
*/
|
|
206
|
+
readinessStalenessMs?: number;
|
|
207
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
208
|
+
now?: () => number;
|
|
209
|
+
}
|
|
210
|
+
/** Render Prometheus text-format metrics. */
|
|
211
|
+
declare function renderMetrics(store: AlertStore, health: HealthState, nowMs: number): string;
|
|
212
|
+
/** Build the HTTP server. Call `listen` yourself so startup order stays explicit. */
|
|
213
|
+
declare function createHttpServer(options: HttpServerOptions): Server;
|
|
214
|
+
|
|
215
|
+
/** Package version reported by `/health`. Kept in step with package.json. */
|
|
216
|
+
declare const VERSION = "0.1.0";
|
|
217
|
+
/** A wired, runnable service. */
|
|
218
|
+
interface AlertService {
|
|
219
|
+
readonly config: ServiceConfig;
|
|
220
|
+
readonly store: AlertStore;
|
|
221
|
+
readonly dispatcher: Dispatcher;
|
|
222
|
+
readonly logger: Logger;
|
|
223
|
+
/** Current health snapshot. */
|
|
224
|
+
health(): HealthState;
|
|
225
|
+
/** Start the HTTP server, the poll loop, the flush loop and the bots. */
|
|
226
|
+
start(): Promise<void>;
|
|
227
|
+
/** Stop everything and close the database. Safe to call twice. */
|
|
228
|
+
stop(): Promise<void>;
|
|
229
|
+
}
|
|
230
|
+
/** Options for {@link createService}, all optional except the config. */
|
|
231
|
+
interface CreateServiceOptions {
|
|
232
|
+
config: ServiceConfig;
|
|
233
|
+
/** Substitute the chain client (a test double, or a pre-configured client). */
|
|
234
|
+
client?: HoodClient;
|
|
235
|
+
/** Substitute the logger. */
|
|
236
|
+
logger?: Logger;
|
|
237
|
+
/** Substitute the store (`:memory:` in tests). */
|
|
238
|
+
store?: AlertStore;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Wire the whole service from a validated configuration.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* import { createService, loadConfig } from 'hood-alerts/service'
|
|
246
|
+
*
|
|
247
|
+
* const service = createService({ config: loadConfig() })
|
|
248
|
+
* await service.start()
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function createService(options: CreateServiceOptions): AlertService;
|
|
252
|
+
|
|
253
|
+
/** Serialize any value containing bigints. */
|
|
254
|
+
declare function encode(value: unknown): string;
|
|
255
|
+
/** Restore a value serialized by {@link encode}. */
|
|
256
|
+
declare function decode<T>(text: string): T;
|
|
257
|
+
/** Serialize an alert event for the outbox. */
|
|
258
|
+
declare function encodeEvent(event: AlertEvent): string;
|
|
259
|
+
/** Restore an alert event from the outbox. */
|
|
260
|
+
declare function decodeEvent(text: string): AlertEvent;
|
|
261
|
+
|
|
262
|
+
export { type AlertService, AlertStore, type CreateServiceOptions, type Dispatcher, type DispatcherOptions, type FlushStats, type HealthState, type HttpServerOptions, LogLevel, Logger, type PollStats, type ServiceConfig, VERSION, createDispatcher, createHttpServer, createService, decode, decodeEvent, encode, encodeEvent, loadConfig, renderMetrics };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "../chunk-PLMVKTQM.js";
|
|
2
|
+
import {
|
|
3
|
+
AlertStore,
|
|
4
|
+
VERSION,
|
|
5
|
+
createDispatcher,
|
|
6
|
+
createHttpServer,
|
|
7
|
+
createService,
|
|
8
|
+
decode,
|
|
9
|
+
decodeEvent,
|
|
10
|
+
encode,
|
|
11
|
+
encodeEvent,
|
|
12
|
+
loadConfig,
|
|
13
|
+
renderMetrics
|
|
14
|
+
} from "../chunk-Z4DJPESQ.js";
|
|
15
|
+
import "../chunk-CBNZ6AZT.js";
|
|
16
|
+
import "../chunk-Y2M3USWZ.js";
|
|
17
|
+
import "../chunk-6JQK5G3Z.js";
|
|
18
|
+
import "../chunk-SOBZ2FPP.js";
|
|
19
|
+
import {
|
|
20
|
+
LEVELS,
|
|
21
|
+
createLogger,
|
|
22
|
+
createSilentLogger
|
|
23
|
+
} from "../chunk-32GS6XVE.js";
|
|
24
|
+
import "../chunk-NBMINJ5E.js";
|
|
25
|
+
import "../chunk-RSTLW7XH.js";
|
|
26
|
+
import "../chunk-W2GUAWUR.js";
|
|
27
|
+
import "../chunk-VC5MKEY2.js";
|
|
28
|
+
import "../chunk-3LHCQA4Z.js";
|
|
29
|
+
export {
|
|
30
|
+
AlertStore,
|
|
31
|
+
LEVELS,
|
|
32
|
+
VERSION,
|
|
33
|
+
createDispatcher,
|
|
34
|
+
createHttpServer,
|
|
35
|
+
createLogger,
|
|
36
|
+
createService,
|
|
37
|
+
createSilentLogger,
|
|
38
|
+
decode,
|
|
39
|
+
decodeEvent,
|
|
40
|
+
encode,
|
|
41
|
+
encodeEvent,
|
|
42
|
+
loadConfig,
|
|
43
|
+
renderMetrics
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|