solana-risk-engine 0.3.1 → 0.4.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/package.json +1 -1
- package/src/i18n/en.js +1 -0
- package/src/i18n/es.js +1 -0
- package/src/index.js +2 -2
- package/src/risk.js +8 -1
- package/src/rugcheck.js +30 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-risk-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Explainable risk analysis for Solana tokens and wallets: creator control, real holder concentration, inflated liquidity, impostor tokens, funder clusters and PnL. Zero dependencies, runs in Node and the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/i18n/en.js
CHANGED
|
@@ -37,6 +37,7 @@ export default {
|
|
|
37
37
|
CTL_ONLY_PROGRAMS: () => "Only program-controlled authorities (see dependency)",
|
|
38
38
|
CTL_NONE: () => "No active authorities: nobody can mint more or freeze",
|
|
39
39
|
CTL_SERIAL_CREATOR: (d) => "The creator launched " + d.n + " other tokens in the last 24 h (per RugCheck): a serial launcher, who usually drops each token as soon as buyers arrive",
|
|
40
|
+
CTL_CREATOR_HISTORY: (d) => "Of the " + d.old + " tokens the creator launched before (over 24 h ago), " + d.dead + " stayed under $10,000 market cap (per RugCheck): their launches rarely take off",
|
|
40
41
|
IMPOSTOR: (d) => "Impersonates " + d.symbol + ": the real " + d.symbol + " has another address (" + short(d.real) + "). It is a copy, almost certainly a scam",
|
|
41
42
|
|
|
42
43
|
CON_SKIPPED: () => "Holder concentration not checked",
|
package/src/i18n/es.js
CHANGED
|
@@ -40,6 +40,7 @@ export default {
|
|
|
40
40
|
CTL_ONLY_PROGRAMS: () => "Solo hay autoridades controladas por un programa (ver dependencia)",
|
|
41
41
|
CTL_NONE: () => "Sin autoridades activas: nadie puede crear más ni congelar",
|
|
42
42
|
CTL_SERIAL_CREATOR: (d) => "El creador ha lanzado " + d.n + " tokens más en las últimas 24 h (según RugCheck): es un lanzador en serie, que suele abandonar cada token en cuanto llegan compradores",
|
|
43
|
+
CTL_CREATOR_HISTORY: (d) => "De los " + d.old + " tokens que el creador lanzó antes (hace más de 24 h), " + d.dead + " se quedaron por debajo de 10.000 $ de capitalización (según RugCheck): sus lanzamientos no suelen despegar",
|
|
43
44
|
IMPOSTOR: (d) => "Se hace pasar por " + d.symbol + ": el " + d.symbol + " de verdad tiene otra dirección (" + short(d.real) + "). Es una copia, casi seguro una estafa",
|
|
44
45
|
|
|
45
46
|
CON_SKIPPED: () => "Sin comprobar la concentración de holders",
|
package/src/index.js
CHANGED
|
@@ -45,8 +45,8 @@ export * from "./constants.js";
|
|
|
45
45
|
export { isOnCurve, onCurve, b58bytes } from "./curve.js";
|
|
46
46
|
export { getTokens, getMint, getConc, getAuthKinds, isMint, accountKinds, kindOf } from "./tokens.js";
|
|
47
47
|
export { getMarkets, realLiq, impostorOf, INFLATED_RATIO, geckoLiquidity, geckoPair } from "./markets.js";
|
|
48
|
-
export { rugcheckSummary, rugcheckReport, rugcheckGraph, netBalances, creatorLaunches } from "./rugcheck.js";
|
|
49
|
-
export { score, tokenType, buildLights, levelFrom, dataOnly, reasonsFrom, assess, SERIAL_WARN, SERIAL_BAD } from "./risk.js";
|
|
48
|
+
export { rugcheckSummary, rugcheckReport, rugcheckGraph, netBalances, creatorLaunches, CREATOR_DEAD_MCAP } from "./rugcheck.js";
|
|
49
|
+
export { score, tokenType, buildLights, levelFrom, dataOnly, reasonsFrom, assess, SERIAL_WARN, SERIAL_BAD, HISTORY_MIN, HISTORY_WARN, HISTORY_BAD_MIN, HISTORY_BAD } from "./risk.js";
|
|
50
50
|
export { recentLaunches } from "./launch.js";
|
|
51
51
|
export { analyzeToken, analyzeWallet, analyzeAddress, sortResults } from "./wallet.js";
|
|
52
52
|
export { holderBase, markBusy, findFunder, findFunders, clusterByFunder, holderRows, creatorInfo, analyzeHolders } from "./holders.js";
|
package/src/risk.js
CHANGED
|
@@ -16,6 +16,8 @@ function sig(code, s, data) {
|
|
|
16
16
|
const DAY = 86400000;
|
|
17
17
|
// Other tokens launched by the same creator in the last 24 h that make it a serial launcher
|
|
18
18
|
export const SERIAL_WARN = 2, SERIAL_BAD = 5;
|
|
19
|
+
// Creator track record: at least HISTORY_MIN older tokens and this share of them never took off
|
|
20
|
+
export const HISTORY_MIN = 3, HISTORY_WARN = 0.8, HISTORY_BAD_MIN = 10, HISTORY_BAD = 0.9;
|
|
19
21
|
|
|
20
22
|
/* ---------- old point score: only for tokens without a market, to spot spam ---------- */
|
|
21
23
|
export function score(info) {
|
|
@@ -73,7 +75,7 @@ function mkLight(k, items, okSignal) {
|
|
|
73
75
|
* @param value USD value the analysed wallet holds (0 when checking a token alone)
|
|
74
76
|
* @param type tokenType(info, pair)
|
|
75
77
|
* @param rc RugCheck summary or null
|
|
76
|
-
* @param launches creatorLaunches result or null:
|
|
78
|
+
* @param launches creatorLaunches result or null: what else the creator launched (last 24 h, and how older ones did)
|
|
77
79
|
* @returns [control, concentration, market, dependency]
|
|
78
80
|
*/
|
|
79
81
|
export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
@@ -95,6 +97,11 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
95
97
|
// Launch farms put out a token every few minutes and drop each one as soon as buyers arrive: in a
|
|
96
98
|
// 100-token Pump.fun sample one wallet launched 7 copies of "GMS" in 10 minutes
|
|
97
99
|
if (launches && launches.day >= SERIAL_WARN) it.push(sig("CTL_SERIAL_CREATOR", launches.day >= SERIAL_BAD ? "bad" : "warn", { n: launches.day, creator: launches.creator }));
|
|
100
|
+
// Older launches that went nowhere: farms that spread their tokens over days escape the 24 h count
|
|
101
|
+
if (launches && launches.old >= HISTORY_MIN && launches.dead / launches.old >= HISTORY_WARN) {
|
|
102
|
+
const bad = launches.old >= HISTORY_BAD_MIN && launches.dead / launches.old >= HISTORY_BAD;
|
|
103
|
+
it.push(sig("CTL_CREATOR_HISTORY", bad ? "bad" : "warn", { old: launches.old, dead: launches.dead, best: launches.best, creator: launches.creator }));
|
|
104
|
+
}
|
|
98
105
|
const control = mkLight("control", it, sig((info && (info.mintAuth || info.freezeAuth)) ? "CTL_ONLY_PROGRAMS" : "CTL_NONE", "ok"));
|
|
99
106
|
|
|
100
107
|
// 2) concentration in people (pools and programs do not count)
|
package/src/rugcheck.js
CHANGED
|
@@ -7,8 +7,22 @@ import { pool } from "./util.js";
|
|
|
7
7
|
// which on-chain data does not show without decoding each DEX's binary layout (Raydium, Orca, Meteora
|
|
8
8
|
// and pump.fun all differ). It is an extra, never a requirement: on failure these return null and the
|
|
9
9
|
// rest of the analysis is exactly the same.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// RugCheck sometimes answers lpLockedPct 0 for a pool that is ~100% locked (5 of 40 calls on a
|
|
11
|
+
// Raydium CPMM token, 2026-10-26; a watched token flipped high/medium every check). A low value is
|
|
12
|
+
// therefore asked again up to twice and the highest answer wins: a pool that really is unlocked
|
|
13
|
+
// reads low every time, so only the false alarm goes away.
|
|
14
|
+
export const LP_RECHECK_BELOW = 60;
|
|
15
|
+
export async function rugcheckSummary(client, mint) {
|
|
16
|
+
const url = RUGCHECK + mint + "/report/summary";
|
|
17
|
+
let best = await client.softJson(url, 6000);
|
|
18
|
+
for (let i = 0; i < 2 && lowLp(best); i++) {
|
|
19
|
+
const again = await client.softJson(url, 6000);
|
|
20
|
+
if (again && typeof again.lpLockedPct === "number" && again.lpLockedPct > best.lpLockedPct) best = again;
|
|
21
|
+
}
|
|
22
|
+
return best;
|
|
23
|
+
}
|
|
24
|
+
function lowLp(rc) {
|
|
25
|
+
return !!rc && typeof rc.lpLockedPct === "number" && rc.lpLockedPct < LP_RECHECK_BELOW;
|
|
12
26
|
}
|
|
13
27
|
|
|
14
28
|
// Full report (~700 KB, so ask once per holder search, never per token of a wallet): who created the
|
|
@@ -18,16 +32,27 @@ export function rugcheckReport(client, mint) {
|
|
|
18
32
|
return client.softJson(RUGCHECK + mint + "/report", 10000);
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
//
|
|
35
|
+
// A creator token older than a day still under this market cap never took off: a Pump.fun token
|
|
36
|
+
// starts at ~28 SOL (~$2,800 at $100/SOL). In a 43-token farm (2026-10-27) all sat at $2,800-4,400
|
|
37
|
+
export const CREATOR_DEAD_MCAP = 10000;
|
|
38
|
+
|
|
39
|
+
// What else the creator launched: how many in the last 24 h and, of the older ones RugCheck has a
|
|
40
|
+
// market cap for, how many stayed under CREATOR_DEAD_MCAP. Only for new tokens: their full report
|
|
22
41
|
// is ~10 KB (the 700 KB ones are old tokens with many markets and holders).
|
|
23
|
-
// @returns {creator, day, total} or null when RugCheck does not answer or does not know the creator
|
|
42
|
+
// @returns {creator, day, total, old, dead, best} or null when RugCheck does not answer or does not know the creator
|
|
24
43
|
export async function creatorLaunches(client, mint, now) {
|
|
25
44
|
const rc = await rugcheckReport(client, mint);
|
|
26
45
|
if (!rc || !rc.creator) return null;
|
|
27
46
|
now = now || Date.now();
|
|
28
47
|
const others = (rc.creatorTokens || []).filter(function (t) { return t && t.mint !== mint; });
|
|
29
48
|
const day = others.filter(function (t) { const at = Date.parse(t.createdAt); return at && now - at < 86400000; }).length;
|
|
30
|
-
|
|
49
|
+
const old = others.filter(function (t) {
|
|
50
|
+
const at = Date.parse(t.createdAt);
|
|
51
|
+
return at && now - at >= 86400000 && typeof t.marketCap === "number" && isFinite(t.marketCap);
|
|
52
|
+
});
|
|
53
|
+
const dead = old.filter(function (t) { return t.marketCap < CREATOR_DEAD_MCAP; }).length;
|
|
54
|
+
const best = old.reduce(function (m, t) { return Math.max(m, t.marketCap); }, 0);
|
|
55
|
+
return { creator: rc.creator, day: day, total: others.length, old: old.length, dead: dead, best: old.length ? best : null };
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
// The report only gives each network's size; this endpoint gives its wallets and holdings
|