dexterm-cli 0.2.0 → 0.2.2
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/dist/packages/agents/src/spectator.js +28 -2
- package/dist/packages/agents/src/tiers.js +3 -0
- package/dist/packages/agents/src/tools.js +3 -0
- package/dist/packages/autotrade/src/backtest.js +6 -2
- package/dist/packages/autotrade/src/chat/connector.js +5 -4
- package/dist/packages/autotrade/src/chat/control.js +3 -0
- package/dist/packages/autotrade/src/chat/hub.js +37 -2
- package/dist/packages/autotrade/src/chat/index.js +3 -2
- package/dist/packages/autotrade/src/chat/readonly.js +530 -0
- package/dist/packages/autotrade/src/chat/service.js +12 -1
- package/dist/packages/autotrade/src/engine.js +75 -29
- package/dist/packages/autotrade/src/fleet.js +2 -2
- package/dist/packages/autotrade/src/index.js +4 -0
- package/dist/packages/autotrade/src/rugproof-probe.js +415 -0
- package/dist/packages/autotrade/src/rugproof.js +231 -0
- package/dist/packages/autotrade/src/runner.js +203 -30
- package/dist/packages/autotrade/src/service-install.js +6 -4
- package/dist/packages/autotrade/src/sessions.js +16 -7
- package/dist/packages/autotrade/src/store.js +1 -1
- package/dist/packages/autotrade/src/strategy-stats.js +344 -0
- package/dist/packages/autotrade/src/strategy.js +29 -1
- package/dist/packages/autotrade/src/tiers.js +194 -0
- package/dist/packages/classifier/src/calibrate.js +138 -0
- package/dist/packages/classifier/src/classify.js +107 -0
- package/dist/packages/classifier/src/config.js +298 -0
- package/dist/packages/classifier/src/dossier.js +95 -0
- package/dist/packages/classifier/src/hardware.js +63 -0
- package/dist/packages/classifier/src/index.js +19 -0
- package/dist/packages/classifier/src/models.js +111 -0
- package/dist/packages/classifier/src/providers/heuristic.js +77 -0
- package/dist/packages/classifier/src/providers/http.js +63 -0
- package/dist/packages/classifier/src/providers/jev-open.js +86 -0
- package/dist/packages/classifier/src/providers/llm.js +89 -0
- package/dist/packages/classifier/src/providers/systemone-http.js +95 -0
- package/dist/packages/classifier/src/questions.js +97 -0
- package/dist/packages/classifier/src/registry.js +42 -0
- package/dist/packages/classifier/src/snapshot.js +31 -0
- package/dist/packages/classifier/src/systemone.js +88 -0
- package/dist/packages/classifier/src/types.js +18 -0
- package/dist/packages/classifier/src/win-acl.js +71 -0
- package/dist/packages/cli/assets/dash/dash.css +139 -0
- package/dist/packages/cli/assets/dash/dash.js +200 -0
- package/dist/packages/cli/assets/dash/icon.svg +1 -0
- package/dist/packages/cli/assets/dash/index.html +36 -0
- package/dist/packages/cli/src/app-help-gen.js +13 -1
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +12 -3
- package/dist/packages/cli/src/autotrade-cmd/run.js +14 -2
- package/dist/packages/cli/src/autotrade-cmd/service.js +3 -1
- package/dist/packages/cli/src/autotrade-cmd/signal.js +2 -2
- package/dist/packages/cli/src/autotrade-cmd/strategies.js +1 -1
- package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +2 -0
- package/dist/packages/cli/src/classifier-cmd.js +361 -0
- package/dist/packages/cli/src/classifier-live.js +81 -0
- package/dist/packages/cli/src/dash/data.js +217 -0
- package/dist/packages/cli/src/dash/server.js +92 -0
- package/dist/packages/cli/src/dash-cmd.js +113 -0
- package/dist/packages/cli/src/doctor.js +34 -0
- package/dist/packages/cli/src/live/screens/token-live.js +4 -0
- package/dist/packages/cli/src/live/signals.js +8 -0
- package/dist/packages/cli/src/main.js +98 -6
- package/dist/packages/cli/src/mcp-follow.js +21 -1
- package/dist/packages/cli/src/narratives-cmd.js +147 -0
- package/dist/packages/cli/src/narratives.js +114 -0
- package/dist/packages/cli/src/overlay/model.js +147 -0
- package/dist/packages/cli/src/overlay/page.js +146 -0
- package/dist/packages/cli/src/overlay/pnl.js +75 -0
- package/dist/packages/cli/src/overlay/server.js +170 -0
- package/dist/packages/cli/src/overlay-cmd.js +367 -0
- package/dist/packages/cli/src/record-cmd.js +82 -11
- package/dist/packages/cli/src/rules-cmd.js +2 -1
- package/dist/packages/cli/src/rules.js +24 -4
- package/dist/packages/cli/src/screens/narrative.js +154 -2
- package/dist/packages/cli/src/skill-eval-build.js +7 -2
- package/dist/packages/cli/src/state.js +1 -0
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/custom/src/rules.js +5 -1
- package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/classification-models.md +41 -0
- package/dist/packages/mcp/assets/kb/app-help/narratives-tracker.md +45 -0
- package/dist/packages/mcp/assets/kb/app-help/strategy-dashboard.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/streamer-overlay.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-confidence-tiers-experimental-unproven-paper-only.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-rug-proof-entries-experimental.md +52 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-strategy-dashboard-dexterm-dash.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-copy-and-paste-without-surprises.md +0 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-dexterm-says-command-not-found.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-reopen-dexterm-later.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-terminal-shell-prompt-command-output-and-path.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-hosted-jev-with-your-own-key.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-how-they-measured-on-dexterm-s-own-history.md +52 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-set-up-kev-on-your-own-machine.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-settings.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-the-choices.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-where-it-plugs-in.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-which-model-for-which-machine.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-invocations.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +2 -11
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-paste-a-coin-on-the-web.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-how-coins-are-grouped.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-limits.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-what-is-measured.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-what-testing-showed.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-where-to-find-it.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-before-you-start.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-limits.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-options.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-security-model.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-set-it-up-in-obs-step-by-step.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-the-pnl-scene-paper-and-live.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-the-three-scenes.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-what-is-redacted.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-bubble-map.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-streaming-it.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-read-only-data-check-top-and-signals-experimental.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +2 -2
- package/dist/packages/mcp/assets/kb/entries/narratives-tracker.md +45 -0
- package/dist/packages/mcp/src/server.js +106 -1
- package/dist/packages/mcp/src/spectator-sink.js +27 -0
- package/dist/packages/narratives/src/cluster.js +193 -0
- package/dist/packages/narratives/src/embed.js +184 -0
- package/dist/packages/narratives/src/export.js +60 -0
- package/dist/packages/narratives/src/index.js +10 -0
- package/dist/packages/narratives/src/label.js +48 -0
- package/dist/packages/narratives/src/normalize.js +179 -0
- package/dist/packages/narratives/src/recorder.js +93 -0
- package/dist/packages/narratives/src/tracker.js +328 -0
- package/dist/packages/packs/src/skill-care.js +10 -2
- package/dist/packages/recorder/src/exit-probes.js +215 -0
- package/dist/packages/recorder/src/index.js +1 -0
- package/dist/packages/recorder/src/recorder.js +11 -1
- package/dist/packages/recorder/src/retention.js +4 -0
- package/dist/packages/recorder/src/schema.js +20 -1
- package/dist/packages/tui/src/keymap.js +5 -0
- package/package.json +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +0 -39
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// dexterm autotrade — rug-proofing (task-1748): who can pull the pool, and a fast exit when it starts to go. PAPER ONLY.
|
|
2
|
+
//
|
|
3
|
+
// The control's two 2026-10-26 losses ($NOTHING, $AMERICA) and the three on 10-27 were creator LP pulls on hand-made
|
|
4
|
+
// PumpSwap pools: WSOL as the pool's BASE mint, launchpad unknown, the mint not ending in `pump`, the creator holding the
|
|
5
|
+
// LP tokens (FINDINGS-2026-10-27 §1-2). A pull is ONE transaction with no build-up: every post-entry signal (the pool's
|
|
6
|
+
// SOL vault, a Jupiter quote, DexScreener liquidity) fires only after it. So the PRIMARY defense is pre-entry and
|
|
7
|
+
// structural; the drain guard below only shortens the time a position sits in a pool that is being emptied and makes
|
|
8
|
+
// the book honest. A same-block creator pull cannot be front-run by anyone reading the chain.
|
|
9
|
+
//
|
|
10
|
+
// Pre-entry gates, cheapest first, fail closed (a gate that cannot be evaluated refuses the coin with `-unknown`):
|
|
11
|
+
// pool-age pool (pair) age >= minPoolAgeMin. Token age is a free lower bound first (a token younger
|
|
12
|
+
// than the minimum has no older pool); the pair's own creation time decides. tick, pair
|
|
13
|
+
// holders top-10 / dev share under the strategy's safety values (unknown = refuse) tick
|
|
14
|
+
// liquidity-recheck two liquidity reads >= afterSec apart: both >= floorX x the exit floor, the newer
|
|
15
|
+
// not more than maxDropPct under the older history
|
|
16
|
+
// pool-structure the pool is a pump.fun migration (PumpSwap, token = base, WSOL = quote) or a pool
|
|
17
|
+
// whose LP is verifiably burned / locked; WSOL as the BASE mint = hand-made: refuse pair
|
|
18
|
+
// lp-locked PumpSwap pool account -> lp mint: supply 0 (burned) passes; supply > 0 with the
|
|
19
|
+
// largest holder the pool creator (or any wallet over maxHolderLpPct) refuses RPC
|
|
20
|
+
// withdraw-history no `Withdraw` instruction among the pool's newest transactions (bounded scan) RPC
|
|
21
|
+
// sell-route a Jupiter token -> SOL quote at routeX x the position size returns a route Jupiter
|
|
22
|
+
// Every result has a named rule (`rugproof:<gate>[-why]`) that lands in the ledger; time gates (age, recheck) say
|
|
23
|
+
// when to look again instead of skipping the coin for good.
|
|
24
|
+
//
|
|
25
|
+
// Post-entry (drainVerdict): exit the whole position at once on liquidity / reserve down > liqDropPct from the entry
|
|
26
|
+
// read, a reserve near 0, or ANY Jupiter quote error (NO_ROUTES_FOUND, CANNOT_COMPUTE_…). A rate limit or a network
|
|
27
|
+
// failure is not a quote error (no exit on it; it is counted). Young or partly-verified pools get a smaller size
|
|
28
|
+
// (youngPoolSizeCapUsd).
|
|
29
|
+
//
|
|
30
|
+
// Pure: no I/O. The probes (DexScreener pair, PumpSwap pool account, LP mint, signatures, Jupiter) are in rugproof-probe.ts.
|
|
31
|
+
export const WSOL_MINT = 'So11111111111111111111111111111111111111112';
|
|
32
|
+
export const PUMPSWAP_PROGRAM = 'pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA';
|
|
33
|
+
export const RUGPROOF_DEFAULTS = {
|
|
34
|
+
minPoolAgeMin: 10,
|
|
35
|
+
requireSafePool: true,
|
|
36
|
+
maxHolderLpPct: 10,
|
|
37
|
+
maxWithdraws: 0,
|
|
38
|
+
withdrawScanTx: 25,
|
|
39
|
+
liquidityRecheck: { afterSec: 60, floorX: 2, maxDropPct: 15 },
|
|
40
|
+
routeX: 2,
|
|
41
|
+
youngPool: { underMin: 30, maxSizePct: 50 },
|
|
42
|
+
drainGuard: { pollSec: 10, liqDropPct: 25, exitOnNoRoute: true, minReserveSol: 0.5 },
|
|
43
|
+
};
|
|
44
|
+
export const GATE_ORDER = ['pool-age', 'holders', 'liquidity-recheck', 'pool-structure', 'lp-locked', 'withdraw-history', 'sell-route'];
|
|
45
|
+
const pass = (gate, detail, partial = false) => ({ gate, pass: true, rule: `rugproof:${gate}`, detail, ...(partial ? { partial: true } : {}) });
|
|
46
|
+
const fail = (gate, why, detail, retryAfterMs) => ({ gate, pass: false, rule: `rugproof:${gate}${why ? `-${why}` : ''}`, detail, ...(retryAfterMs !== undefined ? { retryAfterMs: Math.max(1000, Math.round(retryAfterMs)) } : {}) });
|
|
47
|
+
const m = (ms) => `${(ms / 60e3).toFixed(1)} min`;
|
|
48
|
+
const usd = (x) => `$${Math.round(x).toLocaleString('en-US')}`;
|
|
49
|
+
/** A pump.fun mint (vanity suffix) or Jupiter's launchpad tag says pump.fun. */
|
|
50
|
+
export const isPumpMint = (mint, launchpad) => mint.endsWith('pump') || launchpad === 'pump' || launchpad === 'pump.fun';
|
|
51
|
+
// ───────────────────────────── free gates (tick + history) ─────────────────────────────
|
|
52
|
+
/** Token age is a lower bound on pool age; the pair's creation time (when known) decides. */
|
|
53
|
+
export function gatePoolAge(r, k, pairCreatedAt, now = k.t) {
|
|
54
|
+
const min = r.minPoolAgeMin * 60e3;
|
|
55
|
+
if (k.createdAt !== null && now - k.createdAt < min)
|
|
56
|
+
return fail('pool-age', null, `token age ${m(now - k.createdAt)} < ${r.minPoolAgeMin} min (no older pool can exist)`, min - (now - k.createdAt));
|
|
57
|
+
if (pairCreatedAt === undefined)
|
|
58
|
+
return pass('pool-age', 'token age ok; pool age not read yet', true);
|
|
59
|
+
if (pairCreatedAt === null)
|
|
60
|
+
return fail('pool-age', 'unknown', 'the pool has no creation time');
|
|
61
|
+
const age = now - pairCreatedAt;
|
|
62
|
+
if (age < min)
|
|
63
|
+
return fail('pool-age', null, `pool age ${m(age)} < ${r.minPoolAgeMin} min`, min - age);
|
|
64
|
+
return pass('pool-age', `pool age ${m(age)}`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The strategy's own holder values (entry.safety, the GUARDRAILS numbers), with its own unknown rules: an unknown top-10
|
|
68
|
+
* share refuses unless unknownHolders is allow; an unknown dev share passes, as the engine's screen has always done (the
|
|
69
|
+
* holder sources rarely report it: refusing it would refuse nearly every coin), and is marked partial.
|
|
70
|
+
*/
|
|
71
|
+
export function gateHolders(k, maxTop10Pct, maxDevPct, unknownHolders = 'reject') {
|
|
72
|
+
let partial = false;
|
|
73
|
+
if (maxTop10Pct !== null) {
|
|
74
|
+
if (k.top10 === null) {
|
|
75
|
+
if (unknownHolders === 'reject')
|
|
76
|
+
return fail('holders', 'unknown', 'top-10 share unknown');
|
|
77
|
+
partial = true;
|
|
78
|
+
}
|
|
79
|
+
else if (k.top10 > maxTop10Pct)
|
|
80
|
+
return fail('holders', 'top10', `top 10 hold ${k.top10.toFixed(1)}% > ${maxTop10Pct}%`);
|
|
81
|
+
}
|
|
82
|
+
if (maxDevPct !== null && k.dev !== null && k.dev > maxDevPct)
|
|
83
|
+
return fail('holders', 'dev', `dev holds ${k.dev.toFixed(1)}% > ${maxDevPct}%`);
|
|
84
|
+
return pass('holders', `top10 ${k.top10 ?? '?'}%, dev ${k.dev ?? 'unknown'}%`, partial);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Two liquidity reads at least afterSec apart (the newest and the newest one at or before now - afterSec, from the
|
|
88
|
+
* coin's own tick history): both >= floorX x the exit floor, the newer not more than maxDropPct under the older.
|
|
89
|
+
*/
|
|
90
|
+
export function gateLiquidityRecheck(r, history, k, exitFloorUsd) {
|
|
91
|
+
const rc = r.liquidityRecheck;
|
|
92
|
+
if (!rc)
|
|
93
|
+
return pass('liquidity-recheck', 'not required');
|
|
94
|
+
const floor = rc.floorX * exitFloorUsd;
|
|
95
|
+
const liqNow = k.liq;
|
|
96
|
+
if (liqNow === null)
|
|
97
|
+
return fail('liquidity-recheck', 'unknown', 'no liquidity read');
|
|
98
|
+
if (liqNow < floor)
|
|
99
|
+
return fail('liquidity-recheck', 'floor', `liquidity ${usd(liqNow)} < ${rc.floorX}x the ${usd(exitFloorUsd)} exit floor`);
|
|
100
|
+
const cutoff = k.t - rc.afterSec * 1000;
|
|
101
|
+
let older = null;
|
|
102
|
+
for (const h of history)
|
|
103
|
+
if (h.mint === k.mint && h.t <= cutoff && h.liq !== null && (!older || h.t > older.t))
|
|
104
|
+
older = h;
|
|
105
|
+
if (!older) {
|
|
106
|
+
const first = history.find((h) => h.mint === k.mint && h.liq !== null);
|
|
107
|
+
const wait = first ? first.t + rc.afterSec * 1000 - k.t : rc.afterSec * 1000;
|
|
108
|
+
return fail('liquidity-recheck', 'pending', `no read ${rc.afterSec} s before this one yet`, wait > 0 ? wait : rc.afterSec * 1000);
|
|
109
|
+
}
|
|
110
|
+
if (older.liq < floor)
|
|
111
|
+
return fail('liquidity-recheck', 'floor', `liquidity ${usd(older.liq)} ${Math.round((k.t - older.t) / 1000)} s ago < ${rc.floorX}x the exit floor`);
|
|
112
|
+
const drop = (older.liq - liqNow) / older.liq * 100;
|
|
113
|
+
if (drop > rc.maxDropPct)
|
|
114
|
+
return fail('liquidity-recheck', 'falling', `liquidity ${usd(older.liq)} -> ${usd(liqNow)} in ${Math.round((k.t - older.t) / 1000)} s (-${drop.toFixed(1)}% > ${rc.maxDropPct}%)`);
|
|
115
|
+
return pass('liquidity-recheck', `${usd(older.liq)} -> ${usd(liqNow)} over ${Math.round((k.t - older.t) / 1000)} s`);
|
|
116
|
+
}
|
|
117
|
+
/** Owners that hold LP tokens without being able to withdraw them (burn / incinerator). Locks are not trusted by name. */
|
|
118
|
+
export const BURN_OWNERS = new Set(['1nc1nerator11111111111111111111111111111111', '11111111111111111111111111111111']);
|
|
119
|
+
export function gatePoolStructure(r, mint, launchpad, pair) {
|
|
120
|
+
if (!r.requireSafePool)
|
|
121
|
+
return pass('pool-structure', 'not required');
|
|
122
|
+
if (!pair)
|
|
123
|
+
return fail('pool-structure', 'unknown', 'no pool listed for the coin');
|
|
124
|
+
if (pair.baseMint === WSOL_MINT)
|
|
125
|
+
return fail('pool-structure', 'wsol-base', `WSOL is the pool's base mint (${pair.dex} ${pair.address.slice(0, 8)}…): a hand-made pool, not a pump.fun migration`);
|
|
126
|
+
if (pair.baseMint !== mint)
|
|
127
|
+
return fail('pool-structure', 'not-base', `the coin is not the pool's base mint (${pair.dex})`);
|
|
128
|
+
if (pair.quoteMint !== WSOL_MINT)
|
|
129
|
+
return fail('pool-structure', 'quote', `the pool's quote is not WSOL (${pair.quoteMint.slice(0, 8)}…)`);
|
|
130
|
+
const dex = pair.dex.toLowerCase();
|
|
131
|
+
if (dex === 'pumpswap') {
|
|
132
|
+
return isPumpMint(mint, launchpad)
|
|
133
|
+
? pass('pool-structure', 'pump.fun migration shape (PumpSwap, token = base, WSOL = quote)')
|
|
134
|
+
: pass('pool-structure', `PumpSwap, token = base, WSOL = quote, launchpad ${launchpad ?? 'unknown'}: LP must be verified`, true);
|
|
135
|
+
}
|
|
136
|
+
// Other venues (Raydium, Meteora, …): the LP lock cannot be verified here, so it is refused (fail closed).
|
|
137
|
+
return fail('pool-structure', 'unverifiable', `${pair.dex} pool: its LP lock cannot be verified by dexterm (only PumpSwap pools are read)`);
|
|
138
|
+
}
|
|
139
|
+
export function gateLpLocked(r, pair, lp, mint) {
|
|
140
|
+
if (!r.requireSafePool)
|
|
141
|
+
return pass('lp-locked', 'not required');
|
|
142
|
+
if (lp === 'error' || lp === null)
|
|
143
|
+
return fail('lp-locked', 'unknown', `the PumpSwap pool account ${pair.address.slice(0, 8)}… could not be read`);
|
|
144
|
+
if (lp.baseMint === WSOL_MINT)
|
|
145
|
+
return fail('lp-locked', 'wsol-base', 'the pool account says WSOL is the base mint (hand-made pool)');
|
|
146
|
+
if (lp.baseMint !== mint || lp.quoteMint !== WSOL_MINT)
|
|
147
|
+
return fail('lp-locked', 'mismatch', 'the pool account does not match the listed pair');
|
|
148
|
+
if (lp.lpSupply === 0n)
|
|
149
|
+
return pass('lp-locked', 'LP supply 0: every LP token is burned, nobody can withdraw');
|
|
150
|
+
const live = lp.holders.filter((h) => !BURN_OWNERS.has(h.owner) && h.amount > 0n);
|
|
151
|
+
const share = (a) => Number((a * 1000000n) / lp.lpSupply) / 10_000;
|
|
152
|
+
const byCreator = live.find((h) => h.owner === lp.creator);
|
|
153
|
+
if (byCreator)
|
|
154
|
+
return fail('lp-locked', 'creator', `the pool creator ${lp.creator.slice(0, 8)}… holds ${share(byCreator.amount).toFixed(2)}% of the LP: it can withdraw at any time`);
|
|
155
|
+
const top = live[0];
|
|
156
|
+
if (top && share(top.amount) > r.maxHolderLpPct)
|
|
157
|
+
return fail('lp-locked', 'holder', `one wallet ${top.owner.slice(0, 8)}… holds ${share(top.amount).toFixed(2)}% of the LP (> ${r.maxHolderLpPct}%)`);
|
|
158
|
+
if (!lp.holders.length)
|
|
159
|
+
return fail('lp-locked', 'unknown', 'LP supply > 0 but the holder list could not be read');
|
|
160
|
+
return pass('lp-locked', `LP supply > 0, largest live holder ${top ? share(top.amount).toFixed(2) : 0}%`, true);
|
|
161
|
+
}
|
|
162
|
+
export function gateWithdrawHistory(r, scan, lp) {
|
|
163
|
+
if (lp && lp.lpSupply === 0n && r.maxWithdraws === 0 && scan === null)
|
|
164
|
+
return pass('withdraw-history', 'LP burned: a withdraw is impossible');
|
|
165
|
+
if (scan === 'error' || scan === null)
|
|
166
|
+
return fail('withdraw-history', 'unknown', 'the pool\'s transactions could not be read');
|
|
167
|
+
if (scan.byCreator > 0)
|
|
168
|
+
return fail('withdraw-history', 'creator', `${scan.byCreator} creator Withdraw among the pool's newest ${scan.scanned} transactions`);
|
|
169
|
+
if (scan.withdraws > r.maxWithdraws)
|
|
170
|
+
return fail('withdraw-history', null, `${scan.withdraws} Withdraw among the pool's newest ${scan.scanned} transactions (max ${r.maxWithdraws})`);
|
|
171
|
+
return pass('withdraw-history', `no Withdraw among the newest ${scan.scanned} transactions`, scan.scanned < Math.min(5, r.withdrawScanTx));
|
|
172
|
+
}
|
|
173
|
+
export function gateSellRoute(r, route, sizeUsd) {
|
|
174
|
+
if (r.routeX === null)
|
|
175
|
+
return pass('sell-route', 'not required');
|
|
176
|
+
if (!route)
|
|
177
|
+
return fail('sell-route', 'unknown', 'no sell quote');
|
|
178
|
+
if (!route.ok)
|
|
179
|
+
return fail('sell-route', route.kind === 'no-route' ? 'none' : 'unknown', `Jupiter token -> SOL at ${r.routeX}x the ${usd(sizeUsd)} size: ${route.error}`);
|
|
180
|
+
return pass('sell-route', `Jupiter routes ${r.routeX}x the size (impact ${route.impactPct ?? '?'}%)`);
|
|
181
|
+
}
|
|
182
|
+
export function verdictOf(r, results, poolAgeMs) {
|
|
183
|
+
const refused = results.find((x) => !x.pass) ?? null;
|
|
184
|
+
const partial = results.some((x) => x.partial);
|
|
185
|
+
const young = r.youngPool && (poolAgeMs === null || poolAgeMs < r.youngPool.underMin * 60e3);
|
|
186
|
+
return { pass: !refused, results, refused, sizeCapPct: !refused && r.youngPool && (partial || young) ? r.youngPool.maxSizePct : null };
|
|
187
|
+
}
|
|
188
|
+
/** Exit the whole position at once on a reserve near 0, a reserve / liquidity drop > liqDropPct, or a quote error. */
|
|
189
|
+
export function drainVerdict(g, base, read) {
|
|
190
|
+
const ratio = base.reserveSol && read.reserveSol !== null ? read.reserveSol / base.reserveSol : null;
|
|
191
|
+
const estLiq = ratio !== null && base.liqUsd !== null ? base.liqUsd * ratio : read.liqUsd;
|
|
192
|
+
if (read.reserveSol !== null && read.reserveSol < g.minReserveSol)
|
|
193
|
+
return { exit: true, reason: 'drain-reserve', detail: `pool SOL reserve ${read.reserveSol.toFixed(3)} < ${g.minReserveSol} SOL`, liqUsd: estLiq ?? 0 };
|
|
194
|
+
if (ratio !== null && (1 - ratio) * 100 > g.liqDropPct)
|
|
195
|
+
return { exit: true, reason: 'drain-liquidity', detail: `pool SOL reserve ${base.reserveSol.toFixed(2)} -> ${read.reserveSol.toFixed(2)} (-${((1 - ratio) * 100).toFixed(1)}% > ${g.liqDropPct}%)`, liqUsd: estLiq };
|
|
196
|
+
if (base.liqUsd && read.liqUsd !== null && (1 - read.liqUsd / base.liqUsd) * 100 > g.liqDropPct)
|
|
197
|
+
return { exit: true, reason: 'drain-liquidity', detail: `liquidity ${usd(base.liqUsd)} -> ${usd(read.liqUsd)} (-${((1 - read.liqUsd / base.liqUsd) * 100).toFixed(1)}% > ${g.liqDropPct}% since entry)`, liqUsd: read.liqUsd };
|
|
198
|
+
if (g.exitOnNoRoute && read.route && !read.route.ok && read.route.kind === 'no-route')
|
|
199
|
+
return { exit: true, reason: 'drain-no-route', detail: `Jupiter has no sell route: ${read.route.error}`, liqUsd: estLiq ?? 0 };
|
|
200
|
+
if (read.route && !read.route.ok)
|
|
201
|
+
return { exit: false, note: `quote ${read.route.kind}: ${read.route.error}` };
|
|
202
|
+
return { exit: false };
|
|
203
|
+
}
|
|
204
|
+
/** The size a young or partly-verified pool may take: maxSizePct of what the strategy picked. */
|
|
205
|
+
export function youngPoolSizeCapUsd(v, usdPicked) {
|
|
206
|
+
return v.sizeCapPct === null ? null : (usdPicked * v.sizeCapPct) / 100;
|
|
207
|
+
}
|
|
208
|
+
// ───────────────────────────── offline (replays, backtests) ─────────────────────────────
|
|
209
|
+
/**
|
|
210
|
+
* The gates a recorded tape can evaluate: pool age (the tape's createdAt: the token's, else the pair's), holders, the
|
|
211
|
+
* second liquidity read (the coin's own recorded rows; the recorder's ~2-min cadence makes afterSec at least one row
|
|
212
|
+
* gap), and the pool-structure PROXY: a pump.fun mint (`…pump` / launchpad pump) passes, anything else is refused as
|
|
213
|
+
* `rugproof:pool-structure-offline` (the tape has no pool account, LP mint or transactions). Used by backtest() for a
|
|
214
|
+
* rug-proof strategy and by the control's replay.
|
|
215
|
+
*/
|
|
216
|
+
export function rugproofTapeVet(r) {
|
|
217
|
+
return async (k, ctx) => {
|
|
218
|
+
const res = [
|
|
219
|
+
gatePoolAge(r, k, k.createdAt, k.t),
|
|
220
|
+
gateHolders(k, ctx?.maxTop10Pct ?? null, ctx?.maxDevPct ?? null, ctx?.unknownHolders),
|
|
221
|
+
// the structural proxy is free on a tape: it goes before the second read (a refusal for good beats a wait)
|
|
222
|
+
!r.requireSafePool || isPumpMint(k.mint, k.launchpad) ? pass('pool-structure', 'pump.fun mint (offline proxy)') : fail('pool-structure', 'offline', `not a pump.fun mint (launchpad ${k.launchpad}): the tape cannot verify its LP`),
|
|
223
|
+
gateLiquidityRecheck(r, ctx?.history ?? [], k, ctx?.exitFloorUsd ?? 2000),
|
|
224
|
+
];
|
|
225
|
+
const bad = res.find((x) => !x.pass);
|
|
226
|
+
if (bad)
|
|
227
|
+
return { ok: false, rule: bad.rule, detail: bad.detail, ...(bad.retryAfterMs !== undefined ? { retryAfterMs: bad.retryAfterMs } : {}) };
|
|
228
|
+
const v = verdictOf(r, res, k.createdAt !== null ? k.t - k.createdAt : null);
|
|
229
|
+
return { ok: true, recheck: true, ...(v.sizeCapPct !== null ? { sizeCapPct: v.sizeCapPct } : {}) };
|
|
230
|
+
};
|
|
231
|
+
}
|