dexterm-cli 0.0.0-stage → 0.1.1
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 +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +379 -0
- package/dist/packages/agents/src/executor.js +177 -0
- package/dist/packages/agents/src/gate.js +176 -0
- package/dist/packages/agents/src/identity.js +179 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +114 -0
- package/dist/packages/agents/src/tools.js +69 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +303 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +149 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +361 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +509 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5583 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +339 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +515 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +208 -0
- package/dist/packages/data/src/intel/helius-history.js +59 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +170 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +247 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +849 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +19 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +15 -0
- package/dist/packages/tui/src/keymap.js +355 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +395 -0
- package/dist/packages/voice/src/doctor.js +63 -0
- package/dist/packages/voice/src/engines.js +44 -0
- package/dist/packages/voice/src/index.js +16 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +102 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/say.js +96 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +42 -4
- package/README.md +0 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Program-derived-address helpers (task-1312). The fast lane needs the pump.fun bonding-curve account
|
|
2
|
+
// for a mint so it can accountSubscribe to it, but a Create event is not always on hand (an opened or
|
|
3
|
+
// watchlisted token the user did not see launch). The curve is a PDA of the mint, so we derive it with
|
|
4
|
+
// no @solana/web3.js dependency: base58 decode, the ed25519 on-curve test (TweetNaCl `unpackneg`, the
|
|
5
|
+
// same check @solana/web3.js uses), and findProgramAddress. Validated against live-harvested mint→curve
|
|
6
|
+
// pairs (task-1283 watchlist.json) in the unit tests, including bumps 255/254/253 so the off-curve path
|
|
7
|
+
// is exercised.
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { base58 } from "./solana-rpc.js";
|
|
10
|
+
const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
11
|
+
const B58_MAP = {};
|
|
12
|
+
for (let i = 0; i < B58_ALPHABET.length; i++)
|
|
13
|
+
B58_MAP[B58_ALPHABET[i]] = i;
|
|
14
|
+
/** Base58 (Bitcoin alphabet) decode to bytes. Throws on an invalid character. */
|
|
15
|
+
export function base58Decode(s) {
|
|
16
|
+
const bytes = [0];
|
|
17
|
+
for (const c of s) {
|
|
18
|
+
const carryStart = B58_MAP[c];
|
|
19
|
+
if (carryStart === undefined)
|
|
20
|
+
throw new Error(`invalid base58 character ${JSON.stringify(c)}`);
|
|
21
|
+
let carry = carryStart;
|
|
22
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
23
|
+
carry += bytes[j] * 58;
|
|
24
|
+
bytes[j] = carry & 0xff;
|
|
25
|
+
carry >>= 8;
|
|
26
|
+
}
|
|
27
|
+
while (carry) {
|
|
28
|
+
bytes.push(carry & 0xff);
|
|
29
|
+
carry >>= 8;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// A value of zero leaves the seed digit [0] behind; drop it so leading '1's map to exactly one zero
|
|
33
|
+
// byte each (the System program `111…1` must decode to 32 zero bytes, not 33 — task-1310 round 2).
|
|
34
|
+
if (bytes.length === 1 && bytes[0] === 0)
|
|
35
|
+
bytes.length = 0;
|
|
36
|
+
for (let k = 0; k < s.length && s[k] === '1'; k++)
|
|
37
|
+
bytes.push(0);
|
|
38
|
+
return Uint8Array.from(bytes.reverse());
|
|
39
|
+
}
|
|
40
|
+
// ---- ed25519 field math for the on-curve test ----
|
|
41
|
+
const P = (1n << 255n) - 19n;
|
|
42
|
+
const D = 37095705934669439343138083508754565189542113879843219016388785533085940283555n;
|
|
43
|
+
function mod(a) {
|
|
44
|
+
a %= P;
|
|
45
|
+
return a < 0n ? a + P : a;
|
|
46
|
+
}
|
|
47
|
+
function pow(a, e) {
|
|
48
|
+
let r = 1n;
|
|
49
|
+
a = mod(a);
|
|
50
|
+
while (e > 0n) {
|
|
51
|
+
if (e & 1n)
|
|
52
|
+
r = mod(r * a);
|
|
53
|
+
a = mod(a * a);
|
|
54
|
+
e >>= 1n;
|
|
55
|
+
}
|
|
56
|
+
return r;
|
|
57
|
+
}
|
|
58
|
+
const SQRT_M1 = pow(2n, (P - 1n) / 4n); // sqrt(-1) mod p
|
|
59
|
+
/**
|
|
60
|
+
* True when the 32-byte value is a valid ed25519 point (i.e. ON the curve). A program-derived address
|
|
61
|
+
* must be OFF the curve. Ported from TweetNaCl's `unpackneg`: recover x from y and check v·x² == ±u.
|
|
62
|
+
*/
|
|
63
|
+
export function isOnCurve(bytes) {
|
|
64
|
+
if (bytes.length !== 32)
|
|
65
|
+
return false;
|
|
66
|
+
let y = 0n;
|
|
67
|
+
for (let i = 31; i >= 0; i--)
|
|
68
|
+
y = (y << 8n) | BigInt(bytes[i]);
|
|
69
|
+
y &= (1n << 255n) - 1n; // drop the sign bit
|
|
70
|
+
const y2 = mod(y * y);
|
|
71
|
+
const u = mod(y2 - 1n);
|
|
72
|
+
const v = mod(D * y2 + 1n);
|
|
73
|
+
const v3 = mod(v * v * v);
|
|
74
|
+
const v7 = mod(v3 * v3 * v);
|
|
75
|
+
let x = mod(u * v3 * pow(mod(u * v7), (P - 5n) / 8n));
|
|
76
|
+
const vx2 = mod(v * x * x);
|
|
77
|
+
if (vx2 === u)
|
|
78
|
+
return true;
|
|
79
|
+
if (vx2 === mod(-u)) {
|
|
80
|
+
x = mod(x * SQRT_M1);
|
|
81
|
+
return mod(v * x * x) === u;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const PDA_MARKER = Buffer.from('ProgramDerivedAddress');
|
|
86
|
+
/**
|
|
87
|
+
* Deterministic findProgramAddress: the highest bump (255→0) whose sha256(seeds‖bump‖program‖marker)
|
|
88
|
+
* is off the curve. Throws if none is found (cryptographically ~impossible).
|
|
89
|
+
*/
|
|
90
|
+
export function findProgramAddress(seeds, programId) {
|
|
91
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
92
|
+
const h = createHash('sha256');
|
|
93
|
+
for (const s of seeds)
|
|
94
|
+
h.update(Buffer.from(s));
|
|
95
|
+
h.update(Buffer.from([bump]));
|
|
96
|
+
h.update(Buffer.from(programId));
|
|
97
|
+
h.update(PDA_MARKER);
|
|
98
|
+
const hash = h.digest();
|
|
99
|
+
if (!isOnCurve(hash))
|
|
100
|
+
return { address: base58(hash), bump };
|
|
101
|
+
}
|
|
102
|
+
throw new Error('unable to find a program address bump');
|
|
103
|
+
}
|
|
104
|
+
export const PUMP_PROGRAM_ID = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P';
|
|
105
|
+
const PUMP_CURVE_SEED = Buffer.from('bonding-curve');
|
|
106
|
+
/** The pump.fun bonding-curve account for a mint (PDA of ["bonding-curve", mint] under the pump program). */
|
|
107
|
+
export function pumpBondingCurve(mint) {
|
|
108
|
+
return findProgramAddress([PUMP_CURVE_SEED, base58Decode(mint)], base58Decode(PUMP_PROGRAM_ID)).address;
|
|
109
|
+
}
|
|
110
|
+
/** pump.fun mints are minted with a `pump` suffix; a cheap keyless hint for "this is streamable on a curve". */
|
|
111
|
+
export function isPumpMint(mint) {
|
|
112
|
+
return /pump$/.test(mint);
|
|
113
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// pump.fun bonding-curve price, computed from on-chain account state only — no aggregator (SPEC §3.9,
|
|
2
|
+
// task-1312; ported from the task-1283 spike's pump-curve.mjs and validated live there against
|
|
3
|
+
// PumpPortal's marketCapSol, 28 SOL ✓).
|
|
4
|
+
//
|
|
5
|
+
// The bonding-curve account (owned by program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P) is an Anchor
|
|
6
|
+
// account. Layout (little-endian), confirmed against pump.fun's published IDL:
|
|
7
|
+
// [0..8) 8-byte account discriminator
|
|
8
|
+
// [8..16) virtualTokenReserves u64 (token base units, 6 decimals)
|
|
9
|
+
// [16..24) virtualSolReserves u64 (lamports, 9 decimals)
|
|
10
|
+
// [24..32) realTokenReserves u64
|
|
11
|
+
// [32..40) realSolReserves u64
|
|
12
|
+
// [40..48) tokenTotalSupply u64
|
|
13
|
+
// [48] complete u8 (1 once migrated to a DEX pool)
|
|
14
|
+
// [49..81) creator pubkey (present on curves created after the 2025 creator-fee upgrade)
|
|
15
|
+
//
|
|
16
|
+
// Spot price of 1 token in SOL on the constant-product curve = virtualSol / virtualToken, adjusted for
|
|
17
|
+
// the 9-vs-6 decimal gap. Virtual reserves are the pricing reserves (they include the initial virtual
|
|
18
|
+
// offset). Multiply by SOL/USD for a USD tick. Decode is < 1 ms and allocation-light.
|
|
19
|
+
export const PUMP_PROGRAM = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P';
|
|
20
|
+
const SOL_DECIMALS = 9;
|
|
21
|
+
const TOKEN_DECIMALS = 6;
|
|
22
|
+
/** Base discriminator (8) + five u64 (40) + complete (1). */
|
|
23
|
+
const CURVE_MIN_LEN = 49;
|
|
24
|
+
/** Decode a base64 bonding-curve account into reserves + derived spot price. Returns null on junk. */
|
|
25
|
+
export function decodeBondingCurve(base64) {
|
|
26
|
+
let buf;
|
|
27
|
+
try {
|
|
28
|
+
buf = Buffer.from(base64, 'base64');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (buf.length < CURVE_MIN_LEN)
|
|
34
|
+
return null;
|
|
35
|
+
const virtualTokenReserves = buf.readBigUInt64LE(8);
|
|
36
|
+
const virtualSolReserves = buf.readBigUInt64LE(16);
|
|
37
|
+
const realTokenReserves = buf.readBigUInt64LE(24);
|
|
38
|
+
const realSolReserves = buf.readBigUInt64LE(32);
|
|
39
|
+
const tokenTotalSupply = buf.readBigUInt64LE(40);
|
|
40
|
+
const complete = buf.readUInt8(48) === 1;
|
|
41
|
+
const vSol = Number(virtualSolReserves) / 10 ** SOL_DECIMALS;
|
|
42
|
+
const vToken = Number(virtualTokenReserves) / 10 ** TOKEN_DECIMALS;
|
|
43
|
+
const priceSol = vToken > 0 ? vSol / vToken : null;
|
|
44
|
+
return { virtualTokenReserves, virtualSolReserves, realTokenReserves, realSolReserves, tokenTotalSupply, complete, vSol, vToken, priceSol };
|
|
45
|
+
}
|
|
46
|
+
/** Full-dilution market cap in SOL from a decoded curve (totalSupply is 6-decimal base units). */
|
|
47
|
+
export function marketCapSol(curve) {
|
|
48
|
+
if (!curve || curve.priceSol == null)
|
|
49
|
+
return null;
|
|
50
|
+
const supply = Number(curve.tokenTotalSupply) / 10 ** TOKEN_DECIMALS;
|
|
51
|
+
return curve.priceSol * supply;
|
|
52
|
+
}
|
|
53
|
+
/** Price one token in USD from a decoded curve and a SOL/USD rate (null when either is missing). */
|
|
54
|
+
export function curvePriceUsd(curve, solUsd) {
|
|
55
|
+
if (!curve || curve.priceSol == null || solUsd == null || !Number.isFinite(solUsd))
|
|
56
|
+
return null;
|
|
57
|
+
return curve.priceSol * solUsd;
|
|
58
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// PumpPortal real-time data stream (SPEC §3.1 S7; inventory 2026-10-06 §1b). Keyless, read-only.
|
|
2
|
+
// wss://pumpportal.fun/api/data
|
|
3
|
+
// -> { method: 'subscribeNewToken' } creates: { signature, mint, traderPublicKey, txType: 'create',
|
|
4
|
+
// initialBuy, solAmount, bondingCurveKey, vTokensInBondingCurve, vSolInBondingCurve, marketCapSol,
|
|
5
|
+
// name, symbol, uri, is_mayhem_mode, pool }
|
|
6
|
+
// -> { method: 'subscribeMigration' } migrations (shape varies; we keep mint + signature + pool)
|
|
7
|
+
// One connection per client (their rule). Measured 2026-10-06: connect 116 ms, first message 139 ms,
|
|
8
|
+
// ~15 creates/min, ~3 migrations/min. The trade streams are paid and never subscribed here.
|
|
9
|
+
//
|
|
10
|
+
// The client reconnects with exponential backoff (1 s doubling to 30 s), reports every message / error
|
|
11
|
+
// to the health registry under `pumpportal`, can record the session into a FixtureStore stream, and the
|
|
12
|
+
// same event path replays a recording (tests, --replay) with the original timing or instantly.
|
|
13
|
+
import { systemClock } from "../clock.js";
|
|
14
|
+
import { numOrNull } from "../util.js";
|
|
15
|
+
export const PUMPPORTAL_WS = 'wss://pumpportal.fun/api/data';
|
|
16
|
+
/** pump.fun Global config (docs/pump-fun/docs-overview.md): the curve starts with these virtual/real reserves. */
|
|
17
|
+
export const PUMP_INITIAL_VIRTUAL_TOKENS = 1_073_000_000; // UI units (6 decimals)
|
|
18
|
+
export const PUMP_INITIAL_REAL_TOKENS = 793_100_000;
|
|
19
|
+
export const PUMP_INITIAL_VIRTUAL_SOL = 30;
|
|
20
|
+
/**
|
|
21
|
+
* Curve completion estimate from the virtual token reserves: real tokens left = vTokens − (virtual − real
|
|
22
|
+
* initial offset); complete when none are left. Returns 0..100, or null when the reserves are missing.
|
|
23
|
+
*/
|
|
24
|
+
export function bondingProgress(vTokens) {
|
|
25
|
+
if (vTokens === null || !Number.isFinite(vTokens))
|
|
26
|
+
return null;
|
|
27
|
+
const offset = PUMP_INITIAL_VIRTUAL_TOKENS - PUMP_INITIAL_REAL_TOKENS; // 279.9M never sold on the curve
|
|
28
|
+
const realLeft = vTokens - offset;
|
|
29
|
+
const pct = (1 - realLeft / PUMP_INITIAL_REAL_TOKENS) * 100;
|
|
30
|
+
return Math.max(0, Math.min(100, Math.round(pct * 10) / 10));
|
|
31
|
+
}
|
|
32
|
+
function launchpadFor(pool, mint) {
|
|
33
|
+
const p = (pool ?? '').toLowerCase();
|
|
34
|
+
if (p.includes('bonk'))
|
|
35
|
+
return 'bonk';
|
|
36
|
+
if (p.includes('pump') || mint.endsWith('pump'))
|
|
37
|
+
return 'pump.fun';
|
|
38
|
+
return 'unknown';
|
|
39
|
+
}
|
|
40
|
+
export function createFromRaw(raw, receivedAt) {
|
|
41
|
+
if (!raw.mint || typeof raw.mint !== 'string')
|
|
42
|
+
return null;
|
|
43
|
+
const vToken = numOrNull(raw.vTokensInBondingCurve);
|
|
44
|
+
return {
|
|
45
|
+
source: 'pumpportal',
|
|
46
|
+
fetchedAt: receivedAt,
|
|
47
|
+
mint: raw.mint,
|
|
48
|
+
creator: typeof raw.traderPublicKey === 'string' ? raw.traderPublicKey : '',
|
|
49
|
+
launchpad: launchpadFor(raw.pool, raw.mint),
|
|
50
|
+
name: typeof raw.name === 'string' ? raw.name : '',
|
|
51
|
+
symbol: typeof raw.symbol === 'string' ? raw.symbol : '',
|
|
52
|
+
uri: typeof raw.uri === 'string' && raw.uri ? raw.uri : null,
|
|
53
|
+
signature: typeof raw.signature === 'string' ? raw.signature : null,
|
|
54
|
+
initialBuyTokens: numOrNull(raw.initialBuy),
|
|
55
|
+
initialBuySol: numOrNull(raw.solAmount),
|
|
56
|
+
vSol: numOrNull(raw.vSolInBondingCurve),
|
|
57
|
+
vToken,
|
|
58
|
+
marketCapSol: numOrNull(raw.marketCapSol),
|
|
59
|
+
bondingCurve: typeof raw.bondingCurveKey === 'string' ? raw.bondingCurveKey : null,
|
|
60
|
+
pool: typeof raw.pool === 'string' ? raw.pool : null,
|
|
61
|
+
bondingPct: bondingProgress(vToken),
|
|
62
|
+
receivedAt,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Classify one text frame. Never throws: junk is `other`. */
|
|
66
|
+
export function parsePumpMessage(text, receivedAt) {
|
|
67
|
+
let j;
|
|
68
|
+
try {
|
|
69
|
+
j = JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { kind: 'other', receivedAt };
|
|
73
|
+
}
|
|
74
|
+
if (!j || typeof j !== 'object')
|
|
75
|
+
return { kind: 'other', receivedAt };
|
|
76
|
+
const o = j;
|
|
77
|
+
if (typeof o.message === 'string' && !o.mint)
|
|
78
|
+
return { kind: 'info', message: o.message, receivedAt };
|
|
79
|
+
if (o.txType === 'create' || (o.mint && (o.name !== undefined || o.vTokensInBondingCurve !== undefined))) {
|
|
80
|
+
const create = createFromRaw(o, receivedAt);
|
|
81
|
+
return create ? { kind: 'create', create, receivedAt } : { kind: 'other', receivedAt };
|
|
82
|
+
}
|
|
83
|
+
if (o.txType === 'migrate' || o.txType === 'migration' || /migrat/i.test(text.slice(0, 200))) {
|
|
84
|
+
return { kind: 'migrate', mint: typeof o.mint === 'string' ? o.mint : null, signature: typeof o.signature === 'string' ? o.signature : null, pool: typeof o.pool === 'string' ? o.pool : null, receivedAt };
|
|
85
|
+
}
|
|
86
|
+
return { kind: 'other', receivedAt };
|
|
87
|
+
}
|
|
88
|
+
export const PUMP_FREE_METHODS = ['subscribeNewToken', 'subscribeMigration'];
|
|
89
|
+
export class PumpPortalClient {
|
|
90
|
+
url;
|
|
91
|
+
make;
|
|
92
|
+
clock;
|
|
93
|
+
health;
|
|
94
|
+
opts;
|
|
95
|
+
ws = null;
|
|
96
|
+
status = { state: 'idle', attempts: 0, failures: 0, messages: 0, creates: 0, migrations: 0, bytes: 0, connectedAt: null, lastMessageAt: null, lastError: null, nextRetryMs: null };
|
|
97
|
+
stopped = false;
|
|
98
|
+
startedAt = 0;
|
|
99
|
+
/** Aborts a pending reconnect-backoff sleep so stop() never leaves a timer holding the loop (task-1311). */
|
|
100
|
+
retryAbort = null;
|
|
101
|
+
constructor(opts = {}) {
|
|
102
|
+
this.opts = opts;
|
|
103
|
+
this.url = opts.url ?? PUMPPORTAL_WS;
|
|
104
|
+
this.clock = opts.clock ?? systemClock;
|
|
105
|
+
this.health = opts.health;
|
|
106
|
+
const g = globalThis;
|
|
107
|
+
this.make = opts.ws ?? ((url) => {
|
|
108
|
+
if (!g.WebSocket)
|
|
109
|
+
throw new Error('WebSocket is not available in this runtime (Node 22+ required)');
|
|
110
|
+
return new g.WebSocket(url);
|
|
111
|
+
});
|
|
112
|
+
for (const m of opts.subscriptions ?? PUMP_FREE_METHODS) {
|
|
113
|
+
if (!PUMP_FREE_METHODS.includes(m))
|
|
114
|
+
throw new Error(`refusing paid PumpPortal method ${m}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
getStatus() {
|
|
118
|
+
return { ...this.status };
|
|
119
|
+
}
|
|
120
|
+
start() {
|
|
121
|
+
if (this.status.state !== 'idle' && this.status.state !== 'stopped')
|
|
122
|
+
return;
|
|
123
|
+
this.stopped = false;
|
|
124
|
+
this.startedAt = this.clock.now();
|
|
125
|
+
if (this.opts.record)
|
|
126
|
+
this.opts.record.startedAt = new Date(this.startedAt).toISOString();
|
|
127
|
+
void this.connect();
|
|
128
|
+
}
|
|
129
|
+
stop() {
|
|
130
|
+
this.stopped = true;
|
|
131
|
+
this.retryAbort?.abort(); // cancel a pending backoff sleep so the process can exit at once (task-1311)
|
|
132
|
+
this.set({ state: 'stopped', nextRetryMs: null });
|
|
133
|
+
const ws = this.ws;
|
|
134
|
+
this.ws = null;
|
|
135
|
+
try {
|
|
136
|
+
ws?.close(1000, 'dexterm stop');
|
|
137
|
+
}
|
|
138
|
+
catch { /* already closed */ }
|
|
139
|
+
}
|
|
140
|
+
set(patch) {
|
|
141
|
+
this.status = { ...this.status, ...patch };
|
|
142
|
+
this.opts.onStatus?.(this.getStatus());
|
|
143
|
+
}
|
|
144
|
+
async connect() {
|
|
145
|
+
if (this.stopped)
|
|
146
|
+
return;
|
|
147
|
+
this.set({ state: this.status.attempts === 0 ? 'connecting' : 'reconnecting', attempts: this.status.attempts + 1, nextRetryMs: null });
|
|
148
|
+
let ws;
|
|
149
|
+
try {
|
|
150
|
+
ws = this.make(this.url);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
this.fail(err instanceof Error ? err.message : String(err));
|
|
154
|
+
return this.retry();
|
|
155
|
+
}
|
|
156
|
+
this.ws = ws;
|
|
157
|
+
let opened = false;
|
|
158
|
+
ws.addEventListener('open', () => {
|
|
159
|
+
if (this.ws !== ws)
|
|
160
|
+
return;
|
|
161
|
+
opened = true;
|
|
162
|
+
this.set({ state: 'open', failures: 0, connectedAt: this.clock.now(), lastError: null });
|
|
163
|
+
for (const method of this.opts.subscriptions ?? PUMP_FREE_METHODS) {
|
|
164
|
+
const text = JSON.stringify({ method });
|
|
165
|
+
try {
|
|
166
|
+
ws.send(text);
|
|
167
|
+
}
|
|
168
|
+
catch { /* close handler will retry */ }
|
|
169
|
+
this.opts.record?.sent.push(text);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
ws.addEventListener('message', (ev) => {
|
|
173
|
+
if (this.ws !== ws)
|
|
174
|
+
return;
|
|
175
|
+
const text = typeof ev.data === 'string' ? ev.data : ev.data instanceof ArrayBuffer ? Buffer.from(ev.data).toString('utf8') : String(ev.data ?? '');
|
|
176
|
+
this.handle(text);
|
|
177
|
+
});
|
|
178
|
+
ws.addEventListener('error', (ev) => {
|
|
179
|
+
if (this.ws !== ws)
|
|
180
|
+
return;
|
|
181
|
+
const msg = ev.message ?? 'websocket error';
|
|
182
|
+
this.set({ lastError: msg });
|
|
183
|
+
});
|
|
184
|
+
ws.addEventListener('close', (ev) => {
|
|
185
|
+
if (this.ws !== ws)
|
|
186
|
+
return;
|
|
187
|
+
this.ws = null;
|
|
188
|
+
if (this.stopped)
|
|
189
|
+
return;
|
|
190
|
+
const reason = `closed ${ev.code ?? '?'}${ev.reason ? ` ${ev.reason}` : ''}${opened ? '' : ' before open'}`;
|
|
191
|
+
this.fail(reason);
|
|
192
|
+
void this.retry();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
fail(message) {
|
|
196
|
+
this.health?.record('pumpportal', { ok: false, error: message });
|
|
197
|
+
this.set({ failures: this.status.failures + 1, lastError: message });
|
|
198
|
+
}
|
|
199
|
+
async retry() {
|
|
200
|
+
if (this.stopped)
|
|
201
|
+
return;
|
|
202
|
+
const base = this.opts.baseBackoffMs ?? 1_000;
|
|
203
|
+
const max = this.opts.maxBackoffMs ?? 30_000;
|
|
204
|
+
const wait = Math.min(max, base * 2 ** Math.max(0, this.status.failures - 1));
|
|
205
|
+
this.set({ state: 'reconnecting', nextRetryMs: wait });
|
|
206
|
+
this.retryAbort = new AbortController();
|
|
207
|
+
await this.clock.sleep(wait, this.retryAbort.signal);
|
|
208
|
+
if (this.stopped)
|
|
209
|
+
return; // stop() fired during the backoff: do not reconnect
|
|
210
|
+
await this.connect();
|
|
211
|
+
}
|
|
212
|
+
/** Parse one frame, record it, report health, emit. Shared by live and replay. */
|
|
213
|
+
handle(text, receivedAt = this.clock.now()) {
|
|
214
|
+
const ev = parsePumpMessage(text, receivedAt);
|
|
215
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
216
|
+
this.opts.record?.messages.push({ tMs: Math.max(0, receivedAt - this.startedAt), text });
|
|
217
|
+
this.set({
|
|
218
|
+
messages: this.status.messages + 1,
|
|
219
|
+
bytes: this.status.bytes + bytes,
|
|
220
|
+
lastMessageAt: receivedAt,
|
|
221
|
+
creates: this.status.creates + (ev.kind === 'create' ? 1 : 0),
|
|
222
|
+
migrations: this.status.migrations + (ev.kind === 'migrate' ? 1 : 0),
|
|
223
|
+
});
|
|
224
|
+
if (ev.kind === 'create' || ev.kind === 'migrate')
|
|
225
|
+
this.health?.record('pumpportal', { ok: true, latencyMs: 0 });
|
|
226
|
+
this.opts.onEvent?.(ev);
|
|
227
|
+
return ev;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Replay a recorded session through the same handler. `speed` 1 keeps the recorded gaps (on the
|
|
231
|
+
* client's clock), 0 delivers everything at once. Resolves when the last message was delivered.
|
|
232
|
+
*/
|
|
233
|
+
async replay(rec, speed = 0) {
|
|
234
|
+
this.startedAt = this.clock.now();
|
|
235
|
+
this.set({ state: 'open', connectedAt: this.startedAt, attempts: 1 });
|
|
236
|
+
let last = 0;
|
|
237
|
+
for (const m of rec.messages) {
|
|
238
|
+
if (this.stopped)
|
|
239
|
+
break;
|
|
240
|
+
if (speed > 0 && m.tMs > last)
|
|
241
|
+
await this.clock.sleep((m.tMs - last) / speed);
|
|
242
|
+
last = m.tMs;
|
|
243
|
+
this.handle(m.text, this.clock.now());
|
|
244
|
+
}
|
|
245
|
+
return rec.messages.length;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// RugCheck adapter (SPEC §3.1 S5). Keyless, read-only, summary endpoint only
|
|
2
|
+
// (the full report may need a JWT; we never send one).
|
|
3
|
+
// GET https://api.rugcheck.xyz/v1/tokens/{mint}/report/summary
|
|
4
|
+
// -> { tokenProgram, tokenType, risks: [{ name, value, description, score, level }],
|
|
5
|
+
// score, score_normalised, lpLockedPct }
|
|
6
|
+
// Limits are undocumented (community: ~20 full reports/day/IP); the bucket is a
|
|
7
|
+
// conservative guess and results are cached 10 min (TTL.rugcheck).
|
|
8
|
+
// `name`/`description`/`value` are third-party text: firewall before any model sees them (CL-06).
|
|
9
|
+
import { num, numOrNull } from "../util.js";
|
|
10
|
+
export const RUGCHECK_BASE = 'https://api.rugcheck.xyz/v1';
|
|
11
|
+
const META = { source: 'rugcheck', bucket: 'rugcheck' };
|
|
12
|
+
const LEVEL_RANK = { danger: 0, warn: 1, info: 2, unknown: 3 };
|
|
13
|
+
function level(v) {
|
|
14
|
+
return v === 'danger' || v === 'warn' || v === 'info' ? v : 'unknown';
|
|
15
|
+
}
|
|
16
|
+
const str = (v) => (typeof v === 'string' && v !== '' ? v : null);
|
|
17
|
+
export function normalizeRugSummary(mint, raw, fetchedAt) {
|
|
18
|
+
const risks = (raw.risks ?? [])
|
|
19
|
+
.filter((r) => typeof r?.name === 'string' && r.name !== '')
|
|
20
|
+
.map((r) => ({ name: r.name, level: level(r.level), score: num(r.score), value: str(r.value), description: str(r.description) }))
|
|
21
|
+
.sort((a, b) => LEVEL_RANK[a.level] - LEVEL_RANK[b.level] || b.score - a.score);
|
|
22
|
+
return {
|
|
23
|
+
mint,
|
|
24
|
+
score: numOrNull(raw.score),
|
|
25
|
+
scoreNormalised: numOrNull(raw.score_normalised),
|
|
26
|
+
lpLockedPct: numOrNull(raw.lpLockedPct),
|
|
27
|
+
tokenProgram: str(raw.tokenProgram),
|
|
28
|
+
tokenType: str(raw.tokenType),
|
|
29
|
+
risks,
|
|
30
|
+
source: 'rugcheck',
|
|
31
|
+
fetchedAt,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** The Audit fields RugCheck owns (SPEC §3.3/§3.6): LP locked, risk names, score. */
|
|
35
|
+
export function auditPatchFromRug(s) {
|
|
36
|
+
return { lpLockedPct: s.lpLockedPct, rugRisks: s.risks.map((r) => r.name), rugScore: s.scoreNormalised ?? s.score };
|
|
37
|
+
}
|
|
38
|
+
export function normalizeNewToken(raw, fetchedAt) {
|
|
39
|
+
if (!raw || typeof raw.mint !== 'string' || !raw.mint)
|
|
40
|
+
return null;
|
|
41
|
+
const auth = (v) => (v === undefined ? null : typeof v === 'string' && v !== '' ? true : false);
|
|
42
|
+
const created = typeof raw.createAt === 'string' ? Date.parse(raw.createAt) : NaN;
|
|
43
|
+
return {
|
|
44
|
+
mint: raw.mint,
|
|
45
|
+
symbol: typeof raw.symbol === 'string' ? raw.symbol : '',
|
|
46
|
+
creator: str(raw.creator),
|
|
47
|
+
mintAuthority: auth(raw.mintAuthority),
|
|
48
|
+
freezeAuthority: auth(raw.freezeAuthority),
|
|
49
|
+
program: str(raw.program),
|
|
50
|
+
decimals: numOrNull(raw.decimals),
|
|
51
|
+
createdAt: Number.isFinite(created) ? created : null,
|
|
52
|
+
source: 'rugcheck',
|
|
53
|
+
fetchedAt,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export class RugCheck {
|
|
57
|
+
http;
|
|
58
|
+
constructor(http) {
|
|
59
|
+
this.http = http;
|
|
60
|
+
}
|
|
61
|
+
async summary(mint) {
|
|
62
|
+
const r = await this.http.getJson(`${RUGCHECK_BASE}/tokens/${encodeURIComponent(mint)}/report/summary`, META);
|
|
63
|
+
return normalizeRugSummary(mint, r.data ?? {}, r.fetchedAt);
|
|
64
|
+
}
|
|
65
|
+
/** Newest mints RugCheck has indexed (≈10 rows, 103 ms measured): creator and authority cross-check for new pairs. */
|
|
66
|
+
async newTokens() {
|
|
67
|
+
const r = await this.http.getJson(`${RUGCHECK_BASE}/stats/new_tokens`, META);
|
|
68
|
+
const list = Array.isArray(r.data) ? r.data : Array.isArray(r.data?.tokens) ? r.data.tokens : [];
|
|
69
|
+
return list.map((t) => normalizeNewToken(t, r.fetchedAt)).filter((t) => t !== null);
|
|
70
|
+
}
|
|
71
|
+
}
|