solana-risk-engine 0.4.8 → 0.4.10
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/README.md +1 -1
- package/package.json +1 -1
- package/src/constants.js +24 -1
- package/src/i18n/en.js +3 -0
- package/src/i18n/es.js +3 -0
- package/src/index.js +1 -1
- package/src/risk.js +33 -10
- package/src/tokens.js +7 -0
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ for (const l of r.lights) // the four traffic lights
|
|
|
31
31
|
| **Serial creators** | For markets under 7 days: how many other tokens the creator launched in the last 24 h (RugCheck). 2 or more is a warning, 5 or more a red flag. `recentLaunches(client, wallet)` counts the same on chain for the wallet that *paid* a launch, which catches farms that name a fresh creator each time. |
|
|
32
32
|
| **Impostors** | Tokens using the symbol or name of a famous token (USDC, JUP, BONK, WIF, BTC, ETH...) with a different address. |
|
|
33
33
|
| **Real brands** | About 320 real companies with no token on Solana (Spotify, Nike, Zara, Ferrari...). A token called just that is a red flag and **HIGH**; the brand as one word of a longer name is a warning. Everyday words that are also brands (Shell, Target, Puma) are left out, and markets at least 30 days old with $100k+ liquidity are not checked. |
|
|
34
|
-
| **Known issuers and official tokens** |
|
|
34
|
+
| **Known issuers and official tokens** | Stablecoins from Paxos (PYUSD, USDG), Circle (USDC, EURC) and Tether (USDT), tokenized shares from xStocks (all recognised by their control keys, taken from mints the issuer lists on its own pages) and official tokens from exchanges and Socios.com (bnSOL, bbSOL, cbBTC, club fan tokens..., by mint address) are shown as dependency on the issuer. Their large holders (treasury, custodians, exchanges) are shown but do not count as concentration, a 0% transfer fee is not a warning, and a tiny Solana pool is a warning, not a red flag. A short **trusted** list (Circle, Paxos, Tether, xStocks; `trustedIssuer()`) goes further: every light green, holders not checked. |
|
|
35
35
|
| **Token type** | `STABLE`, `TOKENIZED`, `BRIDGED`, `CREATOR`, `OPEN`. Each type is judged by the rules that fit it: a bridged token normally has a program as mint authority, a memecoin does not. |
|
|
36
36
|
| **Bots and exchanges** | Wallets that made 200 transactions in under 2 days are marked `active` and are not counted as whales. |
|
|
37
37
|
| **Funder clusters** | The fee payer of each holder's *first* transaction reveals who created it. Holders sharing a funder are grouped, since that is often one team splitting the supply. Exchange hot wallets are excluded as sources. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-risk-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
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/constants.js
CHANGED
|
@@ -157,11 +157,34 @@ export const OFFICIAL_TOKENS = {
|
|
|
157
157
|
// (anyone can hand the mint authority to someone else's wallet) is not enough. Their control is the point of
|
|
158
158
|
// the product: it is shown as dependency on the issuer, not as a creator who can rug
|
|
159
159
|
export const ISSUERS = [
|
|
160
|
-
|
|
160
|
+
// trusted: a short list SolFlag vouches for (1/11/2026: Circle, Paxos, Tether, xStocks): every light green,
|
|
161
|
+
// holders not checked. Other verified issuers and official tokens keep the dependency in yellow
|
|
162
|
+
{ name: "xStocks (Backed)", trusted: true, keys: [
|
|
161
163
|
"7pt9tkctJPK7PPNQJ77GKg8ZffSF6QxoMiCFYHxrtaCj", // mint
|
|
162
164
|
"JDq14BWvqCRFNu1krb12bcRpbGtJZ1FLEakMw6FdxJNs", // freeze and pause (program)
|
|
163
165
|
"5aMNNLQJwAEeoemTEMkv5NVjqKwvvefRYCQ5Z67HFvEq", // permanent delegate, transfer hook, confidential transfers
|
|
164
166
|
"S7vYFFWH6BjJyEsdrPQpqpYTqLTrPRK6KW3VwsJuRaS" // share-split multiplier (scaled UI amount)
|
|
167
|
+
] },
|
|
168
|
+
// Each mint below is listed by its issuer (URL); the keys are the ones those mints carry on-chain (1/11/2026).
|
|
169
|
+
// An issuer is only added with an official page for its mints: FDUSD was left out for lack of one.
|
|
170
|
+
{ name: "Paxos", trusted: true, keys: [
|
|
171
|
+
// PYUSD 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo https://docs.paxos.com/guides/stablecoin/pyusd/mainnet
|
|
172
|
+
// USDG 2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH https://docs.paxos.com/guides/stablecoin/usdg/mainnet
|
|
173
|
+
"AxJkHRY3puoofyj7Pn512M1SGBgWroXMkEdNPQypkzbu", // PYUSD mint
|
|
174
|
+
"2e99QgqSDtiJSi5omGRVeZJuqNFRZdpy2YgG3mBd1AB4", // USDG mint
|
|
175
|
+
"2apBGMsS6ti9RyF5TwQTDswXBWskiJP2LD4cUEDqYJjk" // freeze, permanent delegate, hook, fee (both)
|
|
176
|
+
] },
|
|
177
|
+
{ name: "Circle", trusted: true, keys: [
|
|
178
|
+
// USDC EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, EURC HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr
|
|
179
|
+
// https://developers.circle.com/stablecoins/usdc-contract-addresses , .../eurc-contract-addresses
|
|
180
|
+
"BJE5MMbqXjVwjAF7oxwPYXnTXDyspzZyt4vwenNw5ruG", // USDC mint
|
|
181
|
+
"7dGbd2QZcCKcTndnHcTL8q7SMVXAkp688NTQYwrRCrar", // USDC freeze
|
|
182
|
+
"Hy9168u7b2Toujh8SJKtKK8DWbyaBbXPzxm337R4o4XY", // EURC mint
|
|
183
|
+
"6FF2CPcL6fat16QVJH6W27DeBcX7TSqbwY3a5wtyVJLC" // EURC freeze
|
|
184
|
+
] },
|
|
185
|
+
{ name: "Tether", trusted: true, keys: [
|
|
186
|
+
// USDT Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB (FAMOUS.USDT)
|
|
187
|
+
"Q6XprfkF8RQQKoQVG33xT88H7wi8Uk1B1CC7YAs69Gi" // USDT mint and freeze
|
|
165
188
|
] }
|
|
166
189
|
];
|
|
167
190
|
|
package/src/i18n/en.js
CHANGED
|
@@ -49,6 +49,8 @@ export default {
|
|
|
49
49
|
CON_UNAVAILABLE: () => "Could not check holder concentration (the service did not answer; try again in a while)",
|
|
50
50
|
CON_ALL_POOLS: (d) => "Almost all the supply is in pools or programs" + prog(d),
|
|
51
51
|
CON_OK: (d) => "No worrying concentration (largest individual wallet: " + pct(d.w1) + ")" + prog(d),
|
|
52
|
+
CON_TRUSTED: (d) => "Not counted: in a token from " + d.issuer + " the large holders are its treasury, custodians, exchanges and protocols",
|
|
53
|
+
CON_ISSUER: (d) => "The largest wallet holds " + pct(d.w1) + " and the top 10 hold " + pct(d.w10) + ": with a verified issuer these are usually its treasury, custodians, exchanges or protocols, so it does not count as a risk" + prog(d),
|
|
52
54
|
CON_HIGH: (d) => "The largest individual wallet holds " + pct(d.w1) + " and the top 10 hold " + pct(d.w10) + " of the supply" + prog(d),
|
|
53
55
|
CON_UNSPLIT: (d) => "Could not separate pools from people. The largest account holds " + pct(d.top1) + " of the supply",
|
|
54
56
|
|
|
@@ -65,6 +67,7 @@ export default {
|
|
|
65
67
|
DEP_PROGRAM_AUTHORITY: () => "A program controls the mint or freeze (bridge or protocol): you have to trust its code",
|
|
66
68
|
DEP_PERMANENT_DELEGATE: () => "The issuer can move or burn your tokens (permanent delegate): you trust the issuer",
|
|
67
69
|
DEP_STABLE_ISSUER: () => "Its value depends on whoever issues and backs it",
|
|
70
|
+
DEP_TRUSTED_ISSUER: (d) => "Issued by " + d.issuer + ", one of the few regulated issuers SolFlag trusts. Like any issuer, it can freeze or claw back tokens under a legal order",
|
|
68
71
|
DEP_KNOWN_ISSUER: (d) => "Issued by " + d.issuer + ": it can mint, freeze or move tokens, like any regulated issuer. You trust the issuer",
|
|
69
72
|
DEP_NONE: () => "Does not depend on an issuer or a bridge"
|
|
70
73
|
},
|
package/src/i18n/es.js
CHANGED
|
@@ -52,6 +52,8 @@ export default {
|
|
|
52
52
|
CON_UNAVAILABLE: () => "No se pudo comprobar la concentración de holders (el servicio no respondió; prueba otra vez en un rato)",
|
|
53
53
|
CON_ALL_POOLS: (d) => "Casi todo el supply está en pools o programas" + prog(d),
|
|
54
54
|
CON_OK: (d) => "Sin concentración preocupante (mayor wallet individual: " + pct(d.w1) + ")" + prog(d),
|
|
55
|
+
CON_TRUSTED: (d) => "No cuenta: en un token de " + d.issuer + " los grandes holders son su tesorería, custodios, exchanges y protocolos",
|
|
56
|
+
CON_ISSUER: (d) => "La mayor wallet tiene el " + pct(d.w1) + " y las 10 mayores el " + pct(d.w10) + ": con un emisor verificado suelen ser su tesorería, custodios, exchanges o protocolos, no cuenta como riesgo" + prog(d),
|
|
55
57
|
CON_HIGH: (d) => "La mayor wallet individual tiene el " + pct(d.w1) + " y las 10 mayores el " + pct(d.w10) + " del supply" + prog(d),
|
|
56
58
|
CON_UNSPLIT: (d) => "No se pudo separar pools de personas. La mayor cuenta tiene el " + pct(d.top1) + " del supply",
|
|
57
59
|
|
|
@@ -68,6 +70,7 @@ export default {
|
|
|
68
70
|
DEP_PROGRAM_AUTHORITY: () => "Un programa controla el mint o el freeze (puente o protocolo): hay que confiar en su código",
|
|
69
71
|
DEP_PERMANENT_DELEGATE: () => "El emisor puede mover o quemar tus tokens (delegado permanente): confías en el emisor",
|
|
70
72
|
DEP_STABLE_ISSUER: () => "Su valor depende de quien la emite y la respalda",
|
|
73
|
+
DEP_TRUSTED_ISSUER: (d) => "Lo emite " + d.issuer + ", un emisor regulado de los pocos que SolFlag considera de confianza. Como cualquier emisor, puede congelar o retirar tokens por orden legal",
|
|
71
74
|
DEP_KNOWN_ISSUER: (d) => "Lo emite " + d.issuer + ": puede crear, congelar o mover tokens, como cualquier emisor regulado. Confías en el emisor",
|
|
72
75
|
DEP_NONE: () => "No depende de un emisor ni de un puente"
|
|
73
76
|
},
|
package/src/index.js
CHANGED
|
@@ -48,7 +48,7 @@ export { isOnCurve, onCurve, b58bytes } from "./curve.js";
|
|
|
48
48
|
export { getTokens, getMint, getConc, getAuthKinds, isMint, accountKinds, kindOf } from "./tokens.js";
|
|
49
49
|
export { getMarkets, realLiq, impostorOf, INFLATED_RATIO, geckoLiquidity, geckoPair, brandOf, copiesOf, fakeLiquidity, FAKE_LIQ_RATIO } from "./markets.js";
|
|
50
50
|
export { rugcheckSummary, rugcheckReport, rugcheckGraph, netBalances, creatorLaunches, CREATOR_DEAD_MCAP } from "./rugcheck.js";
|
|
51
|
-
export { score, issuerOf, tokenType, buildLights, levelFrom, dataOnly, reasonsFrom, assess, SERIAL_WARN, SERIAL_BAD, HISTORY_MIN, HISTORY_WARN, HISTORY_BAD_MIN, HISTORY_BAD } from "./risk.js";
|
|
51
|
+
export { score, issuerOf, trustedIssuer, tokenType, buildLights, levelFrom, dataOnly, reasonsFrom, assess, SERIAL_WARN, SERIAL_BAD, HISTORY_MIN, HISTORY_WARN, HISTORY_BAD_MIN, HISTORY_BAD } from "./risk.js";
|
|
52
52
|
export { recentLaunches } from "./launch.js";
|
|
53
53
|
export { analyzeToken, analyzeWallet, analyzeAddress, sortResults } from "./wallet.js";
|
|
54
54
|
export { holderBase, markBusy, onlyReceives, PASSIVE_SAMPLE, findFunder, findFunders, clusterByFunder, holderRows, creatorInfo, analyzeHolders,
|
package/src/risk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EXT_POINTS, RANKS, PUMP_CURVE_DEXES, ISSUERS, OFFICIAL_TOKENS } from "./constants.js";
|
|
1
|
+
import { EXT_POINTS, RANKS, PUMP_CURVE_DEXES, ISSUERS, OFFICIAL_TOKENS, FAMOUS } from "./constants.js";
|
|
2
2
|
import { isCancel } from "./errors.js";
|
|
3
3
|
import { short, sleep } from "./util.js";
|
|
4
4
|
import { getMint, getAuthKinds, getConc } from "./tokens.js";
|
|
@@ -53,6 +53,9 @@ export function tokenType(info, pair, now) {
|
|
|
53
53
|
// it needs a stable price, real liquidity and some time trading (otherwise a scammer only has to
|
|
54
54
|
// name the token "USDX" and keep the price near $1 for a while)
|
|
55
55
|
if (/USD|DOLLAR/i.test(sym) && price > 0.97 && price < 1.03 && liq >= 200000 && ageDays >= 30) return "STABLE";
|
|
56
|
+
// a verified issuer's dollar or euro token is a stablecoin whatever pair DexScreener found (USDC showed
|
|
57
|
+
// "bridged" because its pair as base token is recent)
|
|
58
|
+
if (/USD|DOLLAR|EUR/i.test(sym) && issuerOf(info, pair && pair.baseToken && pair.baseToken.address)) return "STABLE";
|
|
56
59
|
if (info.ext.indexOf("permanentDelegate") >= 0) return "TOKENIZED";
|
|
57
60
|
const walletAuth = (info.mintAuth && ak[info.mintAuth] !== "program") || (info.freezeAuth && ak[info.freezeAuth] !== "program");
|
|
58
61
|
if (walletAuth) return "CREATOR";
|
|
@@ -72,6 +75,12 @@ export function issuerOf(info, mint) {
|
|
|
72
75
|
return found ? found.name : null;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
// Name of the issuer when it is on SolFlag's short trusted list, or null
|
|
79
|
+
export function trustedIssuer(info, mint) {
|
|
80
|
+
const name = issuerOf(info, mint);
|
|
81
|
+
return name && ISSUERS.some(function (i) { return i.trusted && i.name === name; }) ? name : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
// Extensions that only matter as they are set: a hook with no program and nobody able to add one does
|
|
76
85
|
// nothing, and new accounts are only born frozen when the default state says "frozen". Mint info without
|
|
77
86
|
// those details (older callers) keeps the old behaviour: the extension alone is enough
|
|
@@ -102,6 +111,7 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
102
111
|
now = now || Date.now();
|
|
103
112
|
const ak = (info && info.authKind) || {};
|
|
104
113
|
const issuer = issuerOf(info, pair && pair.baseToken && pair.baseToken.address);
|
|
114
|
+
const trusted = issuer && trustedIssuer(info, pair && pair.baseToken && pair.baseToken.address);
|
|
105
115
|
let it;
|
|
106
116
|
|
|
107
117
|
// 1) creator control
|
|
@@ -115,7 +125,8 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
115
125
|
if (info.ext.indexOf("transferHook") >= 0 && hookActive(info)) it.push(sig("CTL_TRANSFER_HOOK", "warn"));
|
|
116
126
|
if (info.ext.indexOf("defaultAccountState") >= 0 && bornFrozen(info)) it.push(sig("CTL_DEFAULT_FROZEN", "warn"));
|
|
117
127
|
}
|
|
118
|
-
|
|
128
|
+
// PYUSD and USDG carry the fee extension at 0%: under a known issuer that is how the product is built
|
|
129
|
+
if (info.ext.indexOf("transferFeeConfig") >= 0 && !(issuer && info.feeBps === 0)) it.push(sig("CTL_TRANSFER_FEE", "warn"));
|
|
119
130
|
if (info.ext.indexOf("nonTransferable") >= 0) it.push(sig("CTL_NON_TRANSFERABLE", "warn"));
|
|
120
131
|
}
|
|
121
132
|
// Launch farms put out a token every few minutes and drop each one as soon as buyers arrive: in a
|
|
@@ -132,14 +143,18 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
132
143
|
// 2) concentration in people (pools and programs do not count)
|
|
133
144
|
let concentration;
|
|
134
145
|
const soft = (type === "STABLE" || type === "TOKENIZED" || type === "BRIDGED");
|
|
135
|
-
if (
|
|
146
|
+
if (trusted) concentration = mkLight("concentration", [sig("CON_TRUSTED", "ok", { issuer: issuer })]);
|
|
147
|
+
else if (conc && conc.skip) concentration = mkLight("concentration", [sig("CON_SKIPPED", "na")]);
|
|
136
148
|
else if (!conc) concentration = mkLight("concentration", [sig("CON_UNAVAILABLE", "na")]);
|
|
137
149
|
else if (conc.ok) {
|
|
138
150
|
let st = "ok";
|
|
139
151
|
if (conc.w1 > 30 || conc.w10 > 70) st = "bad"; else if (conc.w1 > 15 || conc.w10 > 50) st = "warn";
|
|
140
152
|
if (soft && st === "bad") st = "warn";
|
|
141
153
|
const data = { w1: conc.w1, w10: conc.w10, prog: conc.prog, wallets: conc.nW }; // prog >= 20 is worth mentioning
|
|
142
|
-
|
|
154
|
+
let code = !conc.nW ? "CON_ALL_POOLS" : (st === "ok" ? "CON_OK" : "CON_HIGH");
|
|
155
|
+
// under a verified issuer the big holders are its treasury, custodians, exchanges and lending
|
|
156
|
+
// protocols, not someone about to dump: shown, not counted (PYUSD 56% in one wallet, 1/11)
|
|
157
|
+
if (issuer && code === "CON_HIGH") { code = "CON_ISSUER"; st = "ok"; }
|
|
143
158
|
concentration = mkLight("concentration", [sig(code, st, data)]);
|
|
144
159
|
} else {
|
|
145
160
|
concentration = mkLight("concentration", [sig("CON_UNSPLIT", (conc.top1 > 30 || conc.top10 > 70) ? "warn" : "ok", { top1: conc.top1, top10: conc.top10 })]);
|
|
@@ -148,7 +163,9 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
148
163
|
// 3) market and exit
|
|
149
164
|
const liq = (pair.liquidity && pair.liquidity.usd) || 0, vol = (pair.volume && pair.volume.h24) || 0;
|
|
150
165
|
it = [];
|
|
151
|
-
|
|
166
|
+
// a verified issuer's token with little real liquidity on Solana trades elsewhere: not a trap
|
|
167
|
+
if (pair.liqRaw && issuer) it.push(sig(liq < 10000 ? "MKT_LIQ_VERY_LOW" : "MKT_LIQ_LOW", "warn", { liq: liq, offSolana: true }));
|
|
168
|
+
else if (pair.liqRaw) it.push(sig("MKT_LIQ_INFLATED", "bad", { reported: pair.liqRaw, real: liq }));
|
|
152
169
|
else if (liq < 10000) it.push(sig("MKT_LIQ_VERY_LOW", "bad", { liq: liq }));
|
|
153
170
|
else if (liq < 50000) it.push(sig("MKT_LIQ_LOW", "warn", { liq: liq }));
|
|
154
171
|
if (vol < 1000) it.push(sig("MKT_VOLUME_LOW", "warn", { vol: vol }));
|
|
@@ -180,13 +197,14 @@ export function buildLights(info, conc, pair, value, type, rc, now, launches) {
|
|
|
180
197
|
|
|
181
198
|
// 4) dependency on an issuer or a bridge
|
|
182
199
|
it = [];
|
|
183
|
-
if (
|
|
200
|
+
if (trusted) it.push(sig("DEP_TRUSTED_ISSUER", "ok", { issuer: issuer }));
|
|
201
|
+
else if (issuer) it.push(sig("DEP_KNOWN_ISSUER", "warn", { issuer: issuer }));
|
|
184
202
|
else if (info) {
|
|
185
203
|
if ((info.mintAuth && ak[info.mintAuth] === "program") || (info.freezeAuth && ak[info.freezeAuth] === "program"))
|
|
186
204
|
it.push(sig("DEP_PROGRAM_AUTHORITY", "warn"));
|
|
187
205
|
if (info.ext.indexOf("permanentDelegate") >= 0) it.push(sig("DEP_PERMANENT_DELEGATE", "warn"));
|
|
188
206
|
}
|
|
189
|
-
if (type === "STABLE") it.push(sig("DEP_STABLE_ISSUER", "warn"));
|
|
207
|
+
if (type === "STABLE" && !trusted) it.push(sig("DEP_STABLE_ISSUER", "warn"));
|
|
190
208
|
const dependency = mkLight("dependency", it, sig("DEP_NONE", "ok"));
|
|
191
209
|
|
|
192
210
|
return [control, concentration, market, dependency];
|
|
@@ -253,7 +271,9 @@ export async function assess(client, mint, pair, opts) {
|
|
|
253
271
|
if (info && pair && (info.mintAuth || info.freezeAuth)) {
|
|
254
272
|
try { info.authKind = await getAuthKinds(client, info); } catch (e) { if (isCancel(e)) throw e; }
|
|
255
273
|
}
|
|
256
|
-
|
|
274
|
+
// a trusted issuer's holders are not checked (its treasury and exchanges; from the browser the public RPC
|
|
275
|
+
// often cannot list USDC's holders, which left it "unchecked" and medium)
|
|
276
|
+
if (info && pair && (opts.skipConcentration || trustedIssuer(info, mint))) conc = { skip: true };
|
|
257
277
|
else if (info && pair) {
|
|
258
278
|
// In tokens with millions of holders (BONK) getTokenLargestAccounts sometimes times out: a second try usually works
|
|
259
279
|
for (let tryC = 0; tryC < 2 && !conc; tryC++) {
|
|
@@ -274,7 +294,10 @@ export async function assess(client, mint, pair, opts) {
|
|
|
274
294
|
const front = [];
|
|
275
295
|
if (imp) front.push(sig("IMPOSTOR", "bad", { symbol: imp.sym, real: imp.real }));
|
|
276
296
|
if (brand) front.push(sig(brand.exact ? "BRAND_COPY" : "BRAND_NAME", brand.exact ? "bad" : "warn", { brand: brand.brand }));
|
|
277
|
-
|
|
297
|
+
// the real USDC/USDT is rarely the base token of a DexScreener pair, so the search found an older
|
|
298
|
+
// "USDC" and called the real one the copy (USDC and USDT showed HIGH, 1/11)
|
|
299
|
+
const canon = Object.keys(FAMOUS).some(function (k) { return FAMOUS[k] === mint; }) || !!issuerOf(info, mint);
|
|
300
|
+
if (copies && !imp && !official && !canon) {
|
|
278
301
|
const strong = copies.origLiq >= 50000 && copies.origLiq >= 10 * Math.max(copies.liq, 1);
|
|
279
302
|
front.push(sig("COPYCAT", strong ? "bad" : "warn", copies));
|
|
280
303
|
}
|
|
@@ -285,7 +308,7 @@ export async function assess(client, mint, pair, opts) {
|
|
|
285
308
|
const lf = levelFrom(lights); lvl = lf.lvl; sev = lf.sev; reasons = reasonsFrom(lights);
|
|
286
309
|
// a copy of a famous token or a real company, or liquidity faked 100 times over, is high risk even
|
|
287
310
|
// when everything else looks normal: it is built to look like something it is not
|
|
288
|
-
if (imp || (brand && brand.exact) || fakeLiquidity(pair)) { if (lvl !== "HIGH") sev += 5; lvl = "HIGH"; }
|
|
311
|
+
if (imp || (brand && brand.exact) || (fakeLiquidity(pair) && !issuerOf(info, mint))) { if (lvl !== "HIGH") sev += 5; lvl = "HIGH"; }
|
|
289
312
|
} else {
|
|
290
313
|
const s = score(info);
|
|
291
314
|
pts = s.pts; reasons = s.why; lvl = pts >= 25 ? "SPAM" : "NO_MARKET";
|
package/src/tokens.js
CHANGED
|
@@ -45,11 +45,18 @@ export async function getMint(client, mint) {
|
|
|
45
45
|
// "default account state" only matters when new accounts are born frozen
|
|
46
46
|
hook: { program: hook.programId || null, auth: hook.authority || null },
|
|
47
47
|
defaultState: state("defaultAccountState").accountState || null,
|
|
48
|
+
// transfer fee in basis points (the higher of the current and the scheduled one); null when not set
|
|
49
|
+
feeBps: feeBps(state("transferFeeConfig")),
|
|
48
50
|
// every key that controls something in the extensions (delegate, hook, pause, metadata...)
|
|
49
51
|
extKeys: extKeys(exts)
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
function feeBps(s) {
|
|
56
|
+
const a = s.newerTransferFee && s.newerTransferFee.transferFeeBasisPoints, b = s.olderTransferFee && s.olderTransferFee.transferFeeBasisPoints;
|
|
57
|
+
return (typeof a === "number" || typeof b === "number") ? Math.max(Number(a) || 0, Number(b) || 0) : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
function extKeys(exts) {
|
|
54
61
|
const out = [];
|
|
55
62
|
exts.forEach(function (e) {
|