solana-risk-engine 0.3.2 → 0.4.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/package.json +1 -1
- package/src/holders.js +7 -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 +14 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-risk-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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/holders.js
CHANGED
|
@@ -64,7 +64,13 @@ export async function findFunder(client, addr) {
|
|
|
64
64
|
}
|
|
65
65
|
if (!reachedEnd || !sigs || !sigs.length) return null;
|
|
66
66
|
const earliest = sigs[sigs.length - 1].signature;
|
|
67
|
-
|
|
67
|
+
// version 1 is needed for recent transactions; an older RPC that does not know it yet gets 0
|
|
68
|
+
let tx;
|
|
69
|
+
try { tx = await client.rpc("getTransaction", [earliest, { maxSupportedTransactionVersion: 1, encoding: "jsonParsed" }]); }
|
|
70
|
+
catch (e) {
|
|
71
|
+
if (isCancel(e) || !/version|invalid param/i.test(e.message || "")) throw e;
|
|
72
|
+
tx = await client.rpc("getTransaction", [earliest, { maxSupportedTransactionVersion: 0, encoding: "jsonParsed" }]);
|
|
73
|
+
}
|
|
68
74
|
const keys = tx && tx.transaction && tx.transaction.message && tx.transaction.message.accountKeys;
|
|
69
75
|
if (!keys || !keys.length) return null;
|
|
70
76
|
const payer = keys[0] && (keys[0].pubkey !== undefined ? keys[0].pubkey : keys[0]);
|
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
|
@@ -32,16 +32,27 @@ export function rugcheckReport(client, mint) {
|
|
|
32
32
|
return client.softJson(RUGCHECK + mint + "/report", 10000);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
//
|
|
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
|
|
36
41
|
// is ~10 KB (the 700 KB ones are old tokens with many markets and holders).
|
|
37
|
-
// @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
|
|
38
43
|
export async function creatorLaunches(client, mint, now) {
|
|
39
44
|
const rc = await rugcheckReport(client, mint);
|
|
40
45
|
if (!rc || !rc.creator) return null;
|
|
41
46
|
now = now || Date.now();
|
|
42
47
|
const others = (rc.creatorTokens || []).filter(function (t) { return t && t.mint !== mint; });
|
|
43
48
|
const day = others.filter(function (t) { const at = Date.parse(t.createdAt); return at && now - at < 86400000; }).length;
|
|
44
|
-
|
|
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 };
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
// The report only gives each network's size; this endpoint gives its wallets and holdings
|