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.
Files changed (107) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +369 -0
  3. package/dist/bot/index.cjs +1008 -0
  4. package/dist/bot/index.cjs.map +1 -0
  5. package/dist/bot/index.d.cts +241 -0
  6. package/dist/bot/index.d.ts +241 -0
  7. package/dist/bot/index.js +30 -0
  8. package/dist/bot/index.js.map +1 -0
  9. package/dist/chunk-32GS6XVE.js +732 -0
  10. package/dist/chunk-32GS6XVE.js.map +1 -0
  11. package/dist/chunk-3LHCQA4Z.js +76 -0
  12. package/dist/chunk-3LHCQA4Z.js.map +1 -0
  13. package/dist/chunk-3NHLSKZE.js +1 -0
  14. package/dist/chunk-3NHLSKZE.js.map +1 -0
  15. package/dist/chunk-6JQK5G3Z.js +72 -0
  16. package/dist/chunk-6JQK5G3Z.js.map +1 -0
  17. package/dist/chunk-CBNZ6AZT.js +598 -0
  18. package/dist/chunk-CBNZ6AZT.js.map +1 -0
  19. package/dist/chunk-CIURPPOW.js +1 -0
  20. package/dist/chunk-CIURPPOW.js.map +1 -0
  21. package/dist/chunk-IUOEGBF6.js +49 -0
  22. package/dist/chunk-IUOEGBF6.js.map +1 -0
  23. package/dist/chunk-NBMINJ5E.js +230 -0
  24. package/dist/chunk-NBMINJ5E.js.map +1 -0
  25. package/dist/chunk-O2RY2SWN.js +1 -0
  26. package/dist/chunk-O2RY2SWN.js.map +1 -0
  27. package/dist/chunk-PLMVKTQM.js +1 -0
  28. package/dist/chunk-PLMVKTQM.js.map +1 -0
  29. package/dist/chunk-RSTLW7XH.js +130 -0
  30. package/dist/chunk-RSTLW7XH.js.map +1 -0
  31. package/dist/chunk-SOBZ2FPP.js +355 -0
  32. package/dist/chunk-SOBZ2FPP.js.map +1 -0
  33. package/dist/chunk-VC5MKEY2.js +170 -0
  34. package/dist/chunk-VC5MKEY2.js.map +1 -0
  35. package/dist/chunk-W2GUAWUR.js +147 -0
  36. package/dist/chunk-W2GUAWUR.js.map +1 -0
  37. package/dist/chunk-Y2M3USWZ.js +355 -0
  38. package/dist/chunk-Y2M3USWZ.js.map +1 -0
  39. package/dist/chunk-Z4DJPESQ.js +1216 -0
  40. package/dist/chunk-Z4DJPESQ.js.map +1 -0
  41. package/dist/chunk-ZDM5VNWB.js +1 -0
  42. package/dist/chunk-ZDM5VNWB.js.map +1 -0
  43. package/dist/events/index.cjs +820 -0
  44. package/dist/events/index.cjs.map +1 -0
  45. package/dist/events/index.d.cts +58 -0
  46. package/dist/events/index.d.ts +58 -0
  47. package/dist/events/index.js +69 -0
  48. package/dist/events/index.js.map +1 -0
  49. package/dist/format-6RG4OcyE.d.ts +57 -0
  50. package/dist/format-B7d40f65.d.cts +57 -0
  51. package/dist/index.cjs +4122 -0
  52. package/dist/index.cjs.map +1 -0
  53. package/dist/index.d.cts +21 -0
  54. package/dist/index.d.ts +21 -0
  55. package/dist/index.js +274 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/logger-3phvV_fl.d.cts +185 -0
  58. package/dist/logger-CYza_0eU.d.ts +185 -0
  59. package/dist/logs-D7sYBvSy.d.cts +66 -0
  60. package/dist/logs-D7sYBvSy.d.ts +66 -0
  61. package/dist/notifiers/index.cjs +620 -0
  62. package/dist/notifiers/index.cjs.map +1 -0
  63. package/dist/notifiers/index.d.cts +186 -0
  64. package/dist/notifiers/index.d.ts +186 -0
  65. package/dist/notifiers/index.js +70 -0
  66. package/dist/notifiers/index.js.map +1 -0
  67. package/dist/pricing-BumgLpvB.d.cts +79 -0
  68. package/dist/pricing-BumgLpvB.d.ts +79 -0
  69. package/dist/ratelimit-BKVQG2oY.d.cts +42 -0
  70. package/dist/ratelimit-CGLlXNIS.d.ts +42 -0
  71. package/dist/registry-D3We8Lg6.d.ts +50 -0
  72. package/dist/registry-DePA3IyS.d.cts +50 -0
  73. package/dist/rules/index.cjs +614 -0
  74. package/dist/rules/index.cjs.map +1 -0
  75. package/dist/rules/index.d.cts +166 -0
  76. package/dist/rules/index.d.ts +166 -0
  77. package/dist/rules/index.js +60 -0
  78. package/dist/rules/index.js.map +1 -0
  79. package/dist/schema-DuDMPqpq.d.ts +183 -0
  80. package/dist/schema-NnIdKFl2.d.cts +183 -0
  81. package/dist/service/index.cjs +3774 -0
  82. package/dist/service/index.cjs.map +1 -0
  83. package/dist/service/index.d.cts +262 -0
  84. package/dist/service/index.d.ts +262 -0
  85. package/dist/service/index.js +45 -0
  86. package/dist/service/index.js.map +1 -0
  87. package/dist/service/main.cjs +3759 -0
  88. package/dist/service/main.cjs.map +1 -0
  89. package/dist/service/main.d.cts +1 -0
  90. package/dist/service/main.d.ts +1 -0
  91. package/dist/service/main.js +60 -0
  92. package/dist/service/main.js.map +1 -0
  93. package/dist/sources-CAaQR6-N.d.ts +159 -0
  94. package/dist/sources-CpDOUlzx.d.cts +159 -0
  95. package/dist/telegram-CpodYJrP.d.cts +64 -0
  96. package/dist/telegram-pvphmBwn.d.ts +64 -0
  97. package/dist/tiers/index.cjs +471 -0
  98. package/dist/tiers/index.cjs.map +1 -0
  99. package/dist/tiers/index.d.cts +231 -0
  100. package/dist/tiers/index.d.ts +231 -0
  101. package/dist/tiers/index.js +34 -0
  102. package/dist/tiers/index.js.map +1 -0
  103. package/dist/types-C0ETog04.d.ts +109 -0
  104. package/dist/types-CcoKpStA.d.cts +109 -0
  105. package/dist/types-DFPpTMWo.d.cts +186 -0
  106. package/dist/types-DFPpTMWo.d.ts +186 -0
  107. package/package.json +114 -0
@@ -0,0 +1,820 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/events/index.ts
21
+ var events_exports = {};
22
+ __export(events_exports, {
23
+ EVENT_KIND_LABELS: () => EVENT_KIND_LABELS,
24
+ LAUNCHPAD_EVENT_KINDS: () => LAUNCHPAD_EVENT_KINDS,
25
+ MAINNET_EXPLORER: () => MAINNET_EXPLORER,
26
+ SUPPORTED_LAUNCHPADS: () => SUPPORTED_LAUNCHPADS,
27
+ TESTNET_EXPLORER: () => TESTNET_EXPLORER,
28
+ addressUrl: () => addressUrl,
29
+ batchAddresses: () => batchAddresses,
30
+ blockUrl: () => blockUrl,
31
+ createBlockTimeReader: () => createBlockTimeReader,
32
+ createEventSources: () => createEventSources,
33
+ createLiquidityReader: () => createLiquidityReader,
34
+ createMemoryPoolRegistry: () => createMemoryPoolRegistry,
35
+ createNoxaLaunchSource: () => createNoxaLaunchSource,
36
+ createOdysseyCurveTradeSource: () => createOdysseyCurveTradeSource,
37
+ createOdysseyGraduationSource: () => createOdysseyGraduationSource,
38
+ createOdysseyLaunchSource: () => createOdysseyLaunchSource,
39
+ createPriceOracle: () => createPriceOracle,
40
+ createStaticPriceOracle: () => createStaticPriceOracle,
41
+ createTokenMetaReader: () => createTokenMetaReader,
42
+ createWhaleTradeSource: () => createWhaleTradeSource,
43
+ eventId: () => eventId,
44
+ explorerBase: () => explorerBase,
45
+ fetchLogRange: () => fetchLogRange,
46
+ isKind: () => isKind,
47
+ isResultTooLarge: () => isResultTooLarge,
48
+ isRetryableRpcError: () => isRetryableRpcError,
49
+ quoteTokens: () => quoteTokens,
50
+ tokenUrl: () => tokenUrl,
51
+ txUrl: () => txUrl
52
+ });
53
+ module.exports = __toCommonJS(events_exports);
54
+
55
+ // src/events/types.ts
56
+ function isKind(event, kind) {
57
+ return event.kind === kind;
58
+ }
59
+ var EVENT_KIND_LABELS = {
60
+ launch: "New launch",
61
+ curve_trade: "Curve trade",
62
+ graduation: "Graduation",
63
+ whale_trade: "Whale trade"
64
+ };
65
+ var LAUNCHPAD_EVENT_KINDS = {
66
+ noxa: ["launch", "whale_trade"],
67
+ odyssey: ["launch", "curve_trade", "graduation", "whale_trade"]
68
+ };
69
+
70
+ // src/events/explorer.ts
71
+ var MAINNET_EXPLORER = "https://robinhoodchain.blockscout.com";
72
+ var TESTNET_EXPLORER = "https://explorer.testnet.chain.robinhood.com";
73
+ function explorerBase(network) {
74
+ return network === "testnet" ? TESTNET_EXPLORER : MAINNET_EXPLORER;
75
+ }
76
+ function txUrl(base, hash) {
77
+ return `${base}/tx/${hash}`;
78
+ }
79
+ function addressUrl(base, address) {
80
+ return `${base}/address/${address}`;
81
+ }
82
+ function tokenUrl(base, token) {
83
+ return `${base}/token/${token}`;
84
+ }
85
+ function blockUrl(base, blockNumber) {
86
+ return `${base}/block/${blockNumber}`;
87
+ }
88
+
89
+ // src/events/pricing.ts
90
+ var import_viem = require("viem");
91
+ var import_hoodchain = require("hoodchain");
92
+ function quoteTokens(client) {
93
+ return client.network === "testnet" ? { weth: import_hoodchain.TESTNET_ADDRESSES.weth, usdg: import_hoodchain.TESTNET_ADDRESSES.usdg } : { weth: import_hoodchain.MAINNET_ADDRESSES.weth, usdg: import_hoodchain.MAINNET_ADDRESSES.usdg };
94
+ }
95
+ function createPriceOracle(client, options = {}) {
96
+ const ttlMs = options.ttlMs ?? 3e4;
97
+ const probeWei = options.probeWei ?? 10n ** 18n;
98
+ const now = options.now ?? Date.now;
99
+ const { weth, usdg } = quoteTokens(client);
100
+ let cached = null;
101
+ let cachedAt = 0;
102
+ let inflight = null;
103
+ async function refresh() {
104
+ try {
105
+ const quote = await (0, import_hoodchain.quoteSwap)(client, { tokenIn: weth, tokenOut: usdg, amountIn: probeWei });
106
+ const usdgOut = Number((0, import_viem.formatUnits)(quote.amountOut, import_hoodchain.USDG_DECIMALS));
107
+ const ethIn = Number((0, import_viem.formatUnits)(probeWei, 18));
108
+ const rate = ethIn > 0 ? usdgOut / ethIn : 0;
109
+ cached = rate > 0 ? rate : null;
110
+ } catch {
111
+ cached = null;
112
+ }
113
+ cachedAt = now();
114
+ return cached;
115
+ }
116
+ async function ethUsd() {
117
+ if (cachedAt !== 0 && now() - cachedAt < ttlMs) return cached;
118
+ inflight ??= refresh().finally(() => {
119
+ inflight = null;
120
+ });
121
+ return inflight;
122
+ }
123
+ return {
124
+ ethUsd,
125
+ async weiToUsd(wei) {
126
+ const rate = await ethUsd();
127
+ if (rate === null) return null;
128
+ return Number((0, import_viem.formatUnits)(wei < 0n ? -wei : wei, 18)) * rate;
129
+ },
130
+ usdgToUsd(atomic) {
131
+ return Number((0, import_viem.formatUnits)(atomic < 0n ? -atomic : atomic, import_hoodchain.USDG_DECIMALS));
132
+ }
133
+ };
134
+ }
135
+ function createStaticPriceOracle(ethUsdRate) {
136
+ return {
137
+ async ethUsd() {
138
+ return ethUsdRate;
139
+ },
140
+ async weiToUsd(wei) {
141
+ if (ethUsdRate === null) return null;
142
+ return Number((0, import_viem.formatUnits)(wei < 0n ? -wei : wei, 18)) * ethUsdRate;
143
+ },
144
+ usdgToUsd(atomic) {
145
+ return Number((0, import_viem.formatUnits)(atomic < 0n ? -atomic : atomic, import_hoodchain.USDG_DECIMALS));
146
+ }
147
+ };
148
+ }
149
+
150
+ // src/events/tokens.ts
151
+ var import_viem2 = require("viem");
152
+ var import_hoodchain2 = require("hoodchain");
153
+ function createTokenMetaReader(client, maxEntries = 5e3) {
154
+ const cache = /* @__PURE__ */ new Map();
155
+ const inflight = /* @__PURE__ */ new Map();
156
+ function remember(meta) {
157
+ if (cache.size >= maxEntries) {
158
+ const oldest = cache.keys().next();
159
+ if (!oldest.done) cache.delete(oldest.value);
160
+ }
161
+ cache.set(meta.address.toLowerCase(), meta);
162
+ }
163
+ async function load(token) {
164
+ const results = await client.public.multicall({
165
+ contracts: [
166
+ { address: token, abi: import_hoodchain2.erc20Abi, functionName: "symbol" },
167
+ { address: token, abi: import_hoodchain2.erc20Abi, functionName: "name" },
168
+ { address: token, abi: import_hoodchain2.erc20Abi, functionName: "decimals" }
169
+ ],
170
+ allowFailure: true
171
+ });
172
+ const [symbolResult, nameResult, decimalsResult] = results;
173
+ const meta = {
174
+ address: (0, import_viem2.getAddress)(token),
175
+ symbol: symbolResult && symbolResult.status === "success" ? String(symbolResult.result) : null,
176
+ name: nameResult && nameResult.status === "success" ? String(nameResult.result) : null,
177
+ decimals: decimalsResult && decimalsResult.status === "success" ? Number(decimalsResult.result) : 18
178
+ };
179
+ remember(meta);
180
+ return meta;
181
+ }
182
+ return {
183
+ async get(token) {
184
+ const key = token.toLowerCase();
185
+ const hit = cache.get(key);
186
+ if (hit) return hit;
187
+ const pending = inflight.get(key);
188
+ if (pending) return pending;
189
+ const promise = load(token).catch(() => {
190
+ const fallback = { address: (0, import_viem2.getAddress)(token), symbol: null, name: null, decimals: 18 };
191
+ remember(fallback);
192
+ return fallback;
193
+ }).finally(() => {
194
+ inflight.delete(key);
195
+ });
196
+ inflight.set(key, promise);
197
+ return promise;
198
+ },
199
+ prime(meta) {
200
+ remember(meta);
201
+ }
202
+ };
203
+ }
204
+
205
+ // src/events/blocktime.ts
206
+ function createBlockTimeReader(client, maxEntries = 2e3) {
207
+ const cache = /* @__PURE__ */ new Map();
208
+ function remember(block, ts) {
209
+ if (cache.size >= maxEntries) {
210
+ const oldest = cache.keys().next();
211
+ if (!oldest.done) cache.delete(oldest.value);
212
+ }
213
+ cache.set(block.toString(), ts);
214
+ }
215
+ async function get(blockNumber) {
216
+ const hit = cache.get(blockNumber.toString());
217
+ if (hit !== void 0) return hit;
218
+ try {
219
+ const block = await client.public.getBlock({ blockNumber, includeTransactions: false });
220
+ const ms = Number(block.timestamp) * 1e3;
221
+ remember(blockNumber, ms);
222
+ return ms;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
227
+ return {
228
+ get,
229
+ async getMany(blocks, maxLookups) {
230
+ const unique = [...new Set(blocks.map((b) => b.toString()))].map((b) => BigInt(b));
231
+ const out = /* @__PURE__ */ new Map();
232
+ let budget = maxLookups;
233
+ for (const block of unique) {
234
+ const cached = cache.get(block.toString());
235
+ if (cached !== void 0) {
236
+ out.set(block, cached);
237
+ continue;
238
+ }
239
+ if (budget <= 0) {
240
+ out.set(block, null);
241
+ continue;
242
+ }
243
+ budget -= 1;
244
+ out.set(block, await get(block));
245
+ }
246
+ return out;
247
+ }
248
+ };
249
+ }
250
+
251
+ // src/events/registry.ts
252
+ var import_viem3 = require("viem");
253
+ function createMemoryPoolRegistry(entries = []) {
254
+ const pools = /* @__PURE__ */ new Map();
255
+ for (const entry of entries) pools.set(entry.pool.toLowerCase(), entry);
256
+ return {
257
+ async record(entry) {
258
+ pools.set(entry.pool.toLowerCase(), {
259
+ ...entry,
260
+ pool: (0, import_viem3.getAddress)(entry.pool),
261
+ token: (0, import_viem3.getAddress)(entry.token),
262
+ quoteToken: (0, import_viem3.getAddress)(entry.quoteToken)
263
+ });
264
+ },
265
+ async active(limit, pinned = []) {
266
+ const pinnedSet = new Set(pinned.map((a) => a.toLowerCase()));
267
+ const all = [...pools.values()].sort((a, b) => a.createdBlock > b.createdBlock ? -1 : 1);
268
+ const isPinned = (entry) => pinnedSet.has(entry.token.toLowerCase());
269
+ const picked = all.filter(isPinned);
270
+ let recent = 0;
271
+ for (const entry of all) {
272
+ if (recent >= limit) break;
273
+ if (isPinned(entry)) continue;
274
+ picked.push(entry);
275
+ recent += 1;
276
+ }
277
+ return picked;
278
+ },
279
+ async originOf(token) {
280
+ const key = token.toLowerCase();
281
+ for (const entry of pools.values()) {
282
+ if (entry.token.toLowerCase() === key) return entry.launchpad;
283
+ }
284
+ return null;
285
+ },
286
+ async poolsFor(token) {
287
+ const key = token.toLowerCase();
288
+ return [...pools.values()].filter((entry) => entry.token.toLowerCase() === key);
289
+ },
290
+ async size() {
291
+ return pools.size;
292
+ }
293
+ };
294
+ }
295
+
296
+ // src/events/liquidity.ts
297
+ var import_viem4 = require("viem");
298
+ var import_hoodchain3 = require("hoodchain");
299
+ function createLiquidityReader(client, oracle, registry, options = {}) {
300
+ const ttlMs = options.ttlMs ?? 6e4;
301
+ const now = options.now ?? Date.now;
302
+ const cache = /* @__PURE__ */ new Map();
303
+ const { weth, usdg } = quoteTokens(client);
304
+ async function readPool(pool, quoteToken) {
305
+ const quote = quoteToken.toLowerCase();
306
+ if (quote !== weth.toLowerCase() && quote !== usdg.toLowerCase()) return null;
307
+ const balance = await client.public.readContract({
308
+ address: quoteToken,
309
+ abi: import_hoodchain3.erc20Abi,
310
+ functionName: "balanceOf",
311
+ args: [pool]
312
+ });
313
+ if (quote === usdg.toLowerCase()) {
314
+ return Number((0, import_viem4.formatUnits)(balance, import_hoodchain3.USDG_DECIMALS)) * 2;
315
+ }
316
+ const usd = await oracle.weiToUsd(balance);
317
+ return usd === null ? null : usd * 2;
318
+ }
319
+ async function poolLiquidityUsd(pool, quoteToken) {
320
+ const key = pool.toLowerCase();
321
+ const hit = cache.get(key);
322
+ if (hit && now() - hit.at < ttlMs) return hit.value;
323
+ let value;
324
+ try {
325
+ value = await readPool(pool, quoteToken);
326
+ } catch {
327
+ value = null;
328
+ }
329
+ cache.set(key, { value, at: now() });
330
+ return value;
331
+ }
332
+ return {
333
+ poolLiquidityUsd,
334
+ async tokenLiquidityUsd(token) {
335
+ const pools = await registry.poolsFor(token);
336
+ if (pools.length === 0) return null;
337
+ const values = await Promise.all(
338
+ pools.map((entry) => poolLiquidityUsd(entry.pool, entry.quoteToken))
339
+ );
340
+ const known = values.filter((v) => v !== null);
341
+ return known.length === 0 ? null : Math.max(...known);
342
+ }
343
+ };
344
+ }
345
+
346
+ // src/events/logs.ts
347
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
348
+ function isResultTooLarge(error) {
349
+ const message = error instanceof Error ? error.message : String(error);
350
+ return /exceeds limit|too many results|response size exceeded|query returned more than/i.test(message);
351
+ }
352
+ function isRetryableRpcError(error) {
353
+ const message = error instanceof Error ? error.message : String(error);
354
+ return /too many requests|rate limit|timed out|timeout|429|503|502|ECONNRESET|socket hang up|fetch failed/i.test(
355
+ message
356
+ );
357
+ }
358
+ async function fetchLogRange(fetcher, from, to, options = {}) {
359
+ const minSpan = options.minSpan ?? 1n;
360
+ const retries = options.retries ?? 5;
361
+ const backoffMs = options.backoffMs ?? 500;
362
+ const sleep = options.sleep ?? defaultSleep;
363
+ const run = async (lo, hi) => {
364
+ let attempt = 0;
365
+ for (; ; ) {
366
+ try {
367
+ return [...await fetcher(lo, hi)];
368
+ } catch (error) {
369
+ if (isResultTooLarge(error)) {
370
+ const span = hi - lo;
371
+ if (span < minSpan || span === 0n) throw error;
372
+ const mid = lo + span / 2n;
373
+ const left = await run(lo, mid);
374
+ const right = await run(mid + 1n, hi);
375
+ return [...left, ...right];
376
+ }
377
+ if (isRetryableRpcError(error) && attempt < retries) {
378
+ await sleep(backoffMs * 2 ** attempt);
379
+ attempt += 1;
380
+ continue;
381
+ }
382
+ throw error;
383
+ }
384
+ }
385
+ };
386
+ if (to < from) return [];
387
+ return run(from, to);
388
+ }
389
+ function batchAddresses(addresses, size = 200) {
390
+ const batches = [];
391
+ for (let i = 0; i < addresses.length; i += size) {
392
+ batches.push(addresses.slice(i, i + size));
393
+ }
394
+ return batches;
395
+ }
396
+
397
+ // src/events/sources.ts
398
+ var import_viem5 = require("viem");
399
+ var import_hoodchain4 = require("hoodchain");
400
+ var import_hoodkit = require("hoodkit");
401
+ var ODYSSEY_FACTORIES = [
402
+ import_hoodchain4.ODYSSEY_ADDRESSES.bondingCurveFactory,
403
+ import_hoodchain4.ODYSSEY_ADDRESSES.reflectionFactory,
404
+ import_hoodchain4.ODYSSEY_ADDRESSES.instantFactory
405
+ ];
406
+ function eventId(kind, transactionHash, logIndex) {
407
+ return `${kind}:${transactionHash.toLowerCase()}:${logIndex}`;
408
+ }
409
+ function links(base, token, actor, hash, pool) {
410
+ return {
411
+ tx: txUrl(base, hash),
412
+ token: tokenUrl(base, token),
413
+ actor: addressUrl(base, actor),
414
+ ...pool ? { pool: addressUrl(base, pool) } : {}
415
+ };
416
+ }
417
+ async function valueQuote(ctx, quoteToken, raw) {
418
+ const { weth, usdg } = quoteTokens(ctx.client);
419
+ const token = quoteToken.toLowerCase();
420
+ if (token === usdg.toLowerCase()) return ctx.oracle.usdgToUsd(raw);
421
+ if (token === weth.toLowerCase()) return ctx.oracle.weiToUsd(raw);
422
+ return null;
423
+ }
424
+ function assertMainnet(client) {
425
+ if (client.network !== "mainnet") {
426
+ throw new Error(
427
+ 'hood-alerts: the NOXA and Odyssey launchpads are deployed on Robinhood Chain mainnet (chain 4663) only. Create the client with { chain: "mainnet" } (the default) to watch them.'
428
+ );
429
+ }
430
+ }
431
+ function createEventSources(ctx) {
432
+ assertMainnet(ctx.client);
433
+ return [
434
+ createNoxaLaunchSource(ctx),
435
+ createOdysseyLaunchSource(ctx),
436
+ createOdysseyCurveTradeSource(ctx),
437
+ createOdysseyGraduationSource(ctx),
438
+ createWhaleTradeSource(ctx)
439
+ ];
440
+ }
441
+ function blockTimes(ctx) {
442
+ ctx.blockTimes ??= createBlockTimeReader(ctx.client);
443
+ return ctx.blockTimes;
444
+ }
445
+ async function stampTimes(ctx, events) {
446
+ if (events.length === 0) return events;
447
+ const reader = blockTimes(ctx);
448
+ const times = await reader.getMany(
449
+ events.map((e) => e.blockNumber),
450
+ ctx.maxBlockTimeLookupsPerPoll ?? 25
451
+ );
452
+ for (const event of events) event.timestampMs = times.get(event.blockNumber) ?? null;
453
+ return events;
454
+ }
455
+ function createNoxaLaunchSource(ctx) {
456
+ const base = explorerBase(ctx.client.network);
457
+ return {
458
+ id: "noxa:launch",
459
+ label: "NOXA launches",
460
+ startBlock: import_hoodchain4.NOXA_ADDRESSES.deployBlock,
461
+ async poll(from, to) {
462
+ const logs = await fetchLogRange(
463
+ (lo, hi) => ctx.client.public.getLogs({
464
+ address: import_hoodchain4.NOXA_ADDRESSES.launchFactory,
465
+ event: import_hoodchain4.noxaTokenLaunchedEvent,
466
+ fromBlock: lo,
467
+ toBlock: hi
468
+ }),
469
+ from,
470
+ to,
471
+ ctx.logOptions ?? {}
472
+ );
473
+ const events = [];
474
+ for (const log of logs) {
475
+ const token = log.args.token;
476
+ const deployer = log.args.deployer;
477
+ if (!token || !deployer) continue;
478
+ const pool = log.args.pool ?? null;
479
+ const pairToken = log.args.pairToken ?? null;
480
+ const initialBuyAmount = log.args.initialBuyAmount ?? 0n;
481
+ if (pool && pairToken) {
482
+ await ctx.registry.record({
483
+ pool: (0, import_viem5.getAddress)(pool),
484
+ token: (0, import_viem5.getAddress)(token),
485
+ quoteToken: (0, import_viem5.getAddress)(pairToken),
486
+ launchpad: "noxa",
487
+ createdBlock: log.blockNumber
488
+ });
489
+ }
490
+ const meta = await ctx.tokens.get(token);
491
+ const usdValue = pairToken && initialBuyAmount > 0n ? await valueQuote(ctx, pairToken, initialBuyAmount) : null;
492
+ events.push({
493
+ id: eventId("launch", log.transactionHash, log.logIndex),
494
+ kind: "launch",
495
+ launchpad: "noxa",
496
+ token: (0, import_viem5.getAddress)(token),
497
+ symbol: meta.symbol,
498
+ name: meta.name,
499
+ actor: (0, import_viem5.getAddress)(deployer),
500
+ blockNumber: log.blockNumber,
501
+ logIndex: log.logIndex,
502
+ transactionHash: log.transactionHash,
503
+ timestampMs: null,
504
+ usdValue,
505
+ explorer: links(base, token, deployer, log.transactionHash, pool ?? void 0),
506
+ pool: pool ? (0, import_viem5.getAddress)(pool) : null,
507
+ pairToken: pairToken ? (0, import_viem5.getAddress)(pairToken) : null,
508
+ initialBuyAmount,
509
+ positionId: log.args.positionId ?? null,
510
+ // NOXA has no bonding curve at all: a launch is already a listing.
511
+ instantListing: true
512
+ });
513
+ }
514
+ return stampTimes(ctx, events);
515
+ }
516
+ };
517
+ }
518
+ function createOdysseyLaunchSource(ctx) {
519
+ const base = explorerBase(ctx.client.network);
520
+ return {
521
+ id: "odyssey:launch",
522
+ label: "Odyssey launches",
523
+ startBlock: import_hoodchain4.NOXA_ADDRESSES.deployBlock,
524
+ async poll(from, to) {
525
+ const logs = await fetchLogRange(
526
+ (lo, hi) => ctx.client.public.getLogs({
527
+ address: ODYSSEY_FACTORIES,
528
+ event: import_hoodchain4.odysseyTokenCreatedEvent,
529
+ fromBlock: lo,
530
+ toBlock: hi
531
+ }),
532
+ from,
533
+ to,
534
+ ctx.logOptions ?? {}
535
+ );
536
+ const events = [];
537
+ for (const log of logs) {
538
+ const token = log.args.token;
539
+ const creator = log.args.creator;
540
+ if (!token || !creator) continue;
541
+ const meta = await ctx.tokens.get(token);
542
+ events.push({
543
+ id: eventId("launch", log.transactionHash, log.logIndex),
544
+ kind: "launch",
545
+ launchpad: "odyssey",
546
+ token: (0, import_viem5.getAddress)(token),
547
+ symbol: meta.symbol,
548
+ name: meta.name,
549
+ actor: (0, import_viem5.getAddress)(creator),
550
+ blockNumber: log.blockNumber,
551
+ logIndex: log.logIndex,
552
+ transactionHash: log.transactionHash,
553
+ timestampMs: null,
554
+ // The curve holds liquidity until graduation, so a new Odyssey token
555
+ // has no pool and no priceable notional at creation time.
556
+ usdValue: null,
557
+ explorer: links(base, token, creator, log.transactionHash),
558
+ pool: null,
559
+ pairToken: null,
560
+ initialBuyAmount: 0n,
561
+ positionId: null,
562
+ instantListing: log.address.toLowerCase() === import_hoodchain4.ODYSSEY_ADDRESSES.instantFactory.toLowerCase()
563
+ });
564
+ }
565
+ return stampTimes(ctx, events);
566
+ }
567
+ };
568
+ }
569
+ function createOdysseyCurveTradeSource(ctx) {
570
+ const base = explorerBase(ctx.client.network);
571
+ return {
572
+ id: "odyssey:curve_trade",
573
+ label: "Odyssey curve trades",
574
+ startBlock: import_hoodchain4.NOXA_ADDRESSES.deployBlock,
575
+ async poll(from, to) {
576
+ const logs = await fetchLogRange(
577
+ (lo, hi) => ctx.client.public.getLogs({
578
+ address: ODYSSEY_FACTORIES,
579
+ event: import_hoodchain4.odysseyTradedEvent,
580
+ fromBlock: lo,
581
+ toBlock: hi
582
+ }),
583
+ from,
584
+ to,
585
+ ctx.logOptions ?? {}
586
+ );
587
+ const events = [];
588
+ for (const log of logs) {
589
+ const token = log.args.token;
590
+ const trader = log.args.trader;
591
+ if (!token || !trader) continue;
592
+ const tokenAmount = log.args.tokenAmount ?? 0n;
593
+ const quoteAmountWei = log.args.quoteAmount ?? 0n;
594
+ const meta = await ctx.tokens.get(token);
595
+ const usdValue = await ctx.oracle.weiToUsd(quoteAmountWei);
596
+ const priceEth = tokenAmount > 0n ? Number(quoteAmountWei) / Number(tokenAmount) : null;
597
+ events.push({
598
+ id: eventId("curve_trade", log.transactionHash, log.logIndex),
599
+ kind: "curve_trade",
600
+ launchpad: "odyssey",
601
+ token: (0, import_viem5.getAddress)(token),
602
+ symbol: meta.symbol,
603
+ name: meta.name,
604
+ actor: (0, import_viem5.getAddress)(trader),
605
+ blockNumber: log.blockNumber,
606
+ logIndex: log.logIndex,
607
+ transactionHash: log.transactionHash,
608
+ timestampMs: null,
609
+ usdValue,
610
+ explorer: links(base, token, trader, log.transactionHash),
611
+ side: log.args.isBuy ? "buy" : "sell",
612
+ tokenAmount,
613
+ quoteAmountWei,
614
+ feeWei: log.args.fee ?? 0n,
615
+ virtualQuoteWei: log.args.virtualQuote ?? 0n,
616
+ virtualTokenAmount: log.args.virtualToken ?? 0n,
617
+ priceEth
618
+ });
619
+ }
620
+ return stampTimes(ctx, events);
621
+ }
622
+ };
623
+ }
624
+ function createOdysseyGraduationSource(ctx) {
625
+ const base = explorerBase(ctx.client.network);
626
+ return {
627
+ id: "odyssey:graduation",
628
+ label: "Odyssey graduations",
629
+ startBlock: import_hoodchain4.NOXA_ADDRESSES.deployBlock,
630
+ async poll(from, to) {
631
+ const logs = await fetchLogRange(
632
+ (lo, hi) => ctx.client.public.getLogs({
633
+ address: ODYSSEY_FACTORIES,
634
+ event: import_hoodchain4.odysseyPoolMigratedEvent,
635
+ fromBlock: lo,
636
+ toBlock: hi
637
+ }),
638
+ from,
639
+ to,
640
+ ctx.logOptions ?? {}
641
+ );
642
+ const events = [];
643
+ for (const log of logs) {
644
+ const token = log.args.token;
645
+ const pool = log.args.pool;
646
+ if (!token || !pool) continue;
647
+ const quoteUsed = log.args.usdcUsed ?? 0n;
648
+ let quoteToken = null;
649
+ try {
650
+ const info = await (0, import_hoodkit.loadPoolInfo)(ctx.client, pool);
651
+ quoteToken = info.token0.toLowerCase() === token.toLowerCase() ? info.token1 : info.token0;
652
+ await ctx.registry.record({
653
+ pool: (0, import_viem5.getAddress)(pool),
654
+ token: (0, import_viem5.getAddress)(token),
655
+ quoteToken,
656
+ launchpad: "odyssey",
657
+ createdBlock: log.blockNumber
658
+ });
659
+ } catch {
660
+ quoteToken = null;
661
+ }
662
+ const meta = await ctx.tokens.get(token);
663
+ const usdValue = quoteToken ? await valueQuote(ctx, quoteToken, quoteUsed) : null;
664
+ events.push({
665
+ id: eventId("graduation", log.transactionHash, log.logIndex),
666
+ kind: "graduation",
667
+ launchpad: "odyssey",
668
+ token: (0, import_viem5.getAddress)(token),
669
+ symbol: meta.symbol,
670
+ name: meta.name,
671
+ // A migration is executed by the factory on behalf of the curve, so
672
+ // the closest thing to an actor is the factory that emitted it.
673
+ actor: (0, import_viem5.getAddress)(log.address),
674
+ blockNumber: log.blockNumber,
675
+ logIndex: log.logIndex,
676
+ transactionHash: log.transactionHash,
677
+ timestampMs: null,
678
+ usdValue,
679
+ explorer: links(base, token, log.address, log.transactionHash, pool),
680
+ pool: (0, import_viem5.getAddress)(pool),
681
+ positionId: log.args.tokenId ?? 0n,
682
+ liquidity: log.args.liquidity ?? 0n,
683
+ tokenUsed: log.args.tokenUsed ?? 0n,
684
+ quoteUsed
685
+ });
686
+ }
687
+ return stampTimes(ctx, events);
688
+ }
689
+ };
690
+ }
691
+ function createWhaleTradeSource(ctx) {
692
+ const base = explorerBase(ctx.client.network);
693
+ const poolInfoCache = /* @__PURE__ */ new Map();
694
+ const minUsd = ctx.whaleMinUsd ?? 1e3;
695
+ async function poolInfo(pool) {
696
+ const key = pool.toLowerCase();
697
+ const hit = poolInfoCache.get(key);
698
+ if (hit) return hit;
699
+ try {
700
+ const info = await (0, import_hoodkit.loadPoolInfo)(ctx.client, pool);
701
+ poolInfoCache.set(key, info);
702
+ return info;
703
+ } catch {
704
+ return null;
705
+ }
706
+ }
707
+ return {
708
+ id: "uniswap:whale_trade",
709
+ label: "Whale trades",
710
+ startBlock: import_hoodchain4.NOXA_ADDRESSES.deployBlock,
711
+ async poll(from, to) {
712
+ const pinned = ctx.pinnedTokens ? await ctx.pinnedTokens() : [];
713
+ const entries = await ctx.registry.active(ctx.whalePoolLimit ?? 400, pinned);
714
+ if (entries.length === 0) return [];
715
+ const byPool = new Map(entries.map((entry) => [entry.pool.toLowerCase(), entry]));
716
+ const batches = batchAddresses(
717
+ entries.map((entry) => entry.pool),
718
+ ctx.whalePoolBatchSize ?? 200
719
+ );
720
+ const logs = (await Promise.all(
721
+ batches.map(
722
+ (batch) => fetchLogRange(
723
+ (lo, hi) => ctx.client.public.getLogs({
724
+ address: batch,
725
+ event: import_hoodkit.uniswapV3SwapEvent,
726
+ fromBlock: lo,
727
+ toBlock: hi
728
+ }),
729
+ from,
730
+ to,
731
+ ctx.logOptions ?? {}
732
+ )
733
+ )
734
+ )).flat();
735
+ const events = [];
736
+ for (const log of logs) {
737
+ const entry = byPool.get(log.address.toLowerCase());
738
+ if (!entry) continue;
739
+ const info = await poolInfo(entry.pool);
740
+ if (!info) continue;
741
+ const amount0 = log.args.amount0 ?? 0n;
742
+ const amount1 = log.args.amount1 ?? 0n;
743
+ const quoteIsToken0 = info.token0.toLowerCase() === entry.quoteToken.toLowerCase();
744
+ const quoteRaw = quoteIsToken0 ? amount0 : amount1;
745
+ const tokenRaw = quoteIsToken0 ? amount1 : amount0;
746
+ if (quoteRaw === 0n || tokenRaw === 0n) continue;
747
+ const usdValue = await valueQuote(ctx, entry.quoteToken, quoteRaw);
748
+ if (usdValue === null || usdValue < minUsd) continue;
749
+ const quoteDecimals = quoteIsToken0 ? info.decimals0 : info.decimals1;
750
+ const tokenDecimals = quoteIsToken0 ? info.decimals1 : info.decimals0;
751
+ const quoteAmount = Math.abs(Number(quoteRaw)) / 10 ** quoteDecimals;
752
+ const tokenAmount = Math.abs(Number(tokenRaw)) / 10 ** tokenDecimals;
753
+ const [tokenMeta, quoteMeta] = await Promise.all([
754
+ ctx.tokens.get(entry.token),
755
+ ctx.tokens.get(entry.quoteToken)
756
+ ]);
757
+ const trader = log.args.recipient ?? log.args.sender;
758
+ events.push({
759
+ id: eventId("whale_trade", log.transactionHash, log.logIndex),
760
+ kind: "whale_trade",
761
+ launchpad: entry.launchpad,
762
+ token: entry.token,
763
+ symbol: tokenMeta.symbol,
764
+ name: tokenMeta.name,
765
+ actor: (0, import_viem5.getAddress)(trader),
766
+ blockNumber: log.blockNumber,
767
+ logIndex: log.logIndex,
768
+ transactionHash: log.transactionHash,
769
+ timestampMs: null,
770
+ usdValue,
771
+ explorer: links(base, entry.token, trader, log.transactionHash, entry.pool),
772
+ pool: entry.pool,
773
+ quoteToken: entry.quoteToken,
774
+ quoteSymbol: quoteMeta.symbol ?? "quote",
775
+ // The memecoin left the pool (negative delta) means the trader bought it.
776
+ side: tokenRaw < 0n ? "buy" : "sell",
777
+ tokenAmount,
778
+ quoteAmount,
779
+ price: tokenAmount > 0 ? quoteAmount / tokenAmount : 0,
780
+ feeTier: info.fee
781
+ });
782
+ }
783
+ return stampTimes(ctx, events);
784
+ }
785
+ };
786
+ }
787
+ var SUPPORTED_LAUNCHPADS = ["noxa", "odyssey"];
788
+ // Annotate the CommonJS export names for ESM import in node:
789
+ 0 && (module.exports = {
790
+ EVENT_KIND_LABELS,
791
+ LAUNCHPAD_EVENT_KINDS,
792
+ MAINNET_EXPLORER,
793
+ SUPPORTED_LAUNCHPADS,
794
+ TESTNET_EXPLORER,
795
+ addressUrl,
796
+ batchAddresses,
797
+ blockUrl,
798
+ createBlockTimeReader,
799
+ createEventSources,
800
+ createLiquidityReader,
801
+ createMemoryPoolRegistry,
802
+ createNoxaLaunchSource,
803
+ createOdysseyCurveTradeSource,
804
+ createOdysseyGraduationSource,
805
+ createOdysseyLaunchSource,
806
+ createPriceOracle,
807
+ createStaticPriceOracle,
808
+ createTokenMetaReader,
809
+ createWhaleTradeSource,
810
+ eventId,
811
+ explorerBase,
812
+ fetchLogRange,
813
+ isKind,
814
+ isResultTooLarge,
815
+ isRetryableRpcError,
816
+ quoteTokens,
817
+ tokenUrl,
818
+ txUrl
819
+ });
820
+ //# sourceMappingURL=index.cjs.map