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/events/index.ts","../../src/events/types.ts","../../src/events/explorer.ts","../../src/events/pricing.ts","../../src/events/tokens.ts","../../src/events/blocktime.ts","../../src/events/registry.ts","../../src/events/liquidity.ts","../../src/events/logs.ts","../../src/events/sources.ts"],"sourcesContent":["/**\n * The normalized event taxonomy: launch, curve trade, graduation and whale\n * trade, over both Robinhood Chain memecoin launchpads.\n *\n * @packageDocumentation\n */\n\nexport type {\n AlertEvent,\n AlertEventBase,\n AlertEventKind,\n CurveTradeEvent,\n ExplorerLinks,\n GraduationEvent,\n LaunchEvent,\n Launchpad,\n TradeSide,\n WhaleTradeEvent,\n} from './types.js'\nexport { EVENT_KIND_LABELS, LAUNCHPAD_EVENT_KINDS, isKind } from './types.js'\n\nexport {\n MAINNET_EXPLORER,\n TESTNET_EXPLORER,\n addressUrl,\n blockUrl,\n explorerBase,\n tokenUrl,\n txUrl,\n} from './explorer.js'\n\nexport { createPriceOracle, createStaticPriceOracle, quoteTokens } from './pricing.js'\nexport type { PriceOracle, PriceOracleOptions } from './pricing.js'\n\nexport { createTokenMetaReader } from './tokens.js'\nexport type { TokenMeta, TokenMetaReader } from './tokens.js'\n\nexport { createBlockTimeReader } from './blocktime.js'\nexport type { BlockTimeReader } from './blocktime.js'\n\nexport { createMemoryPoolRegistry } from './registry.js'\nexport type { PoolEntry, PoolRegistry } from './registry.js'\n\nexport { createLiquidityReader } from './liquidity.js'\nexport type { LiquidityReader, LiquidityReaderOptions } from './liquidity.js'\n\nexport { batchAddresses, fetchLogRange, isResultTooLarge, isRetryableRpcError } from './logs.js'\nexport type { FetchLogRangeOptions } from './logs.js'\n\nexport {\n SUPPORTED_LAUNCHPADS,\n createEventSources,\n createNoxaLaunchSource,\n createOdysseyCurveTradeSource,\n createOdysseyGraduationSource,\n createOdysseyLaunchSource,\n createWhaleTradeSource,\n eventId,\n} from './sources.js'\nexport type { EventSource, SourceContext } from './sources.js'\n","import type { Address, Hash } from 'viem'\n\n/**\n * The normalized alert event taxonomy for Robinhood Chain memecoins.\n *\n * Two launchpads run on mainnet (chain 4663) and they have genuinely\n * different lifecycles. The taxonomy models that difference instead of\n * pretending they are the same product:\n *\n * - **NOXA** (`fun.noxa.fi/robinhood`) is an *instant* launcher. One\n * transaction deploys the ERC-20, creates a Uniswap v3 pool, seeds\n * single-sided liquidity and locks the LP NFT. There is no bonding curve,\n * so NOXA emits `launch` and (through its pool) `whale_trade`, and it can\n * never emit `curve_trade` or `graduation`.\n * - **The Odyssey** (`theodyssey.fun`) is a pump.fun-style native-ETH bonding\n * curve with virtual reserves. It emits `launch` when a curve opens,\n * `curve_trade` for every buy/sell on the curve, and `graduation` when the\n * curve fills and liquidity migrates to a locked Uniswap v3 pool. After\n * graduation its tokens also produce `whale_trade` from the migrated pool.\n *\n * A `graduation` alert therefore covers The Odyssey only. Claiming NOXA\n * \"graduations\" would be describing an event that does not exist on chain.\n */\n\n/** Which launchpad an event originated from. */\nexport type Launchpad = 'noxa' | 'odyssey'\n\n/** The kinds of alert this package emits. */\nexport type AlertEventKind = 'launch' | 'curve_trade' | 'graduation' | 'whale_trade'\n\n/** Buy or sell, from the trader's perspective. */\nexport type TradeSide = 'buy' | 'sell'\n\n/** Explorer deep links attached to every event. */\nexport interface ExplorerLinks {\n /** The transaction that produced the event. */\n tx: string\n /** The token contract. */\n token: string\n /** The actor (deployer or trader). */\n actor: string\n /** The Uniswap v3 pool, when the event has one. */\n pool?: string\n}\n\n/** Fields every alert event carries. */\nexport interface AlertEventBase {\n /**\n * Stable, content-addressed identity for this event: `kind:txHash:logIndex`.\n * Reprocessing the same block range regenerates the identical id, which is\n * what makes the delivery dedupe table work across restarts and reorg\n * re-reads.\n */\n id: string\n kind: AlertEventKind\n launchpad: Launchpad\n /** The memecoin the event is about. */\n token: Address\n /** Token symbol read from the ERC-20, or `null` when the call reverts. */\n symbol: string | null\n /** Token name read from the ERC-20, or `null` when the call reverts. */\n name: string | null\n /** The wallet responsible: the deployer for launches, the trader for trades. */\n actor: Address\n blockNumber: bigint\n logIndex: number\n transactionHash: Hash\n /**\n * Block timestamp in milliseconds since epoch, or `null` when the event was\n * decoded without a block fetch (the source fills it whenever available).\n */\n timestampMs: number | null\n /**\n * USD value of the event, or `null` when it cannot be derived from a real\n * price source. Never guessed: launches carry the seeded/initial-buy value,\n * trades carry the traded notional, and `null` means \"no USDG or WETH leg to\n * price against\", which rules treat as \"does not satisfy a min-USD filter\".\n */\n usdValue: number | null\n explorer: ExplorerLinks\n}\n\n/**\n * A new token launch.\n *\n * NOXA: decoded from `TokenLaunched` on the launch factory. `pool` is present\n * immediately and `initialBuyAmount` is the deployer's own first buy.\n * Odyssey: decoded from `TokenCreated` on a bonding-curve factory. `pool` is\n * `null` because the curve holds the liquidity until graduation.\n */\nexport interface LaunchEvent extends AlertEventBase {\n kind: 'launch'\n /** The Uniswap v3 pool, or `null` for an Odyssey token still on its curve. */\n pool: Address | null\n /** The quote token of the pool (NOXA only, `null` on Odyssey). */\n pairToken: Address | null\n /** Raw initial buy in the pair token's units (NOXA), else `0n`. */\n initialBuyAmount: bigint\n /**\n * The Uniswap v3 LP position NFT minted for this launch (NOXA), or `null`\n * when the launchpad did not mint one at launch time (every Odyssey token,\n * whose LP appears at graduation). Reputation rules read its owner to prove\n * the LP is locked.\n */\n positionId: bigint | null\n /**\n * `true` when this launchpad lists instantly with no bonding curve, so no\n * `graduation` event will ever follow. Always `true` for NOXA. For Odyssey\n * it is `true` only for launches from the instant factory variant.\n */\n instantListing: boolean\n}\n\n/**\n * A bonding-curve buy or sell. The Odyssey only: NOXA has no curve.\n * Decoded from `Traded` on an Odyssey factory.\n */\nexport interface CurveTradeEvent extends AlertEventBase {\n kind: 'curve_trade'\n launchpad: 'odyssey'\n side: TradeSide\n /** Token amount bought or sold, raw (18 decimals). */\n tokenAmount: bigint\n /** Native ETH paid or received, in wei. */\n quoteAmountWei: bigint\n /** Protocol fee taken on this trade, in wei. */\n feeWei: bigint\n /** Curve virtual quote reserve after the trade, in wei. */\n virtualQuoteWei: bigint\n /** Curve virtual token reserve after the trade, raw. */\n virtualTokenAmount: bigint\n /** Price of one token in ETH implied by this fill, or `null` for a zero-size fill. */\n priceEth: number | null\n}\n\n/**\n * A curve that filled and migrated to a locked Uniswap v3 pool.\n * The Odyssey only. Decoded from `PoolMigrated`.\n */\nexport interface GraduationEvent extends AlertEventBase {\n kind: 'graduation'\n launchpad: 'odyssey'\n /** The Uniswap v3 pool liquidity migrated into. */\n pool: Address\n /** The position manager token id holding the migrated liquidity. */\n positionId: bigint\n /** Uniswap v3 liquidity units minted at migration. */\n liquidity: bigint\n /** Token side used to seed the pool, raw. */\n tokenUsed: bigint\n /** Quote side used to seed the pool, raw (6 decimals when the quote is USDG). */\n quoteUsed: bigint\n}\n\n/**\n * A Uniswap v3 swap on a memecoin pool above the configured USD threshold.\n * Both launchpads produce these: NOXA from block one, Odyssey after\n * graduation.\n */\nexport interface WhaleTradeEvent extends AlertEventBase {\n kind: 'whale_trade'\n pool: Address\n /** The pool's quote token (the non-memecoin side). */\n quoteToken: Address\n /** Quote token symbol, e.g. `WETH` or `USDG`. */\n quoteSymbol: string\n side: TradeSide\n /** Memecoin amount that moved, in human units. */\n tokenAmount: number\n /** Quote amount that moved, in human units. */\n quoteAmount: number\n /** Executed price of the memecoin in quote-token units. */\n price: number\n /** Uniswap v3 fee tier of the pool, in hundredths of a bip (3000 = 0.3%). */\n feeTier: number\n}\n\n/** Any normalized alert event. */\nexport type AlertEvent = LaunchEvent | CurveTradeEvent | GraduationEvent | WhaleTradeEvent\n\n/** Narrow an event to a kind. */\nexport function isKind<K extends AlertEventKind>(\n event: AlertEvent,\n kind: K,\n): event is Extract<AlertEvent, { kind: K }> {\n return event.kind === kind\n}\n\n/**\n * Human-readable one-line label for an event kind, used by the bot and the\n * notifier formatters.\n */\nexport const EVENT_KIND_LABELS: Record<AlertEventKind, string> = {\n launch: 'New launch',\n curve_trade: 'Curve trade',\n graduation: 'Graduation',\n whale_trade: 'Whale trade',\n}\n\n/**\n * Which event kinds each launchpad can actually produce. The rule engine uses\n * this to reject impossible rules (for example: Odyssey-only `graduation`\n * combined with a NOXA-only launchpad filter would never fire).\n */\nexport const LAUNCHPAD_EVENT_KINDS: Record<Launchpad, readonly AlertEventKind[]> = {\n noxa: ['launch', 'whale_trade'],\n odyssey: ['launch', 'curve_trade', 'graduation', 'whale_trade'],\n}\n","import type { Address, Hash } from 'viem'\n\n/** Blockscout explorer base URL for Robinhood Chain mainnet (chain 4663). */\nexport const MAINNET_EXPLORER = 'https://robinhoodchain.blockscout.com'\n\n/** Explorer base URL for Robinhood Chain testnet (chain 46630). */\nexport const TESTNET_EXPLORER = 'https://explorer.testnet.chain.robinhood.com'\n\n/** Resolve the explorer base URL for a network name. */\nexport function explorerBase(network: 'mainnet' | 'testnet'): string {\n return network === 'testnet' ? TESTNET_EXPLORER : MAINNET_EXPLORER\n}\n\n/** Link to a transaction. */\nexport function txUrl(base: string, hash: Hash): string {\n return `${base}/tx/${hash}`\n}\n\n/** Link to an address (wallet or contract). */\nexport function addressUrl(base: string, address: Address): string {\n return `${base}/address/${address}`\n}\n\n/** Link to a token page (Blockscout renders holders/transfers there). */\nexport function tokenUrl(base: string, token: Address): string {\n return `${base}/token/${token}`\n}\n\n/** Link to a block. */\nexport function blockUrl(base: string, blockNumber: bigint): string {\n return `${base}/block/${blockNumber}`\n}\n","import { formatUnits, type Address } from 'viem'\nimport {\n MAINNET_ADDRESSES,\n TESTNET_ADDRESSES,\n USDG_DECIMALS,\n quoteSwap,\n type HoodClient,\n} from 'hoodchain'\n\n/**\n * USD valuation for alert events.\n *\n * There is no hardcoded price anywhere in this package. Every dollar figure\n * comes from live chain state:\n *\n * - **USDG legs are the unit of account.** USDG is Paxos' Global Dollar, a\n * fully reserved 1:1 USD stablecoin with 6 decimals. A USDG leg of a trade\n * is its own USD value, no oracle needed.\n * - **ETH legs are priced through the chain's own Uniswap v3 liquidity.** The\n * oracle asks `hoodchain`'s `quoteSwap` for the real output of selling 1\n * WETH into USDG across every fee tier and two-hop route, and takes the best\n * route's output as the ETH/USD rate. That is the price a trader would\n * actually get, sourced from the same pools the alerts watch.\n * - **Anything else is `null`.** A memecoin/memecoin pool with no USDG or WETH\n * leg has no honest USD value from on-chain data alone, so the event carries\n * `usdValue: null` and min-USD rules simply do not match it.\n *\n * Rates are cached for a short TTL so a burst of events in one block does not\n * re-quote the router once per event.\n */\n\n/** A source of USD rates for the two priceable legs. */\nexport interface PriceOracle {\n /**\n * USD per 1 ETH, or `null` when no WETH/USDG route has liquidity right now.\n * Never throws: a quote failure degrades to `null`, which downgrades USD\n * filters rather than dropping the alert pipeline.\n */\n ethUsd(): Promise<number | null>\n /** Convert a raw wei amount to USD, or `null` when the rate is unavailable. */\n weiToUsd(wei: bigint): Promise<number | null>\n /** Convert a raw USDG amount (6 decimals) to USD. Always available. */\n usdgToUsd(atomic: bigint): number\n}\n\n/** Options for {@link createPriceOracle}. */\nexport interface PriceOracleOptions {\n /**\n * How long a quoted ETH/USD rate stays fresh, in milliseconds.\n * @defaultValue `30_000`\n */\n ttlMs?: number\n /**\n * Probe size used to quote WETH into USDG. Larger sizes are less sensitive\n * to a thin tick but need deeper liquidity to fill.\n * @defaultValue `1` ETH\n */\n probeWei?: bigint\n /** Clock injection point for tests. @defaultValue `Date.now` */\n now?: () => number\n}\n\n/** WETH and USDG for the client's network. */\nexport function quoteTokens(client: HoodClient): { weth: Address; usdg: Address } {\n return client.network === 'testnet'\n ? { weth: TESTNET_ADDRESSES.weth, usdg: TESTNET_ADDRESSES.usdg }\n : { weth: MAINNET_ADDRESSES.weth, usdg: MAINNET_ADDRESSES.usdg }\n}\n\n/**\n * Build a caching ETH/USD oracle backed by real Uniswap v3 quotes.\n *\n * @example\n * ```ts\n * import { createHoodClient } from 'hoodchain'\n * import { createPriceOracle } from 'hood-alerts/events'\n *\n * const hood = createHoodClient()\n * const oracle = createPriceOracle(hood)\n * console.log('ETH/USD from chain liquidity:', await oracle.ethUsd())\n * ```\n */\nexport function createPriceOracle(client: HoodClient, options: PriceOracleOptions = {}): PriceOracle {\n const ttlMs = options.ttlMs ?? 30_000\n const probeWei = options.probeWei ?? 10n ** 18n\n const now = options.now ?? Date.now\n const { weth, usdg } = quoteTokens(client)\n\n let cached: number | null = null\n let cachedAt = 0\n let inflight: Promise<number | null> | null = null\n\n async function refresh(): Promise<number | null> {\n try {\n const quote = await quoteSwap(client, { tokenIn: weth, tokenOut: usdg, amountIn: probeWei })\n const usdgOut = Number(formatUnits(quote.amountOut, USDG_DECIMALS))\n const ethIn = Number(formatUnits(probeWei, 18))\n const rate = ethIn > 0 ? usdgOut / ethIn : 0\n cached = rate > 0 ? rate : null\n } catch {\n // No route or no liquidity right now. Report \"unknown\", never a guess.\n cached = null\n }\n cachedAt = now()\n return cached\n }\n\n async function ethUsd(): Promise<number | null> {\n if (cachedAt !== 0 && now() - cachedAt < ttlMs) return cached\n // Coalesce concurrent refreshes so a block full of events makes one quote.\n inflight ??= refresh().finally(() => {\n inflight = null\n })\n return inflight\n }\n\n return {\n ethUsd,\n async weiToUsd(wei: bigint): Promise<number | null> {\n const rate = await ethUsd()\n if (rate === null) return null\n return Number(formatUnits(wei < 0n ? -wei : wei, 18)) * rate\n },\n usdgToUsd(atomic: bigint): number {\n return Number(formatUnits(atomic < 0n ? -atomic : atomic, USDG_DECIMALS))\n },\n }\n}\n\n/**\n * A fixed-rate oracle. Useful when an operator already has a trusted ETH/USD\n * feed and wants alerts to agree with it, and for deterministic tests.\n */\nexport function createStaticPriceOracle(ethUsdRate: number | null): PriceOracle {\n return {\n async ethUsd() {\n return ethUsdRate\n },\n async weiToUsd(wei: bigint) {\n if (ethUsdRate === null) return null\n return Number(formatUnits(wei < 0n ? -wei : wei, 18)) * ethUsdRate\n },\n usdgToUsd(atomic: bigint) {\n return Number(formatUnits(atomic < 0n ? -atomic : atomic, USDG_DECIMALS))\n },\n }\n}\n","import { getAddress, type Address } from 'viem'\nimport { erc20Abi, type HoodClient } from 'hoodchain'\n\n/** ERC-20 display metadata, with `null` for anything the contract does not answer. */\nexport interface TokenMeta {\n address: Address\n symbol: string | null\n name: string | null\n /** Decimals, defaulting to 18 when the call reverts (the launchpad standard). */\n decimals: number\n}\n\n/** Cached reader for ERC-20 display metadata. */\nexport interface TokenMetaReader {\n get(token: Address): Promise<TokenMeta>\n /** Prime the cache without a network round trip (used by the service store). */\n prime(meta: TokenMeta): void\n}\n\n/**\n * Build a token metadata reader.\n *\n * Metadata is immutable in practice for launchpad tokens, so entries are\n * cached for the process lifetime. A token whose `symbol()`/`name()` revert\n * (some minimal proxies omit them) resolves to `null` fields rather than\n * throwing, because a missing symbol must never drop an alert.\n */\nexport function createTokenMetaReader(client: HoodClient, maxEntries = 5_000): TokenMetaReader {\n const cache = new Map<string, TokenMeta>()\n const inflight = new Map<string, Promise<TokenMeta>>()\n\n function remember(meta: TokenMeta): void {\n if (cache.size >= maxEntries) {\n const oldest = cache.keys().next()\n if (!oldest.done) cache.delete(oldest.value)\n }\n cache.set(meta.address.toLowerCase(), meta)\n }\n\n async function load(token: Address): Promise<TokenMeta> {\n const results = await client.public.multicall({\n contracts: [\n { address: token, abi: erc20Abi, functionName: 'symbol' },\n { address: token, abi: erc20Abi, functionName: 'name' },\n { address: token, abi: erc20Abi, functionName: 'decimals' },\n ],\n allowFailure: true,\n })\n const [symbolResult, nameResult, decimalsResult] = results\n const meta: TokenMeta = {\n address: getAddress(token),\n symbol:\n symbolResult && symbolResult.status === 'success' ? String(symbolResult.result) : null,\n name: nameResult && nameResult.status === 'success' ? String(nameResult.result) : null,\n decimals:\n decimalsResult && decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,\n }\n remember(meta)\n return meta\n }\n\n return {\n async get(token: Address): Promise<TokenMeta> {\n const key = token.toLowerCase()\n const hit = cache.get(key)\n if (hit) return hit\n const pending = inflight.get(key)\n if (pending) return pending\n const promise = load(token)\n .catch((): TokenMeta => {\n // A dead RPC must not break event construction: fall back to a\n // metadata-free record. The address is always enough to alert on.\n const fallback: TokenMeta = { address: getAddress(token), symbol: null, name: null, decimals: 18 }\n remember(fallback)\n return fallback\n })\n .finally(() => {\n inflight.delete(key)\n })\n inflight.set(key, promise)\n return promise\n },\n prime(meta: TokenMeta): void {\n remember(meta)\n },\n }\n}\n","import type { HoodClient } from 'hoodchain'\n\n/** Cached block-timestamp reader. */\nexport interface BlockTimeReader {\n /**\n * Timestamp of `blockNumber` in ms, or `null` when it could not be read\n * within the caller's budget. Events carry `timestampMs: null` in that case\n * rather than a fabricated \"now\".\n */\n get(blockNumber: bigint): Promise<number | null>\n /**\n * Resolve timestamps for a set of blocks, bounded by `maxLookups`. Blocks\n * past the budget resolve to `null`: a backfill of a million blocks must not\n * turn into a million `eth_getBlockByNumber` calls.\n */\n getMany(blocks: readonly bigint[], maxLookups: number): Promise<Map<bigint, number | null>>\n}\n\n/** Build a block-timestamp reader with a bounded LRU cache. */\nexport function createBlockTimeReader(client: HoodClient, maxEntries = 2_000): BlockTimeReader {\n const cache = new Map<string, number>()\n\n function remember(block: bigint, ts: number): void {\n if (cache.size >= maxEntries) {\n const oldest = cache.keys().next()\n if (!oldest.done) cache.delete(oldest.value)\n }\n cache.set(block.toString(), ts)\n }\n\n async function get(blockNumber: bigint): Promise<number | null> {\n const hit = cache.get(blockNumber.toString())\n if (hit !== undefined) return hit\n try {\n const block = await client.public.getBlock({ blockNumber, includeTransactions: false })\n const ms = Number(block.timestamp) * 1000\n remember(blockNumber, ms)\n return ms\n } catch {\n return null\n }\n }\n\n return {\n get,\n async getMany(blocks: readonly bigint[], maxLookups: number): Promise<Map<bigint, number | null>> {\n const unique = [...new Set(blocks.map((b) => b.toString()))].map((b) => BigInt(b))\n const out = new Map<bigint, number | null>()\n let budget = maxLookups\n for (const block of unique) {\n const cached = cache.get(block.toString())\n if (cached !== undefined) {\n out.set(block, cached)\n continue\n }\n if (budget <= 0) {\n out.set(block, null)\n continue\n }\n budget -= 1\n out.set(block, await get(block))\n }\n return out\n },\n }\n}\n","import { getAddress, type Address } from 'viem'\nimport type { Launchpad } from './types.js'\n\n/**\n * A memecoin pool the whale watcher tracks.\n *\n * Why a registry exists at all: chain-wide `Swap` queries are not viable on\n * Robinhood Chain. A topic-only query for the Uniswap v3 `Swap` signature\n * overflows the RPC's 10,000-log result cap in well under 2,000 blocks (the\n * chain produces a block roughly every 100-130ms and is busy). The whale\n * source therefore watches an explicit, address-filtered *active pool set*\n * assembled from launchpad activity: NOXA pools are known at launch, Odyssey\n * pools are known at graduation.\n */\nexport interface PoolEntry {\n pool: Address\n /** The memecoin side of the pool. */\n token: Address\n /** The quote side (WETH or USDG). */\n quoteToken: Address\n launchpad: Launchpad\n /** Block the pool became known (launch for NOXA, graduation for Odyssey). */\n createdBlock: bigint\n}\n\n/** Storage for known memecoin pools and token origins. */\nexport interface PoolRegistry {\n /** Record (or update) a pool. Idempotent. */\n record(entry: PoolEntry): Promise<void>\n /**\n * The pools to watch for whale trades, newest first, capped at `limit`.\n * `pinned` tokens (subscriber watchlists) are always included regardless of\n * age, because a watchlist alert that silently ages out would be a lie.\n */\n active(limit: number, pinned?: readonly Address[]): Promise<PoolEntry[]>\n /** Which launchpad a token came from, or `null` if never seen. */\n originOf(token: Address): Promise<Launchpad | null>\n /** Every pool recorded for a token. */\n poolsFor(token: Address): Promise<PoolEntry[]>\n /** Total pools recorded, for `/metrics`. */\n size(): Promise<number>\n}\n\n/**\n * In-memory pool registry. Correct and dependency-free, but it starts empty\n * on every process start, so the service uses the SQLite-backed registry from\n * `hood-alerts/service` instead. This one is the right choice for scripts,\n * examples and tests.\n */\nexport function createMemoryPoolRegistry(entries: readonly PoolEntry[] = []): PoolRegistry {\n const pools = new Map<string, PoolEntry>()\n for (const entry of entries) pools.set(entry.pool.toLowerCase(), entry)\n\n return {\n async record(entry: PoolEntry): Promise<void> {\n pools.set(entry.pool.toLowerCase(), {\n ...entry,\n pool: getAddress(entry.pool),\n token: getAddress(entry.token),\n quoteToken: getAddress(entry.quoteToken),\n })\n },\n async active(limit: number, pinned: readonly Address[] = []): Promise<PoolEntry[]> {\n const pinnedSet = new Set(pinned.map((a) => a.toLowerCase()))\n const all = [...pools.values()].sort((a, b) => (a.createdBlock > b.createdBlock ? -1 : 1))\n const isPinned = (entry: PoolEntry): boolean => pinnedSet.has(entry.token.toLowerCase())\n // Pinned pools are additional to the limit, not counted against it: a\n // watchlist alert that aged out of the window would be a silent lie.\n const picked = all.filter(isPinned)\n let recent = 0\n for (const entry of all) {\n if (recent >= limit) break\n if (isPinned(entry)) continue\n picked.push(entry)\n recent += 1\n }\n return picked\n },\n async originOf(token: Address): Promise<Launchpad | null> {\n const key = token.toLowerCase()\n for (const entry of pools.values()) {\n if (entry.token.toLowerCase() === key) return entry.launchpad\n }\n return null\n },\n async poolsFor(token: Address): Promise<PoolEntry[]> {\n const key = token.toLowerCase()\n return [...pools.values()].filter((entry) => entry.token.toLowerCase() === key)\n },\n async size(): Promise<number> {\n return pools.size\n },\n }\n}\n","import { formatUnits, type Address } from 'viem'\nimport { USDG_DECIMALS, erc20Abi, type HoodClient } from 'hoodchain'\nimport type { PriceOracle } from './pricing.js'\nimport { quoteTokens } from './pricing.js'\nimport type { PoolRegistry } from './registry.js'\n\n/**\n * Pool liquidity in USD, measured the only way that is honest without an\n * indexer: the pool's live balance of its quote token, doubled.\n *\n * A Uniswap v3 pool holds real balances of both sides. The quote side (WETH\n * or USDG) is the side with a known USD value, so `quoteBalance * 2` is the\n * standard two-sided TVL approximation and it is exactly what a trader means\n * by \"how much liquidity is in there\". For concentrated positions this is the\n * *total* reserve rather than the reserve inside the active tick, which is\n * documented rather than papered over: min/max liquidity rules filter on this\n * number, so subscribers should read it as pool reserves, not tradeable depth.\n */\nexport interface LiquidityReader {\n /** USD liquidity of a specific pool, or `null` when the quote side is unpriceable. */\n poolLiquidityUsd(pool: Address, quoteToken: Address): Promise<number | null>\n /**\n * Best-known USD liquidity for a token: the largest of its recorded pools.\n * `null` when the token has no recorded pool (an Odyssey token still on its\n * bonding curve, for example).\n */\n tokenLiquidityUsd(token: Address): Promise<number | null>\n}\n\n/** Options for {@link createLiquidityReader}. */\nexport interface LiquidityReaderOptions {\n /** Cache lifetime for a pool's USD liquidity, in ms. @defaultValue `60_000` */\n ttlMs?: number\n /** Clock injection point for tests. @defaultValue `Date.now` */\n now?: () => number\n}\n\ninterface CacheEntry {\n value: number | null\n at: number\n}\n\n/** Build a caching liquidity reader over live pool balances. */\nexport function createLiquidityReader(\n client: HoodClient,\n oracle: PriceOracle,\n registry: PoolRegistry,\n options: LiquidityReaderOptions = {},\n): LiquidityReader {\n const ttlMs = options.ttlMs ?? 60_000\n const now = options.now ?? Date.now\n const cache = new Map<string, CacheEntry>()\n const { weth, usdg } = quoteTokens(client)\n\n async function readPool(pool: Address, quoteToken: Address): Promise<number | null> {\n const quote = quoteToken.toLowerCase()\n if (quote !== weth.toLowerCase() && quote !== usdg.toLowerCase()) return null\n const balance = await client.public.readContract({\n address: quoteToken,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [pool],\n })\n if (quote === usdg.toLowerCase()) {\n return Number(formatUnits(balance, USDG_DECIMALS)) * 2\n }\n const usd = await oracle.weiToUsd(balance)\n return usd === null ? null : usd * 2\n }\n\n async function poolLiquidityUsd(pool: Address, quoteToken: Address): Promise<number | null> {\n const key = pool.toLowerCase()\n const hit = cache.get(key)\n if (hit && now() - hit.at < ttlMs) return hit.value\n let value: number | null\n try {\n value = await readPool(pool, quoteToken)\n } catch {\n // A failed read must not be reported as zero liquidity: that would\n // silently satisfy every `maxLiquidityUsd` rule. Unknown stays unknown.\n value = null\n }\n cache.set(key, { value, at: now() })\n return value\n }\n\n return {\n poolLiquidityUsd,\n async tokenLiquidityUsd(token: Address): Promise<number | null> {\n const pools = await registry.poolsFor(token)\n if (pools.length === 0) return null\n const values = await Promise.all(\n pools.map((entry) => poolLiquidityUsd(entry.pool, entry.quoteToken)),\n )\n const known = values.filter((v): v is number => v !== null)\n return known.length === 0 ? null : Math.max(...known)\n },\n }\n}\n","import type { Address } from 'viem'\n\n/**\n * Resilient log fetching for Robinhood Chain's public RPC.\n *\n * Two behaviours of the public endpoint drive this helper, both measured\n * against `https://rpc.mainnet.chain.robinhood.com` while building this\n * package:\n *\n * 1. **There is no block-range cap, but there is a 10,000-log result cap.**\n * A query spanning millions of blocks succeeds if it is selective enough\n * (the three Odyssey factories return every log they ever emitted in one\n * call); a query spanning 2,000 blocks fails with\n * `logs matched by query exceeds limit of 10000` if it is not (a topic-only\n * Uniswap v3 `Swap` query does). Fixed-size range chunking therefore\n * cannot guarantee success, so {@link fetchLogRange} bisects the range on\n * that specific error until each half fits.\n * 2. **It rate limits bursts with `Too Many Requests`.** Those are retried\n * with exponential backoff rather than surfaced, because a retryable 429\n * must never advance a caller's block cursor.\n */\n\n/** Options for {@link fetchLogRange}. */\nexport interface FetchLogRangeOptions {\n /**\n * Smallest range the bisector will produce. A single block that still\n * overflows the result cap throws, because there is nothing left to split.\n * @defaultValue `1n`\n */\n minSpan?: bigint\n /** Retries for rate-limit and transient transport errors. @defaultValue `5` */\n retries?: number\n /** Base backoff in ms, doubled per attempt. @defaultValue `500` */\n backoffMs?: number\n /** Sleep injection point for tests. @defaultValue a real timer */\n sleep?: (ms: number) => Promise<void>\n}\n\nconst defaultSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\n/** `true` when the RPC rejected the query for returning too many logs. */\nexport function isResultTooLarge(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error)\n return /exceeds limit|too many results|response size exceeded|query returned more than/i.test(message)\n}\n\n/** `true` when the error is a transient rate limit or timeout worth retrying. */\nexport function isRetryableRpcError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error)\n return /too many requests|rate limit|timed out|timeout|429|503|502|ECONNRESET|socket hang up|fetch failed/i.test(\n message,\n )\n}\n\n/**\n * Fetch logs for `[from, to]` inclusive through `fetcher`, bisecting the range\n * when the RPC reports too many results and retrying transient failures.\n *\n * @throws the underlying error when a single block still overflows the result\n * cap, or when retries are exhausted. Callers must treat a throw as \"range not\n * processed\" and leave their cursor untouched.\n *\n * @example\n * ```ts\n * const logs = await fetchLogRange(\n * (from, to) => hood.public.getLogs({ address: factory, event, fromBlock: from, toBlock: to }),\n * 0n,\n * await hood.public.getBlockNumber(),\n * )\n * ```\n */\nexport async function fetchLogRange<T>(\n fetcher: (from: bigint, to: bigint) => Promise<readonly T[]>,\n from: bigint,\n to: bigint,\n options: FetchLogRangeOptions = {},\n): Promise<T[]> {\n const minSpan = options.minSpan ?? 1n\n const retries = options.retries ?? 5\n const backoffMs = options.backoffMs ?? 500\n const sleep = options.sleep ?? defaultSleep\n\n const run = async (lo: bigint, hi: bigint): Promise<T[]> => {\n let attempt = 0\n for (;;) {\n try {\n return [...(await fetcher(lo, hi))]\n } catch (error) {\n if (isResultTooLarge(error)) {\n const span = hi - lo\n if (span < minSpan || span === 0n) throw error\n const mid = lo + span / 2n\n const left = await run(lo, mid)\n const right = await run(mid + 1n, hi)\n return [...left, ...right]\n }\n if (isRetryableRpcError(error) && attempt < retries) {\n await sleep(backoffMs * 2 ** attempt)\n attempt += 1\n continue\n }\n throw error\n }\n }\n }\n\n if (to < from) return []\n return run(from, to)\n}\n\n/**\n * Split a list of addresses into query-sized batches. The RPC accepts long\n * address arrays, but batching keeps any single response under the result cap\n * and keeps one failing batch from invalidating the whole set.\n */\nexport function batchAddresses(addresses: readonly Address[], size = 200): Address[][] {\n const batches: Address[][] = []\n for (let i = 0; i < addresses.length; i += size) {\n batches.push(addresses.slice(i, i + size) as Address[])\n }\n return batches\n}\n","import { getAddress, type Address, type Hash } from 'viem'\nimport {\n NOXA_ADDRESSES,\n ODYSSEY_ADDRESSES,\n noxaTokenLaunchedEvent,\n odysseyTokenCreatedEvent,\n odysseyTradedEvent,\n odysseyPoolMigratedEvent,\n type HoodClient,\n} from 'hoodchain'\nimport { loadPoolInfo, uniswapV3SwapEvent, type PoolInfo } from 'hoodkit'\nimport { addressUrl, explorerBase, tokenUrl, txUrl } from './explorer.js'\nimport { batchAddresses, fetchLogRange, type FetchLogRangeOptions } from './logs.js'\nimport { createBlockTimeReader, type BlockTimeReader } from './blocktime.js'\nimport { quoteTokens, type PriceOracle } from './pricing.js'\nimport type { PoolRegistry } from './registry.js'\nimport type { TokenMetaReader } from './tokens.js'\nimport type {\n AlertEvent,\n CurveTradeEvent,\n ExplorerLinks,\n GraduationEvent,\n LaunchEvent,\n Launchpad,\n WhaleTradeEvent,\n} from './types.js'\n\n/**\n * The event sources: one per on-chain surface, each responsible for turning a\n * confirmed block range into normalized {@link AlertEvent}s.\n *\n * Sources are deliberately *pull* based (`poll(from, to)`) rather than\n * push/subscription based. The service owns the block cursor and persists it,\n * so a source that is asked for the same range twice must produce identical\n * events, and it does: every event id is derived from `txHash` and `logIndex`.\n * That is what makes crash recovery safe.\n */\n\n/** A pollable on-chain source of alert events. */\nexport interface EventSource {\n /** Stable identifier, used as the persisted cursor key. Never change it. */\n id: string\n /** Human label for logs and `/metrics`. */\n label: string\n /** Earliest block this source can produce events from. */\n startBlock: bigint\n /** Decode `[from, to]` inclusive into events. Must be deterministic. */\n poll(from: bigint, to: bigint): Promise<AlertEvent[]>\n}\n\n/** Wiring shared by every source. */\nexport interface SourceContext {\n client: HoodClient\n oracle: PriceOracle\n tokens: TokenMetaReader\n registry: PoolRegistry\n /** Optional shared block-timestamp reader (one is created when omitted). */\n blockTimes?: BlockTimeReader\n /** Max `eth_getBlockByNumber` calls per poll, for timestamps. @defaultValue `25` */\n maxBlockTimeLookupsPerPoll?: number\n /** Retry/bisect options passed to {@link fetchLogRange}. */\n logOptions?: FetchLogRangeOptions\n /**\n * Tokens that must always be in the whale watcher's pool set (subscriber\n * watchlists). Called once per whale poll.\n */\n pinnedTokens?: () => Promise<readonly Address[]>\n /**\n * Max pools the whale watcher queries per poll, newest first.\n * @defaultValue `400`\n */\n whalePoolLimit?: number\n /**\n * Service-wide floor for whale trades in USD. Swaps below this never become\n * events at all, which keeps the delivery pipeline from doing per-subscriber\n * work on dust. Per-subscription `minUsd` rules are applied on top and can\n * only be stricter.\n * @defaultValue `1000`\n */\n whaleMinUsd?: number\n /** Pools per address-filtered `eth_getLogs` call. @defaultValue `200` */\n whalePoolBatchSize?: number\n}\n\nconst ODYSSEY_FACTORIES: Address[] = [\n ODYSSEY_ADDRESSES.bondingCurveFactory,\n ODYSSEY_ADDRESSES.reflectionFactory,\n ODYSSEY_ADDRESSES.instantFactory,\n]\n\n/** Deterministic event id: reprocessing a range regenerates the same value. */\nexport function eventId(kind: string, transactionHash: Hash, logIndex: number): string {\n return `${kind}:${transactionHash.toLowerCase()}:${logIndex}`\n}\n\nfunction links(base: string, token: Address, actor: Address, hash: Hash, pool?: Address): ExplorerLinks {\n return {\n tx: txUrl(base, hash),\n token: tokenUrl(base, token),\n actor: addressUrl(base, actor),\n ...(pool ? { pool: addressUrl(base, pool) } : {}),\n }\n}\n\n/**\n * Value a raw amount of a quote token in USD. Returns `null` for any token\n * that is neither USDG nor WETH, because there is no honest rate for it.\n */\nasync function valueQuote(\n ctx: SourceContext,\n quoteToken: Address,\n raw: bigint,\n): Promise<number | null> {\n const { weth, usdg } = quoteTokens(ctx.client)\n const token = quoteToken.toLowerCase()\n if (token === usdg.toLowerCase()) return ctx.oracle.usdgToUsd(raw)\n if (token === weth.toLowerCase()) return ctx.oracle.weiToUsd(raw)\n return null\n}\n\nfunction assertMainnet(client: HoodClient): void {\n if (client.network !== 'mainnet') {\n throw new Error(\n 'hood-alerts: the NOXA and Odyssey launchpads are deployed on Robinhood Chain mainnet (chain 4663) only. ' +\n 'Create the client with { chain: \"mainnet\" } (the default) to watch them.',\n )\n }\n}\n\n/**\n * Build every event source for a mainnet client.\n *\n * @throws when the client targets testnet: neither launchpad exists there, so\n * silently returning an empty source list would look like \"no launches\".\n *\n * @example\n * ```ts\n * import { createHoodClient } from 'hoodchain'\n * import {\n * createEventSources, createPriceOracle, createTokenMetaReader, createMemoryPoolRegistry,\n * } from 'hood-alerts/events'\n *\n * const hood = createHoodClient()\n * const sources = createEventSources({\n * client: hood,\n * oracle: createPriceOracle(hood),\n * tokens: createTokenMetaReader(hood),\n * registry: createMemoryPoolRegistry(),\n * })\n * const head = await hood.public.getBlockNumber()\n * for (const source of sources) {\n * console.log(source.label, (await source.poll(head - 2_000n, head)).length)\n * }\n * ```\n */\nexport function createEventSources(ctx: SourceContext): EventSource[] {\n assertMainnet(ctx.client)\n return [\n createNoxaLaunchSource(ctx),\n createOdysseyLaunchSource(ctx),\n createOdysseyCurveTradeSource(ctx),\n createOdysseyGraduationSource(ctx),\n createWhaleTradeSource(ctx),\n ]\n}\n\nfunction blockTimes(ctx: SourceContext): BlockTimeReader {\n ctx.blockTimes ??= createBlockTimeReader(ctx.client)\n return ctx.blockTimes\n}\n\nasync function stampTimes(\n ctx: SourceContext,\n events: AlertEvent[],\n): Promise<AlertEvent[]> {\n if (events.length === 0) return events\n const reader = blockTimes(ctx)\n const times = await reader.getMany(\n events.map((e) => e.blockNumber),\n ctx.maxBlockTimeLookupsPerPoll ?? 25,\n )\n for (const event of events) event.timestampMs = times.get(event.blockNumber) ?? null\n return events\n}\n\n/** NOXA `TokenLaunched`: an instant listing with a live Uniswap v3 pool. */\nexport function createNoxaLaunchSource(ctx: SourceContext): EventSource {\n const base = explorerBase(ctx.client.network)\n return {\n id: 'noxa:launch',\n label: 'NOXA launches',\n startBlock: NOXA_ADDRESSES.deployBlock,\n async poll(from: bigint, to: bigint): Promise<AlertEvent[]> {\n const logs = await fetchLogRange(\n (lo, hi) =>\n ctx.client.public.getLogs({\n address: NOXA_ADDRESSES.launchFactory,\n event: noxaTokenLaunchedEvent,\n fromBlock: lo,\n toBlock: hi,\n }),\n from,\n to,\n ctx.logOptions ?? {},\n )\n\n const events: LaunchEvent[] = []\n for (const log of logs) {\n const token = log.args.token as Address | undefined\n const deployer = log.args.deployer as Address | undefined\n if (!token || !deployer) continue\n const pool = (log.args.pool as Address | undefined) ?? null\n const pairToken = (log.args.pairToken as Address | undefined) ?? null\n const initialBuyAmount = (log.args.initialBuyAmount as bigint | undefined) ?? 0n\n\n if (pool && pairToken) {\n await ctx.registry.record({\n pool: getAddress(pool),\n token: getAddress(token),\n quoteToken: getAddress(pairToken),\n launchpad: 'noxa',\n createdBlock: log.blockNumber,\n })\n }\n const meta = await ctx.tokens.get(token)\n const usdValue =\n pairToken && initialBuyAmount > 0n ? await valueQuote(ctx, pairToken, initialBuyAmount) : null\n\n events.push({\n id: eventId('launch', log.transactionHash, log.logIndex),\n kind: 'launch',\n launchpad: 'noxa',\n token: getAddress(token),\n symbol: meta.symbol,\n name: meta.name,\n actor: getAddress(deployer),\n blockNumber: log.blockNumber,\n logIndex: log.logIndex,\n transactionHash: log.transactionHash,\n timestampMs: null,\n usdValue,\n explorer: links(base, token, deployer, log.transactionHash, pool ?? undefined),\n pool: pool ? getAddress(pool) : null,\n pairToken: pairToken ? getAddress(pairToken) : null,\n initialBuyAmount,\n positionId: (log.args.positionId as bigint | undefined) ?? null,\n // NOXA has no bonding curve at all: a launch is already a listing.\n instantListing: true,\n })\n }\n return stampTimes(ctx, events)\n },\n }\n}\n\n/** The Odyssey `TokenCreated`: a bonding curve opening. */\nexport function createOdysseyLaunchSource(ctx: SourceContext): EventSource {\n const base = explorerBase(ctx.client.network)\n return {\n id: 'odyssey:launch',\n label: 'Odyssey launches',\n startBlock: NOXA_ADDRESSES.deployBlock,\n async poll(from: bigint, to: bigint): Promise<AlertEvent[]> {\n const logs = await fetchLogRange(\n (lo, hi) =>\n ctx.client.public.getLogs({\n address: ODYSSEY_FACTORIES,\n event: odysseyTokenCreatedEvent,\n fromBlock: lo,\n toBlock: hi,\n }),\n from,\n to,\n ctx.logOptions ?? {},\n )\n\n const events: LaunchEvent[] = []\n for (const log of logs) {\n const token = log.args.token as Address | undefined\n const creator = log.args.creator as Address | undefined\n if (!token || !creator) continue\n const meta = await ctx.tokens.get(token)\n events.push({\n id: eventId('launch', log.transactionHash, log.logIndex),\n kind: 'launch',\n launchpad: 'odyssey',\n token: getAddress(token),\n symbol: meta.symbol,\n name: meta.name,\n actor: getAddress(creator),\n blockNumber: log.blockNumber,\n logIndex: log.logIndex,\n transactionHash: log.transactionHash,\n timestampMs: null,\n // The curve holds liquidity until graduation, so a new Odyssey token\n // has no pool and no priceable notional at creation time.\n usdValue: null,\n explorer: links(base, token, creator, log.transactionHash),\n pool: null,\n pairToken: null,\n initialBuyAmount: 0n,\n positionId: null,\n instantListing:\n log.address.toLowerCase() === ODYSSEY_ADDRESSES.instantFactory.toLowerCase(),\n })\n }\n return stampTimes(ctx, events)\n },\n }\n}\n\n/** The Odyssey `Traded`: every bonding-curve buy and sell. */\nexport function createOdysseyCurveTradeSource(ctx: SourceContext): EventSource {\n const base = explorerBase(ctx.client.network)\n return {\n id: 'odyssey:curve_trade',\n label: 'Odyssey curve trades',\n startBlock: NOXA_ADDRESSES.deployBlock,\n async poll(from: bigint, to: bigint): Promise<AlertEvent[]> {\n const logs = await fetchLogRange(\n (lo, hi) =>\n ctx.client.public.getLogs({\n address: ODYSSEY_FACTORIES,\n event: odysseyTradedEvent,\n fromBlock: lo,\n toBlock: hi,\n }),\n from,\n to,\n ctx.logOptions ?? {},\n )\n\n const events: CurveTradeEvent[] = []\n for (const log of logs) {\n const token = log.args.token as Address | undefined\n const trader = log.args.trader as Address | undefined\n if (!token || !trader) continue\n const tokenAmount = (log.args.tokenAmount as bigint | undefined) ?? 0n\n const quoteAmountWei = (log.args.quoteAmount as bigint | undefined) ?? 0n\n const meta = await ctx.tokens.get(token)\n // The curve quotes in native ETH, so the ETH/USD rate from live\n // WETH/USDG liquidity is the only conversion involved.\n const usdValue = await ctx.oracle.weiToUsd(quoteAmountWei)\n const priceEth =\n tokenAmount > 0n\n ? Number(quoteAmountWei) / Number(tokenAmount)\n : null\n\n events.push({\n id: eventId('curve_trade', log.transactionHash, log.logIndex),\n kind: 'curve_trade',\n launchpad: 'odyssey',\n token: getAddress(token),\n symbol: meta.symbol,\n name: meta.name,\n actor: getAddress(trader),\n blockNumber: log.blockNumber,\n logIndex: log.logIndex,\n transactionHash: log.transactionHash,\n timestampMs: null,\n usdValue,\n explorer: links(base, token, trader, log.transactionHash),\n side: (log.args.isBuy as boolean | undefined) ? 'buy' : 'sell',\n tokenAmount,\n quoteAmountWei,\n feeWei: (log.args.fee as bigint | undefined) ?? 0n,\n virtualQuoteWei: (log.args.virtualQuote as bigint | undefined) ?? 0n,\n virtualTokenAmount: (log.args.virtualToken as bigint | undefined) ?? 0n,\n priceEth,\n })\n }\n return stampTimes(ctx, events)\n },\n }\n}\n\n/**\n * The Odyssey `PoolMigrated`: the curve filled and liquidity moved to a locked\n * Uniswap v3 pool. This is the only real graduation event on the chain. NOXA\n * cannot emit one, by construction.\n */\nexport function createOdysseyGraduationSource(ctx: SourceContext): EventSource {\n const base = explorerBase(ctx.client.network)\n return {\n id: 'odyssey:graduation',\n label: 'Odyssey graduations',\n startBlock: NOXA_ADDRESSES.deployBlock,\n async poll(from: bigint, to: bigint): Promise<AlertEvent[]> {\n const logs = await fetchLogRange(\n (lo, hi) =>\n ctx.client.public.getLogs({\n address: ODYSSEY_FACTORIES,\n event: odysseyPoolMigratedEvent,\n fromBlock: lo,\n toBlock: hi,\n }),\n from,\n to,\n ctx.logOptions ?? {},\n )\n\n const events: GraduationEvent[] = []\n for (const log of logs) {\n const token = log.args.token as Address | undefined\n const pool = log.args.pool as Address | undefined\n if (!token || !pool) continue\n const quoteUsed = (log.args.usdcUsed as bigint | undefined) ?? 0n\n\n // Read the pool to learn which side is the quote token, then record it\n // so the whale watcher starts covering this graduate immediately.\n let quoteToken: Address | null = null\n try {\n const info = await loadPoolInfo(ctx.client, pool)\n quoteToken =\n info.token0.toLowerCase() === token.toLowerCase() ? info.token1 : info.token0\n await ctx.registry.record({\n pool: getAddress(pool),\n token: getAddress(token),\n quoteToken,\n launchpad: 'odyssey',\n createdBlock: log.blockNumber,\n })\n } catch {\n // Pool metadata unavailable (pruned archive state, RPC blip). The\n // graduation is still real and still worth alerting on.\n quoteToken = null\n }\n\n const meta = await ctx.tokens.get(token)\n const usdValue = quoteToken ? await valueQuote(ctx, quoteToken, quoteUsed) : null\n\n events.push({\n id: eventId('graduation', log.transactionHash, log.logIndex),\n kind: 'graduation',\n launchpad: 'odyssey',\n token: getAddress(token),\n symbol: meta.symbol,\n name: meta.name,\n // A migration is executed by the factory on behalf of the curve, so\n // the closest thing to an actor is the factory that emitted it.\n actor: getAddress(log.address),\n blockNumber: log.blockNumber,\n logIndex: log.logIndex,\n transactionHash: log.transactionHash,\n timestampMs: null,\n usdValue,\n explorer: links(base, token, log.address as Address, log.transactionHash, pool),\n pool: getAddress(pool),\n positionId: (log.args.tokenId as bigint | undefined) ?? 0n,\n liquidity: (log.args.liquidity as bigint | undefined) ?? 0n,\n tokenUsed: (log.args.tokenUsed as bigint | undefined) ?? 0n,\n quoteUsed,\n })\n }\n return stampTimes(ctx, events)\n },\n }\n}\n\n/**\n * Uniswap v3 swaps on known memecoin pools, above the service-wide USD floor.\n *\n * The pool set comes from {@link PoolRegistry}: NOXA pools are known from\n * launch, Odyssey pools from graduation. That is a deliberate design choice,\n * not a shortcut. A topic-only `Swap` query across the whole chain overflows\n * the RPC's 10,000-log result cap inside 2,000 blocks, so \"watch every pool\"\n * is not something the public endpoint can serve. Watching the launchpad pool\n * set is both cheaper and exactly the product: memecoin whale alerts.\n */\nexport function createWhaleTradeSource(ctx: SourceContext): EventSource {\n const base = explorerBase(ctx.client.network)\n const poolInfoCache = new Map<string, PoolInfo>()\n const minUsd = ctx.whaleMinUsd ?? 1000\n\n async function poolInfo(pool: Address): Promise<PoolInfo | null> {\n const key = pool.toLowerCase()\n const hit = poolInfoCache.get(key)\n if (hit) return hit\n try {\n const info = await loadPoolInfo(ctx.client, pool)\n poolInfoCache.set(key, info)\n return info\n } catch {\n return null\n }\n }\n\n return {\n id: 'uniswap:whale_trade',\n label: 'Whale trades',\n startBlock: NOXA_ADDRESSES.deployBlock,\n async poll(from: bigint, to: bigint): Promise<AlertEvent[]> {\n const pinned = ctx.pinnedTokens ? await ctx.pinnedTokens() : []\n const entries = await ctx.registry.active(ctx.whalePoolLimit ?? 400, pinned)\n if (entries.length === 0) return []\n\n const byPool = new Map(entries.map((entry) => [entry.pool.toLowerCase(), entry]))\n const batches = batchAddresses(\n entries.map((entry) => entry.pool),\n ctx.whalePoolBatchSize ?? 200,\n )\n\n const logs = (\n await Promise.all(\n batches.map((batch) =>\n fetchLogRange(\n (lo, hi) =>\n ctx.client.public.getLogs({\n address: batch,\n event: uniswapV3SwapEvent,\n fromBlock: lo,\n toBlock: hi,\n }),\n from,\n to,\n ctx.logOptions ?? {},\n ),\n ),\n )\n ).flat()\n\n const events: WhaleTradeEvent[] = []\n for (const log of logs) {\n const entry = byPool.get(log.address.toLowerCase())\n if (!entry) continue\n const info = await poolInfo(entry.pool)\n if (!info) continue\n\n const amount0 = (log.args.amount0 as bigint | undefined) ?? 0n\n const amount1 = (log.args.amount1 as bigint | undefined) ?? 0n\n const quoteIsToken0 = info.token0.toLowerCase() === entry.quoteToken.toLowerCase()\n const quoteRaw = quoteIsToken0 ? amount0 : amount1\n const tokenRaw = quoteIsToken0 ? amount1 : amount0\n if (quoteRaw === 0n || tokenRaw === 0n) continue\n\n const usdValue = await valueQuote(ctx, entry.quoteToken, quoteRaw)\n if (usdValue === null || usdValue < minUsd) continue\n\n const quoteDecimals = quoteIsToken0 ? info.decimals0 : info.decimals1\n const tokenDecimals = quoteIsToken0 ? info.decimals1 : info.decimals0\n const quoteAmount = Math.abs(Number(quoteRaw)) / 10 ** quoteDecimals\n const tokenAmount = Math.abs(Number(tokenRaw)) / 10 ** tokenDecimals\n\n const [tokenMeta, quoteMeta] = await Promise.all([\n ctx.tokens.get(entry.token),\n ctx.tokens.get(entry.quoteToken),\n ])\n const trader = (log.args.recipient as Address | undefined) ?? (log.args.sender as Address)\n\n events.push({\n id: eventId('whale_trade', log.transactionHash, log.logIndex),\n kind: 'whale_trade',\n launchpad: entry.launchpad,\n token: entry.token,\n symbol: tokenMeta.symbol,\n name: tokenMeta.name,\n actor: getAddress(trader),\n blockNumber: log.blockNumber,\n logIndex: log.logIndex,\n transactionHash: log.transactionHash,\n timestampMs: null,\n usdValue,\n explorer: links(base, entry.token, trader, log.transactionHash, entry.pool),\n pool: entry.pool,\n quoteToken: entry.quoteToken,\n quoteSymbol: quoteMeta.symbol ?? 'quote',\n // The memecoin left the pool (negative delta) means the trader bought it.\n side: tokenRaw < 0n ? 'buy' : 'sell',\n tokenAmount,\n quoteAmount,\n price: tokenAmount > 0 ? quoteAmount / tokenAmount : 0,\n feeTier: info.fee,\n })\n }\n return stampTimes(ctx, events)\n },\n }\n}\n\n/** Every launchpad an event source can attribute an event to. */\nexport const SUPPORTED_LAUNCHPADS: readonly Launchpad[] = ['noxa', 'odyssey']\n"],"mappings":"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","names":["import_viem","import_hoodchain","import_viem","import_viem","import_hoodchain","import_viem","import_hoodchain"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export { A as AlertEvent, a as AlertEventBase, b as AlertEventKind, C as CurveTradeEvent, E as EVENT_KIND_LABELS, c as ExplorerLinks, G as GraduationEvent, L as LAUNCHPAD_EVENT_KINDS, d as LaunchEvent, e as Launchpad, T as TradeSide, W as WhaleTradeEvent, i as isKind } from '../types-DFPpTMWo.cjs';
|
|
2
|
+
import { Address, Hash } from 'viem';
|
|
3
|
+
import { P as PriceOracle } from '../pricing-BumgLpvB.cjs';
|
|
4
|
+
export { a as PriceOracleOptions, c as createPriceOracle, b as createStaticPriceOracle, q as quoteTokens } from '../pricing-BumgLpvB.cjs';
|
|
5
|
+
export { B as BlockTimeReader, E as EventSource, S as SUPPORTED_LAUNCHPADS, a as SourceContext, T as TokenMeta, b as TokenMetaReader, c as createBlockTimeReader, d as createEventSources, e as createNoxaLaunchSource, f as createOdysseyCurveTradeSource, g as createOdysseyGraduationSource, h as createOdysseyLaunchSource, i as createTokenMetaReader, j as createWhaleTradeSource, k as eventId } from '../sources-CpDOUlzx.cjs';
|
|
6
|
+
import { a as PoolRegistry } from '../registry-DePA3IyS.cjs';
|
|
7
|
+
export { P as PoolEntry, c as createMemoryPoolRegistry } from '../registry-DePA3IyS.cjs';
|
|
8
|
+
import { HoodClient } from 'hoodchain';
|
|
9
|
+
export { F as FetchLogRangeOptions, b as batchAddresses, f as fetchLogRange, i as isResultTooLarge, a as isRetryableRpcError } from '../logs-D7sYBvSy.cjs';
|
|
10
|
+
|
|
11
|
+
/** Blockscout explorer base URL for Robinhood Chain mainnet (chain 4663). */
|
|
12
|
+
declare const MAINNET_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
13
|
+
/** Explorer base URL for Robinhood Chain testnet (chain 46630). */
|
|
14
|
+
declare const TESTNET_EXPLORER = "https://explorer.testnet.chain.robinhood.com";
|
|
15
|
+
/** Resolve the explorer base URL for a network name. */
|
|
16
|
+
declare function explorerBase(network: 'mainnet' | 'testnet'): string;
|
|
17
|
+
/** Link to a transaction. */
|
|
18
|
+
declare function txUrl(base: string, hash: Hash): string;
|
|
19
|
+
/** Link to an address (wallet or contract). */
|
|
20
|
+
declare function addressUrl(base: string, address: Address): string;
|
|
21
|
+
/** Link to a token page (Blockscout renders holders/transfers there). */
|
|
22
|
+
declare function tokenUrl(base: string, token: Address): string;
|
|
23
|
+
/** Link to a block. */
|
|
24
|
+
declare function blockUrl(base: string, blockNumber: bigint): string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pool liquidity in USD, measured the only way that is honest without an
|
|
28
|
+
* indexer: the pool's live balance of its quote token, doubled.
|
|
29
|
+
*
|
|
30
|
+
* A Uniswap v3 pool holds real balances of both sides. The quote side (WETH
|
|
31
|
+
* or USDG) is the side with a known USD value, so `quoteBalance * 2` is the
|
|
32
|
+
* standard two-sided TVL approximation and it is exactly what a trader means
|
|
33
|
+
* by "how much liquidity is in there". For concentrated positions this is the
|
|
34
|
+
* *total* reserve rather than the reserve inside the active tick, which is
|
|
35
|
+
* documented rather than papered over: min/max liquidity rules filter on this
|
|
36
|
+
* number, so subscribers should read it as pool reserves, not tradeable depth.
|
|
37
|
+
*/
|
|
38
|
+
interface LiquidityReader {
|
|
39
|
+
/** USD liquidity of a specific pool, or `null` when the quote side is unpriceable. */
|
|
40
|
+
poolLiquidityUsd(pool: Address, quoteToken: Address): Promise<number | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Best-known USD liquidity for a token: the largest of its recorded pools.
|
|
43
|
+
* `null` when the token has no recorded pool (an Odyssey token still on its
|
|
44
|
+
* bonding curve, for example).
|
|
45
|
+
*/
|
|
46
|
+
tokenLiquidityUsd(token: Address): Promise<number | null>;
|
|
47
|
+
}
|
|
48
|
+
/** Options for {@link createLiquidityReader}. */
|
|
49
|
+
interface LiquidityReaderOptions {
|
|
50
|
+
/** Cache lifetime for a pool's USD liquidity, in ms. @defaultValue `60_000` */
|
|
51
|
+
ttlMs?: number;
|
|
52
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/** Build a caching liquidity reader over live pool balances. */
|
|
56
|
+
declare function createLiquidityReader(client: HoodClient, oracle: PriceOracle, registry: PoolRegistry, options?: LiquidityReaderOptions): LiquidityReader;
|
|
57
|
+
|
|
58
|
+
export { type LiquidityReader, type LiquidityReaderOptions, MAINNET_EXPLORER, PoolRegistry, PriceOracle, TESTNET_EXPLORER, addressUrl, blockUrl, createLiquidityReader, explorerBase, tokenUrl, txUrl };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export { A as AlertEvent, a as AlertEventBase, b as AlertEventKind, C as CurveTradeEvent, E as EVENT_KIND_LABELS, c as ExplorerLinks, G as GraduationEvent, L as LAUNCHPAD_EVENT_KINDS, d as LaunchEvent, e as Launchpad, T as TradeSide, W as WhaleTradeEvent, i as isKind } from '../types-DFPpTMWo.js';
|
|
2
|
+
import { Address, Hash } from 'viem';
|
|
3
|
+
import { P as PriceOracle } from '../pricing-BumgLpvB.js';
|
|
4
|
+
export { a as PriceOracleOptions, c as createPriceOracle, b as createStaticPriceOracle, q as quoteTokens } from '../pricing-BumgLpvB.js';
|
|
5
|
+
export { B as BlockTimeReader, E as EventSource, S as SUPPORTED_LAUNCHPADS, a as SourceContext, T as TokenMeta, b as TokenMetaReader, c as createBlockTimeReader, d as createEventSources, e as createNoxaLaunchSource, f as createOdysseyCurveTradeSource, g as createOdysseyGraduationSource, h as createOdysseyLaunchSource, i as createTokenMetaReader, j as createWhaleTradeSource, k as eventId } from '../sources-CAaQR6-N.js';
|
|
6
|
+
import { a as PoolRegistry } from '../registry-D3We8Lg6.js';
|
|
7
|
+
export { P as PoolEntry, c as createMemoryPoolRegistry } from '../registry-D3We8Lg6.js';
|
|
8
|
+
import { HoodClient } from 'hoodchain';
|
|
9
|
+
export { F as FetchLogRangeOptions, b as batchAddresses, f as fetchLogRange, i as isResultTooLarge, a as isRetryableRpcError } from '../logs-D7sYBvSy.js';
|
|
10
|
+
|
|
11
|
+
/** Blockscout explorer base URL for Robinhood Chain mainnet (chain 4663). */
|
|
12
|
+
declare const MAINNET_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
13
|
+
/** Explorer base URL for Robinhood Chain testnet (chain 46630). */
|
|
14
|
+
declare const TESTNET_EXPLORER = "https://explorer.testnet.chain.robinhood.com";
|
|
15
|
+
/** Resolve the explorer base URL for a network name. */
|
|
16
|
+
declare function explorerBase(network: 'mainnet' | 'testnet'): string;
|
|
17
|
+
/** Link to a transaction. */
|
|
18
|
+
declare function txUrl(base: string, hash: Hash): string;
|
|
19
|
+
/** Link to an address (wallet or contract). */
|
|
20
|
+
declare function addressUrl(base: string, address: Address): string;
|
|
21
|
+
/** Link to a token page (Blockscout renders holders/transfers there). */
|
|
22
|
+
declare function tokenUrl(base: string, token: Address): string;
|
|
23
|
+
/** Link to a block. */
|
|
24
|
+
declare function blockUrl(base: string, blockNumber: bigint): string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pool liquidity in USD, measured the only way that is honest without an
|
|
28
|
+
* indexer: the pool's live balance of its quote token, doubled.
|
|
29
|
+
*
|
|
30
|
+
* A Uniswap v3 pool holds real balances of both sides. The quote side (WETH
|
|
31
|
+
* or USDG) is the side with a known USD value, so `quoteBalance * 2` is the
|
|
32
|
+
* standard two-sided TVL approximation and it is exactly what a trader means
|
|
33
|
+
* by "how much liquidity is in there". For concentrated positions this is the
|
|
34
|
+
* *total* reserve rather than the reserve inside the active tick, which is
|
|
35
|
+
* documented rather than papered over: min/max liquidity rules filter on this
|
|
36
|
+
* number, so subscribers should read it as pool reserves, not tradeable depth.
|
|
37
|
+
*/
|
|
38
|
+
interface LiquidityReader {
|
|
39
|
+
/** USD liquidity of a specific pool, or `null` when the quote side is unpriceable. */
|
|
40
|
+
poolLiquidityUsd(pool: Address, quoteToken: Address): Promise<number | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Best-known USD liquidity for a token: the largest of its recorded pools.
|
|
43
|
+
* `null` when the token has no recorded pool (an Odyssey token still on its
|
|
44
|
+
* bonding curve, for example).
|
|
45
|
+
*/
|
|
46
|
+
tokenLiquidityUsd(token: Address): Promise<number | null>;
|
|
47
|
+
}
|
|
48
|
+
/** Options for {@link createLiquidityReader}. */
|
|
49
|
+
interface LiquidityReaderOptions {
|
|
50
|
+
/** Cache lifetime for a pool's USD liquidity, in ms. @defaultValue `60_000` */
|
|
51
|
+
ttlMs?: number;
|
|
52
|
+
/** Clock injection point for tests. @defaultValue `Date.now` */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/** Build a caching liquidity reader over live pool balances. */
|
|
56
|
+
declare function createLiquidityReader(client: HoodClient, oracle: PriceOracle, registry: PoolRegistry, options?: LiquidityReaderOptions): LiquidityReader;
|
|
57
|
+
|
|
58
|
+
export { type LiquidityReader, type LiquidityReaderOptions, MAINNET_EXPLORER, PoolRegistry, PriceOracle, TESTNET_EXPLORER, addressUrl, blockUrl, createLiquidityReader, explorerBase, tokenUrl, txUrl };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMemoryPoolRegistry
|
|
3
|
+
} from "../chunk-IUOEGBF6.js";
|
|
4
|
+
import {
|
|
5
|
+
MAINNET_EXPLORER,
|
|
6
|
+
SUPPORTED_LAUNCHPADS,
|
|
7
|
+
TESTNET_EXPLORER,
|
|
8
|
+
addressUrl,
|
|
9
|
+
blockUrl,
|
|
10
|
+
createBlockTimeReader,
|
|
11
|
+
createEventSources,
|
|
12
|
+
createLiquidityReader,
|
|
13
|
+
createNoxaLaunchSource,
|
|
14
|
+
createOdysseyCurveTradeSource,
|
|
15
|
+
createOdysseyGraduationSource,
|
|
16
|
+
createOdysseyLaunchSource,
|
|
17
|
+
createTokenMetaReader,
|
|
18
|
+
createWhaleTradeSource,
|
|
19
|
+
eventId,
|
|
20
|
+
explorerBase,
|
|
21
|
+
tokenUrl,
|
|
22
|
+
txUrl
|
|
23
|
+
} from "../chunk-CBNZ6AZT.js";
|
|
24
|
+
import {
|
|
25
|
+
createPriceOracle,
|
|
26
|
+
createStaticPriceOracle,
|
|
27
|
+
quoteTokens
|
|
28
|
+
} from "../chunk-6JQK5G3Z.js";
|
|
29
|
+
import {
|
|
30
|
+
EVENT_KIND_LABELS,
|
|
31
|
+
LAUNCHPAD_EVENT_KINDS,
|
|
32
|
+
batchAddresses,
|
|
33
|
+
fetchLogRange,
|
|
34
|
+
isKind,
|
|
35
|
+
isResultTooLarge,
|
|
36
|
+
isRetryableRpcError
|
|
37
|
+
} from "../chunk-3LHCQA4Z.js";
|
|
38
|
+
export {
|
|
39
|
+
EVENT_KIND_LABELS,
|
|
40
|
+
LAUNCHPAD_EVENT_KINDS,
|
|
41
|
+
MAINNET_EXPLORER,
|
|
42
|
+
SUPPORTED_LAUNCHPADS,
|
|
43
|
+
TESTNET_EXPLORER,
|
|
44
|
+
addressUrl,
|
|
45
|
+
batchAddresses,
|
|
46
|
+
blockUrl,
|
|
47
|
+
createBlockTimeReader,
|
|
48
|
+
createEventSources,
|
|
49
|
+
createLiquidityReader,
|
|
50
|
+
createMemoryPoolRegistry,
|
|
51
|
+
createNoxaLaunchSource,
|
|
52
|
+
createOdysseyCurveTradeSource,
|
|
53
|
+
createOdysseyGraduationSource,
|
|
54
|
+
createOdysseyLaunchSource,
|
|
55
|
+
createPriceOracle,
|
|
56
|
+
createStaticPriceOracle,
|
|
57
|
+
createTokenMetaReader,
|
|
58
|
+
createWhaleTradeSource,
|
|
59
|
+
eventId,
|
|
60
|
+
explorerBase,
|
|
61
|
+
fetchLogRange,
|
|
62
|
+
isKind,
|
|
63
|
+
isResultTooLarge,
|
|
64
|
+
isRetryableRpcError,
|
|
65
|
+
quoteTokens,
|
|
66
|
+
tokenUrl,
|
|
67
|
+
txUrl
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { A as AlertEvent } from './types-DFPpTMWo.js';
|
|
2
|
+
import { R as RenderedAlert } from './types-C0ETog04.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Message formatting, one alert at a time, for both platforms.
|
|
6
|
+
*
|
|
7
|
+
* Every piece of chain-derived text (token name, symbol) goes through the
|
|
8
|
+
* platform's escaper. Every piece of package-authored text (labels, headings)
|
|
9
|
+
* is written to be escape-safe already, so the output stays readable instead
|
|
10
|
+
* of being a wall of backslashes.
|
|
11
|
+
*/
|
|
12
|
+
/** Colour per event kind, as Discord's decimal RGB. */
|
|
13
|
+
declare const EMBED_COLORS: {
|
|
14
|
+
readonly launch: 2278750;
|
|
15
|
+
readonly curve_trade: 3718648;
|
|
16
|
+
readonly graduation: 11032055;
|
|
17
|
+
readonly whale_trade: 16096779;
|
|
18
|
+
};
|
|
19
|
+
/** Compact USD rendering: `$1,234.50`, `$12.4K`, `$3.1M`. */
|
|
20
|
+
declare function formatUsd(value: number | null): string;
|
|
21
|
+
/** Human amount rendering with sane precision across 12 orders of magnitude. */
|
|
22
|
+
declare function formatAmount(value: number): string;
|
|
23
|
+
/** ETH amount from wei, with a trailing `ETH`. */
|
|
24
|
+
declare function formatEth(wei: bigint): string;
|
|
25
|
+
/** The one-line headline for an event, before escaping. */
|
|
26
|
+
declare function headline(event: AlertEvent): string;
|
|
27
|
+
/** Label/value pairs describing an event, before escaping. */
|
|
28
|
+
declare function detailRows(event: AlertEvent): {
|
|
29
|
+
label: string;
|
|
30
|
+
value: string;
|
|
31
|
+
inline?: boolean;
|
|
32
|
+
}[];
|
|
33
|
+
/** Options for {@link renderAlert}. */
|
|
34
|
+
interface RenderOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Telegram parse mode. HTML is the default: its escape rules have three
|
|
37
|
+
* special characters instead of eighteen, which makes malformed-entity 400s
|
|
38
|
+
* far less likely on adversarial token names.
|
|
39
|
+
* @defaultValue `'HTML'`
|
|
40
|
+
*/
|
|
41
|
+
parseMode?: 'HTML' | 'MarkdownV2';
|
|
42
|
+
/** Footer text, e.g. the rule that matched. */
|
|
43
|
+
footer?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Render an event for both platforms.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const alert = renderAlert(event, { footer: 'rule: whales' })
|
|
51
|
+
* await telegram.send('123456789', alert)
|
|
52
|
+
* await discord.send('987654321', alert)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function renderAlert(event: AlertEvent, options?: RenderOptions): RenderedAlert;
|
|
56
|
+
|
|
57
|
+
export { EMBED_COLORS as E, type RenderOptions as R, formatEth as a, formatUsd as b, detailRows as d, formatAmount as f, headline as h, renderAlert as r };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { A as AlertEvent } from './types-DFPpTMWo.cjs';
|
|
2
|
+
import { R as RenderedAlert } from './types-CcoKpStA.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Message formatting, one alert at a time, for both platforms.
|
|
6
|
+
*
|
|
7
|
+
* Every piece of chain-derived text (token name, symbol) goes through the
|
|
8
|
+
* platform's escaper. Every piece of package-authored text (labels, headings)
|
|
9
|
+
* is written to be escape-safe already, so the output stays readable instead
|
|
10
|
+
* of being a wall of backslashes.
|
|
11
|
+
*/
|
|
12
|
+
/** Colour per event kind, as Discord's decimal RGB. */
|
|
13
|
+
declare const EMBED_COLORS: {
|
|
14
|
+
readonly launch: 2278750;
|
|
15
|
+
readonly curve_trade: 3718648;
|
|
16
|
+
readonly graduation: 11032055;
|
|
17
|
+
readonly whale_trade: 16096779;
|
|
18
|
+
};
|
|
19
|
+
/** Compact USD rendering: `$1,234.50`, `$12.4K`, `$3.1M`. */
|
|
20
|
+
declare function formatUsd(value: number | null): string;
|
|
21
|
+
/** Human amount rendering with sane precision across 12 orders of magnitude. */
|
|
22
|
+
declare function formatAmount(value: number): string;
|
|
23
|
+
/** ETH amount from wei, with a trailing `ETH`. */
|
|
24
|
+
declare function formatEth(wei: bigint): string;
|
|
25
|
+
/** The one-line headline for an event, before escaping. */
|
|
26
|
+
declare function headline(event: AlertEvent): string;
|
|
27
|
+
/** Label/value pairs describing an event, before escaping. */
|
|
28
|
+
declare function detailRows(event: AlertEvent): {
|
|
29
|
+
label: string;
|
|
30
|
+
value: string;
|
|
31
|
+
inline?: boolean;
|
|
32
|
+
}[];
|
|
33
|
+
/** Options for {@link renderAlert}. */
|
|
34
|
+
interface RenderOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Telegram parse mode. HTML is the default: its escape rules have three
|
|
37
|
+
* special characters instead of eighteen, which makes malformed-entity 400s
|
|
38
|
+
* far less likely on adversarial token names.
|
|
39
|
+
* @defaultValue `'HTML'`
|
|
40
|
+
*/
|
|
41
|
+
parseMode?: 'HTML' | 'MarkdownV2';
|
|
42
|
+
/** Footer text, e.g. the rule that matched. */
|
|
43
|
+
footer?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Render an event for both platforms.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const alert = renderAlert(event, { footer: 'rule: whales' })
|
|
51
|
+
* await telegram.send('123456789', alert)
|
|
52
|
+
* await discord.send('987654321', alert)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function renderAlert(event: AlertEvent, options?: RenderOptions): RenderedAlert;
|
|
56
|
+
|
|
57
|
+
export { EMBED_COLORS as E, type RenderOptions as R, formatEth as a, formatUsd as b, detailRows as d, formatAmount as f, headline as h, renderAlert as r };
|