brainblast 0.6.2 → 0.6.4

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.
@@ -0,0 +1,31 @@
1
+ // src/solanaCanonicalMints.ts
2
+ var CANONICAL_MINTS = {
3
+ USDC: { symbol: "USDC", name: "USD Coin", mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" },
4
+ USDT: { symbol: "USDT", name: "USD Tether", mint: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" },
5
+ SOL: { symbol: "SOL", name: "Wrapped SOL", mint: "So11111111111111111111111111111111111111112" },
6
+ WSOL: { symbol: "WSOL", name: "Wrapped SOL", mint: "So11111111111111111111111111111111111111112" },
7
+ JUP: { symbol: "JUP", name: "Jupiter", mint: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" },
8
+ BONK: { symbol: "BONK", name: "Bonk", mint: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" },
9
+ WIF: { symbol: "WIF", name: "dogwifhat", mint: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm" },
10
+ PYTH: { symbol: "PYTH", name: "Pyth Network", mint: "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3" },
11
+ RAY: { symbol: "RAY", name: "Raydium", mint: "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" },
12
+ ORCA: { symbol: "ORCA", name: "Orca", mint: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE" },
13
+ MNGO: { symbol: "MNGO", name: "Mango", mint: "MangoCzJ36AjZyKwVPDeoDLiiwVqHVDRtNoCKzSPH7" },
14
+ mSOL: { symbol: "mSOL", name: "Marinade Staked SOL", mint: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So" }
15
+ };
16
+ var CANONICAL_BY_MINT = Object.fromEntries(
17
+ Object.values(CANONICAL_MINTS).map((c) => [c.mint, c])
18
+ );
19
+ function canonicalMintForSymbol(symbol) {
20
+ return CANONICAL_MINTS[symbol.toUpperCase()];
21
+ }
22
+ function isCanonicalMint(mint) {
23
+ return mint in CANONICAL_BY_MINT;
24
+ }
25
+
26
+ export {
27
+ CANONICAL_MINTS,
28
+ CANONICAL_BY_MINT,
29
+ canonicalMintForSymbol,
30
+ isCanonicalMint
31
+ };
@@ -1,3 +1,7 @@
1
+ import {
2
+ CANONICAL_MINTS
3
+ } from "./chunk-2XJORJPQ.js";
4
+
1
5
  // src/finder.ts
2
6
  import { Project, SyntaxKind } from "ts-morph";
3
7
 
@@ -789,6 +793,65 @@ var forbiddenCallReplacement = (c, p) => {
789
793
  };
790
794
  };
791
795
 
796
+ // src/checkers/solanaMintIdentity.ts
797
+ import { Node, SyntaxKind as SyntaxKind11 } from "ts-morph";
798
+ var BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
799
+ function tokenize(name) {
800
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").split(/[_\s]+/).map((t) => t.toUpperCase()).filter(Boolean);
801
+ }
802
+ function symbolFromName(name) {
803
+ return tokenize(name).find((t) => t in CANONICAL_MINTS);
804
+ }
805
+ function addressFromInitializer(init) {
806
+ if (Node.isStringLiteral(init)) {
807
+ return init.getLiteralValue();
808
+ }
809
+ if (Node.isNewExpression(init)) {
810
+ const args = init.getArguments();
811
+ if (args.length > 0 && Node.isStringLiteral(args[0])) {
812
+ return args[0].getLiteralValue?.() ?? args[0].getText().replace(/['"]/g, "");
813
+ }
814
+ }
815
+ return void 0;
816
+ }
817
+ var solanaMintIdentity = (c, _params) => {
818
+ const sf = c.fn.getSourceFile?.();
819
+ if (!sf) return { result: "cant_tell", detail: "Could not access source file" };
820
+ let foundWrong = false;
821
+ let foundRight = false;
822
+ for (const vd of sf.getDescendantsOfKind(SyntaxKind11.VariableDeclaration)) {
823
+ const sym = symbolFromName(vd.getName());
824
+ if (!sym) continue;
825
+ const init = vd.getInitializer();
826
+ if (!init) continue;
827
+ const addr = addressFromInitializer(init);
828
+ if (!addr || !BASE58_RE.test(addr)) continue;
829
+ if (addr === CANONICAL_MINTS[sym].mint) {
830
+ foundRight = true;
831
+ } else {
832
+ foundWrong = true;
833
+ }
834
+ }
835
+ for (const pa of sf.getDescendantsOfKind(SyntaxKind11.PropertyAssignment)) {
836
+ const nameNode = pa.getNameNode();
837
+ const name = Node.isIdentifier(nameNode) ? nameNode.getText() : nameNode.getText().replace(/['"]/g, "");
838
+ const sym = symbolFromName(name);
839
+ if (!sym) continue;
840
+ const init = pa.getInitializer();
841
+ if (!init) continue;
842
+ const addr = addressFromInitializer(init);
843
+ if (!addr || !BASE58_RE.test(addr)) continue;
844
+ if (addr === CANONICAL_MINTS[sym].mint) {
845
+ foundRight = true;
846
+ } else {
847
+ foundWrong = true;
848
+ }
849
+ }
850
+ if (foundWrong) return { result: "fail", detail: "Mint constant has wrong address for its symbol" };
851
+ if (foundRight) return { result: "pass", detail: "All symbol-named mint constants have canonical addresses" };
852
+ return { result: "cant_tell", detail: "No symbol-named mint constants found" };
853
+ };
854
+
792
855
  // src/checkers/index.ts
793
856
  var registry = {
794
857
  "positional-arg-identity": positionalArgIdentity,
@@ -801,7 +864,8 @@ var registry = {
801
864
  "env-secrets-committed": envSecretsCommitted,
802
865
  "taint-to-sink": taintToSink,
803
866
  "literal-multiplier-wrong-constant": literalMultiplierWrongConstant,
804
- "forbidden-call-replacement": forbiddenCallReplacement
867
+ "forbidden-call-replacement": forbiddenCallReplacement,
868
+ "solana-mint-identity-mismatch": solanaMintIdentity
805
869
  };
806
870
  function runChecker(kind, c, params) {
807
871
  const fn = registry[kind];
@@ -1017,7 +1081,7 @@ function findRustCandidates(targetDir, rule) {
1017
1081
  }
1018
1082
 
1019
1083
  // src/fixers/positionalArgIdentity.ts
1020
- import { SyntaxKind as SyntaxKind11 } from "ts-morph";
1084
+ import { SyntaxKind as SyntaxKind12 } from "ts-morph";
1021
1085
 
1022
1086
  // src/fixers/diffUtil.ts
1023
1087
  function buildDiff(node, replacement) {
@@ -1042,9 +1106,9 @@ function buildDiff(node, replacement) {
1042
1106
  // src/fixers/positionalArgIdentity.ts
1043
1107
  var fixPositionalArgIdentity = (c, p, outcome) => {
1044
1108
  if (outcome.result !== "fail") return void 0;
1045
- const calls = c.fn.getDescendantsOfKind(SyntaxKind11.CallExpression).filter((call) => {
1109
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression).filter((call) => {
1046
1110
  const exp = call.getExpression();
1047
- return exp.getKind() === SyntaxKind11.PropertyAccessExpression && exp.asKind(SyntaxKind11.PropertyAccessExpression).getName() === p.call;
1111
+ return exp.getKind() === SyntaxKind12.PropertyAccessExpression && exp.asKind(SyntaxKind12.PropertyAccessExpression).getName() === p.call;
1048
1112
  });
1049
1113
  if (calls.length === 0) {
1050
1114
  const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
@@ -1059,7 +1123,7 @@ Do not call JSON.parse() on the body before this verification step.`
1059
1123
  }
1060
1124
  const arg = calls[0].getArguments()[p.argIndex];
1061
1125
  const wantParam = c.params[p.paramIndex];
1062
- if (arg && wantParam && arg.getKind() === SyntaxKind11.CallExpression) {
1126
+ if (arg && wantParam && arg.getKind() === SyntaxKind12.CallExpression) {
1063
1127
  return {
1064
1128
  summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
1065
1129
  diff: buildDiff(arg, wantParam)
@@ -1069,12 +1133,12 @@ Do not call JSON.parse() on the body before this verification step.`
1069
1133
  };
1070
1134
 
1071
1135
  // src/fixers/requiredCallWithOptions.ts
1072
- import { SyntaxKind as SyntaxKind12 } from "ts-morph";
1136
+ import { SyntaxKind as SyntaxKind13 } from "ts-morph";
1073
1137
  function callName6(call) {
1074
1138
  const exp = call.getExpression();
1075
- if (exp.getKind() === SyntaxKind12.Identifier) return exp.getText();
1076
- if (exp.getKind() === SyntaxKind12.PropertyAccessExpression) {
1077
- return exp.asKind(SyntaxKind12.PropertyAccessExpression).getName();
1139
+ if (exp.getKind() === SyntaxKind13.Identifier) return exp.getText();
1140
+ if (exp.getKind() === SyntaxKind13.PropertyAccessExpression) {
1141
+ return exp.asKind(SyntaxKind13.PropertyAccessExpression).getName();
1078
1142
  }
1079
1143
  return "";
1080
1144
  }
@@ -1092,15 +1156,15 @@ function placeholderFor(propName) {
1092
1156
  }
1093
1157
  var fixRequiredCallWithOptions = (c, p, outcome) => {
1094
1158
  if (outcome.result !== "fail") return void 0;
1095
- const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression);
1159
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression);
1096
1160
  const verify = calls.filter((x) => p.verifyCalls.includes(callName6(x)));
1097
1161
  if (verify.length > 0) {
1098
1162
  const call = verify[0];
1099
1163
  const args = call.getArguments();
1100
1164
  const lastArg = args[args.length - 1];
1101
- const obj = lastArg?.asKind(SyntaxKind12.ObjectLiteralExpression);
1165
+ const obj = lastArg?.asKind(SyntaxKind13.ObjectLiteralExpression);
1102
1166
  const presentNames = obj ? obj.getProperties().map((pr) => {
1103
- const pa = pr.asKind(SyntaxKind12.PropertyAssignment) ?? pr.asKind(SyntaxKind12.ShorthandPropertyAssignment);
1167
+ const pa = pr.asKind(SyntaxKind13.PropertyAssignment) ?? pr.asKind(SyntaxKind13.ShorthandPropertyAssignment);
1104
1168
  return pa?.getName() ?? "";
1105
1169
  }) : [];
1106
1170
  const missingGroups = p.requiredProps.filter(
@@ -1134,22 +1198,22 @@ JWKS must come from Privy's published JWKS endpoint for your app.`
1134
1198
  };
1135
1199
 
1136
1200
  // src/fixers/literalMultiplierWrongConstant.ts
1137
- import { SyntaxKind as SyntaxKind13 } from "ts-morph";
1201
+ import { SyntaxKind as SyntaxKind14 } from "ts-morph";
1138
1202
  function callName7(call) {
1139
1203
  const exp = call.getExpression();
1140
- if (exp.getKind() === SyntaxKind13.Identifier) return exp.getText();
1141
- if (exp.getKind() === SyntaxKind13.PropertyAccessExpression) {
1142
- return exp.asKind(SyntaxKind13.PropertyAccessExpression).getName();
1204
+ if (exp.getKind() === SyntaxKind14.Identifier) return exp.getText();
1205
+ if (exp.getKind() === SyntaxKind14.PropertyAccessExpression) {
1206
+ return exp.asKind(SyntaxKind14.PropertyAccessExpression).getName();
1143
1207
  }
1144
1208
  return "";
1145
1209
  }
1146
1210
  function findIdentifier(node, name) {
1147
- if (node.getKind() === SyntaxKind13.Identifier && node.getText() === name) return node;
1148
- return node.getDescendantsOfKind(SyntaxKind13.Identifier).find((id) => id.getText() === name);
1211
+ if (node.getKind() === SyntaxKind14.Identifier && node.getText() === name) return node;
1212
+ return node.getDescendantsOfKind(SyntaxKind14.Identifier).find((id) => id.getText() === name);
1149
1213
  }
1150
1214
  var fixLiteralMultiplierWrongConstant = (c, p, outcome) => {
1151
1215
  if (outcome.result !== "fail") return void 0;
1152
- const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression).filter((call) => callName7(call) === p.call);
1216
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind14.CallExpression).filter((call) => callName7(call) === p.call);
1153
1217
  if (calls.length === 0) return void 0;
1154
1218
  const arg = calls[0].getArguments()[p.argIndex];
1155
1219
  if (!arg) return void 0;
@@ -0,0 +1,89 @@
1
+ // src/ricomaps.ts
2
+ function deployerFlagsFrom(sec) {
3
+ if (!sec) return [];
4
+ const flags = [];
5
+ if (sec.hasMintAuthority) flags.push("mint-authority-live");
6
+ if (sec.hasFreezeAuthority) flags.push("freeze-authority-live");
7
+ if (sec.isMutable) flags.push("metadata-mutable");
8
+ if (sec.riskFactors) flags.push(...sec.riskFactors);
9
+ return flags;
10
+ }
11
+ function renderRicoText(r) {
12
+ const lines = [
13
+ `Rico Maps analysis \u2014 ${r.mint}`,
14
+ ` Risk score: ${r.riskScore}/100`,
15
+ ` Holders: ${r.totalHolders}`,
16
+ ` Cabal: ${r.cabalCount}`,
17
+ ` Snipers: ${r.snipersDetected ? `yes (${(r.sniperPct * 100).toFixed(1)}%)` : "none"}`,
18
+ ` Bundle clusters: ${r.bundleClustersDetected ? "detected" : "none"}`
19
+ ];
20
+ if (r.deployerFlags.length) {
21
+ lines.push(` Deployer flags: ${r.deployerFlags.join(", ")}`);
22
+ }
23
+ if (r.tier) lines.push(` Tier: ${r.tier}`);
24
+ return lines.join("\n");
25
+ }
26
+ async function analyzeToken(mint, opts = {}) {
27
+ const url = `${opts.baseUrl ?? "https://ricomaps.fun"}/api/v1/analyze`;
28
+ let res;
29
+ try {
30
+ res = await fetch(url, {
31
+ method: "POST",
32
+ headers: { "Content-Type": "application/json" },
33
+ body: JSON.stringify({ mint, apiKey: opts.apiKey ?? "" }),
34
+ signal: AbortSignal.timeout(15e3)
35
+ });
36
+ } catch (err) {
37
+ return { ok: false, kind: "network", error: String(err) };
38
+ }
39
+ if (res.status === 401 || res.status === 403) {
40
+ return { ok: false, kind: "auth", status: res.status, error: "Invalid or missing API key" };
41
+ }
42
+ if (res.status === 429) {
43
+ const retryAfter = res.headers.get("Retry-After");
44
+ return {
45
+ ok: false,
46
+ kind: "rate-limit",
47
+ status: 429,
48
+ error: "Rate limit exceeded",
49
+ retryAfterMs: retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0
50
+ };
51
+ }
52
+ if (res.status === 400) {
53
+ const text = await res.text().catch(() => "bad request");
54
+ return { ok: false, kind: "bad-request", status: 400, error: text };
55
+ }
56
+ if (!res.ok) {
57
+ return { ok: false, kind: "server", status: res.status, error: `Server error ${res.status}` };
58
+ }
59
+ let body;
60
+ try {
61
+ body = await res.json();
62
+ } catch {
63
+ return { ok: false, kind: "server", error: "Invalid JSON response" };
64
+ }
65
+ const s = body.summary;
66
+ const sniperPct = s.totalHolders > 0 && s.sniperCount != null ? s.sniperCount / s.totalHolders : 0;
67
+ const result = {
68
+ mint,
69
+ riskScore: s.riskScore,
70
+ totalHolders: s.totalHolders,
71
+ cabalCount: s.cabalCount,
72
+ snipersDetected: s.snipersDetected,
73
+ sniperPct,
74
+ bundleClustersDetected: s.bundleClustersDetected,
75
+ symbol: body.tokenMetadata?.symbol,
76
+ name: body.tokenMetadata?.name,
77
+ tokenSecurity: body.tokenSecurity,
78
+ deployerFlags: deployerFlagsFrom(body.tokenSecurity),
79
+ tier: body.tier,
80
+ processingMs: body.processingMs
81
+ };
82
+ return { ok: true, result };
83
+ }
84
+
85
+ export {
86
+ deployerFlagsFrom,
87
+ renderRicoText,
88
+ analyzeToken
89
+ };
@@ -7,7 +7,7 @@ import {
7
7
  loadPack,
8
8
  resolveRules,
9
9
  walk
10
- } from "./chunk-A56IF3UX.js";
10
+ } from "./chunk-2Y6UILTZ.js";
11
11
 
12
12
  // src/trustGraph/directory.ts
13
13
  import { readFileSync, existsSync } from "fs";
@@ -0,0 +1,79 @@
1
+ import {
2
+ CANONICAL_BY_MINT,
3
+ CANONICAL_MINTS
4
+ } from "./chunk-2XJORJPQ.js";
5
+
6
+ // src/tokenRegistry.ts
7
+ async function jupiterLookup(mint, baseUrl) {
8
+ const url = `${baseUrl ?? "https://tokens.jup.ag"}/token/${mint}`;
9
+ try {
10
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
11
+ if (res.status === 404) return null;
12
+ if (!res.ok) return null;
13
+ return await res.json();
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ function detectImpersonation(resolvedSymbol) {
19
+ if (!resolvedSymbol) return { impersonation: false };
20
+ const upper = resolvedSymbol.toUpperCase();
21
+ const canonical = CANONICAL_MINTS[upper];
22
+ if (!canonical) return { impersonation: false };
23
+ return { impersonation: true, canonicalMint: canonical.mint };
24
+ }
25
+ async function verifyTokenIdentity(mint, opts = {}) {
26
+ const bundled = CANONICAL_BY_MINT[mint];
27
+ if (bundled) {
28
+ const expectMismatch2 = opts.expectSymbol ? opts.expectSymbol.toUpperCase() !== bundled.symbol.toUpperCase() : void 0;
29
+ return {
30
+ mint,
31
+ status: "verified-canonical",
32
+ symbol: bundled.symbol,
33
+ name: bundled.name,
34
+ source: "bundled",
35
+ impersonation: false,
36
+ expectMismatch: expectMismatch2 ?? void 0
37
+ };
38
+ }
39
+ if (opts.offline) {
40
+ const { impersonation: impersonation2, canonicalMint: canonicalMint2 } = detectImpersonation(opts.claimedSymbol);
41
+ return {
42
+ mint,
43
+ status: "unverified",
44
+ source: "none",
45
+ impersonation: impersonation2,
46
+ canonicalMint: canonicalMint2,
47
+ detail: "offline mode \u2014 Jupiter lookup skipped"
48
+ };
49
+ }
50
+ const jup = await jupiterLookup(mint, opts.baseUrl);
51
+ if (!jup) {
52
+ const { impersonation: impersonation2, canonicalMint: canonicalMint2 } = detectImpersonation(opts.claimedSymbol);
53
+ return {
54
+ mint,
55
+ status: "unknown",
56
+ source: "none",
57
+ impersonation: impersonation2,
58
+ canonicalMint: canonicalMint2
59
+ };
60
+ }
61
+ const resolvedSymbol = jup.symbol ?? opts.claimedSymbol;
62
+ const { impersonation, canonicalMint } = detectImpersonation(resolvedSymbol);
63
+ const expectMismatch = opts.expectSymbol ? opts.expectSymbol.toUpperCase() !== (resolvedSymbol ?? "").toUpperCase() : void 0;
64
+ const status = impersonation ? "unverified" : "verified";
65
+ return {
66
+ mint,
67
+ status,
68
+ symbol: jup.symbol,
69
+ name: jup.name,
70
+ source: "jupiter",
71
+ impersonation,
72
+ canonicalMint,
73
+ expectMismatch: expectMismatch ?? void 0
74
+ };
75
+ }
76
+
77
+ export {
78
+ verifyTokenIdentity
79
+ };
package/dist/cli.js CHANGED
@@ -17,12 +17,13 @@ import {
17
17
  submitTelemetry,
18
18
  telemetryFilePath,
19
19
  validatePack
20
- } from "./chunk-ZZ6LBZV5.js";
20
+ } from "./chunk-HXQNNGSC.js";
21
21
  import {
22
22
  audit,
23
23
  getChangedRanges,
24
24
  resolveRules
25
- } from "./chunk-A56IF3UX.js";
25
+ } from "./chunk-2Y6UILTZ.js";
26
+ import "./chunk-2XJORJPQ.js";
26
27
  import "./chunk-3RG5ZIWI.js";
27
28
 
28
29
  // src/cli.ts
@@ -119,7 +120,7 @@ if (args[0] === "drift") {
119
120
  process.exit(0);
120
121
  }
121
122
  if (args[0] === "mcp") {
122
- const { startMcpServer } = await import("./mcp-AFYJQ7K6.js");
123
+ const { startMcpServer } = await import("./mcp-FCKMS2MQ.js");
123
124
  await startMcpServer();
124
125
  process.exit(0);
125
126
  }
@@ -143,6 +144,10 @@ if (args[0] === "watch") {
143
144
  await new Promise(() => {
144
145
  });
145
146
  }
147
+ if (args[0] === "rico") {
148
+ await runRico(args.slice(1));
149
+ process.exit(0);
150
+ }
146
151
  if (args[0] === "fix") {
147
152
  await runFix(args.slice(1));
148
153
  process.exit(0);
@@ -510,7 +515,7 @@ async function runDiff(argv) {
510
515
  console.error(" e.g.: brainblast diff lodash@4.17.20 lodash@4.17.21");
511
516
  process.exit(2);
512
517
  }
513
- const { diffVersions, renderDiffText, renderDiffMd, riskScore } = await import("./diff-KIR4PCBC.js");
518
+ const { diffVersions, renderDiffText, renderDiffMd: _renderDiffMd, riskScore } = await import("./diff-KIR4PCBC.js");
514
519
  let result;
515
520
  try {
516
521
  result = await diffVersions(ecosystem, pkgName, fromVersion, toVersion);
@@ -538,3 +543,80 @@ Risk profile unchanged (${result.unchanged.length} advisory${result.unchanged.le
538
543
  console.log("\nNo known advisories for either version.");
539
544
  }
540
545
  }
546
+ async function runRico(argv) {
547
+ const { analyzeToken, renderRicoText } = await import("./ricomaps-WTMWBBOY.js");
548
+ const { verifyTokenIdentity } = await import("./tokenRegistry-CYIUZHAZ.js");
549
+ const mint = argv.find((a) => !a.startsWith("--"));
550
+ if (!mint) {
551
+ console.error("usage: brainblast rico <mint> [--expect SYMBOL] [--api-key KEY] [--fail-on SCORE] [--offline] [--json]");
552
+ process.exit(2);
553
+ }
554
+ const expectIdx = argv.indexOf("--expect");
555
+ const expectSymbol = expectIdx >= 0 ? argv[expectIdx + 1] : void 0;
556
+ const apiKeyIdx = argv.indexOf("--api-key");
557
+ let apiKey = apiKeyIdx >= 0 ? argv[apiKeyIdx + 1] : void 0;
558
+ const failOnIdx = argv.indexOf("--fail-on");
559
+ const failOn = failOnIdx >= 0 ? parseInt(argv[failOnIdx + 1], 10) : 70;
560
+ const offline = argv.includes("--offline");
561
+ const jsonOut = argv.includes("--json");
562
+ let ricoResult = null;
563
+ if (!offline) {
564
+ ricoResult = await analyzeToken(mint, { apiKey });
565
+ if (!ricoResult.ok && ricoResult.kind === "auth") {
566
+ const readline = await import("readline");
567
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
568
+ const answer = await new Promise((resolve) => {
569
+ rl.question(
570
+ "\nRico Maps API key missing or invalid.\n [s] Skip quality scan\n [k] Enter API key\nChoice: ",
571
+ resolve
572
+ );
573
+ });
574
+ rl.close();
575
+ if (answer.trim().toLowerCase().startsWith("k")) {
576
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stderr });
577
+ apiKey = await new Promise((resolve) => {
578
+ rl2.question("API key: ", resolve);
579
+ });
580
+ rl2.close();
581
+ ricoResult = await analyzeToken(mint, { apiKey });
582
+ } else {
583
+ ricoResult = null;
584
+ }
585
+ }
586
+ }
587
+ const claimedSymbol = ricoResult?.ok ? ricoResult.result.symbol : void 0;
588
+ const identity = await verifyTokenIdentity(mint, { expectSymbol, claimedSymbol, offline });
589
+ if (jsonOut) {
590
+ console.log(JSON.stringify({ identity, quality: ricoResult?.ok ? ricoResult.result : null }, null, 2));
591
+ } else {
592
+ const impTag = identity.impersonation ? " \u26A0 IMPERSONATION" : "";
593
+ const expectTag = identity.expectMismatch ? " \u26A0 EXPECT MISMATCH" : "";
594
+ console.log(`
595
+ Identity [${identity.status}]${impTag}${expectTag}`);
596
+ if (identity.symbol) console.log(` Symbol: ${identity.symbol}`);
597
+ if (identity.name) console.log(` Name: ${identity.name}`);
598
+ console.log(` Source: ${identity.source}`);
599
+ if (identity.impersonation && identity.canonicalMint) {
600
+ console.log(` Canonical ${identity.symbol} mint: ${identity.canonicalMint}`);
601
+ console.log(` This token: ${mint}`);
602
+ }
603
+ if (identity.detail) console.log(` Note: ${identity.detail}`);
604
+ if (ricoResult === null) {
605
+ console.log("\nQuality [skipped]");
606
+ } else if (!ricoResult.ok) {
607
+ console.log(`
608
+ Quality [error: ${ricoResult.kind}] ${ricoResult.error}`);
609
+ if (ricoResult.kind === "rate-limit" && ricoResult.retryAfterMs) {
610
+ console.log(` Retry after: ${Math.ceil(ricoResult.retryAfterMs / 1e3)}s`);
611
+ }
612
+ } else {
613
+ console.log(`
614
+ ${renderRicoText(ricoResult.result)}`);
615
+ }
616
+ console.log("");
617
+ }
618
+ const highRisk = ricoResult?.ok && ricoResult.result.riskScore >= failOn;
619
+ if (identity.impersonation || identity.expectMismatch || highRisk) {
620
+ process.exit(1);
621
+ }
622
+ }
package/dist/index.d.ts CHANGED
@@ -555,4 +555,73 @@ declare function checkDrift(dir: string, opts?: {
555
555
  }): Promise<DriftResult>;
556
556
  declare function renderDriftText(result: DriftResult): string;
557
557
 
558
- export { type AccountFlow, type AuditRef, type BuildOpts, type Candidate, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_TTL_HOURS, type DiffResult, type DriftAdvisory, type DriftBaseline, type DriftPackage, type DriftResult, type GraduationEvent, type OnChainProgram, type OsvAdvisory, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type Rule, type RustAccountField, type RustCandidate, type RustChecker, type Severity, type TelemetrySubmitResult, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type WatchEvent, type WatchOptions, analyzeCosts, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, checkDrift, checkerKinds, defaultCachePath, diffVersions, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getRepoHash, getUserHash, getWorkingTreeChanges, initPack, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseDiff, putCacheEntry, queryOsv, rangeChanged, recordGraduationEvents, renderCostReportMd, renderDiffMd, renderDiffText, renderDriftText, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, seedPackages, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest };
558
+ interface CanonicalMint {
559
+ symbol: string;
560
+ name: string;
561
+ mint: string;
562
+ }
563
+ declare const CANONICAL_MINTS: Record<string, CanonicalMint>;
564
+ declare const CANONICAL_BY_MINT: Record<string, CanonicalMint>;
565
+ declare function canonicalMintForSymbol(symbol: string): CanonicalMint | undefined;
566
+ declare function isCanonicalMint(mint: string): boolean;
567
+
568
+ type IdentityStatus = "verified-canonical" | "verified" | "unverified" | "unknown";
569
+ interface TokenIdentity {
570
+ mint: string;
571
+ status: IdentityStatus;
572
+ symbol?: string;
573
+ name?: string;
574
+ source: "bundled" | "jupiter" | "none";
575
+ impersonation: boolean;
576
+ canonicalMint?: string;
577
+ expectMismatch?: boolean;
578
+ detail?: string;
579
+ }
580
+ interface VerifyOpts {
581
+ expectSymbol?: string;
582
+ claimedSymbol?: string;
583
+ baseUrl?: string;
584
+ offline?: boolean;
585
+ }
586
+ declare function verifyTokenIdentity(mint: string, opts?: VerifyOpts): Promise<TokenIdentity>;
587
+
588
+ interface RicoTokenSecurity {
589
+ hasMintAuthority: boolean;
590
+ hasFreezeAuthority: boolean;
591
+ isMutable: boolean;
592
+ riskLevel?: string;
593
+ riskFactors?: string[];
594
+ }
595
+ interface RicoResult {
596
+ mint: string;
597
+ riskScore: number;
598
+ totalHolders: number;
599
+ cabalCount: number;
600
+ snipersDetected: boolean;
601
+ sniperPct: number;
602
+ bundleClustersDetected: boolean;
603
+ symbol?: string;
604
+ name?: string;
605
+ tokenSecurity?: RicoTokenSecurity;
606
+ deployerFlags: string[];
607
+ tier?: string;
608
+ processingMs?: number;
609
+ }
610
+ type RicoOutcome = {
611
+ ok: true;
612
+ result: RicoResult;
613
+ } | {
614
+ ok: false;
615
+ kind: "auth" | "rate-limit" | "bad-request" | "server" | "network";
616
+ status?: number;
617
+ error: string;
618
+ retryAfterMs?: number;
619
+ };
620
+ declare function deployerFlagsFrom(sec?: RicoTokenSecurity): string[];
621
+ declare function renderRicoText(r: RicoResult): string;
622
+ declare function analyzeToken(mint: string, opts?: {
623
+ apiKey?: string;
624
+ baseUrl?: string;
625
+ }): Promise<RicoOutcome>;
626
+
627
+ export { type AccountFlow, type AuditRef, type BuildOpts, CANONICAL_BY_MINT, CANONICAL_MINTS, type Candidate, type CanonicalMint, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_TTL_HOURS, type DiffResult, type DriftAdvisory, type DriftBaseline, type DriftPackage, type DriftResult, type GraduationEvent, type IdentityStatus, type OnChainProgram, type OsvAdvisory, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type RicoOutcome, type RicoResult, type RicoTokenSecurity, type Rule, type RustAccountField, type RustCandidate, type RustChecker, type Severity, type TelemetrySubmitResult, type TokenIdentity, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type VerifyOpts, type WatchEvent, type WatchOptions, analyzeCosts, analyzeToken, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, canonicalMintForSymbol, checkDrift, checkerKinds, defaultCachePath, deployerFlagsFrom, diffVersions, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getRepoHash, getUserHash, getWorkingTreeChanges, initPack, isCanonicalMint, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseDiff, putCacheEntry, queryOsv, rangeChanged, recordGraduationEvents, renderCostReportMd, renderDiffMd, renderDiffText, renderDriftText, renderRicoText, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, seedPackages, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest, verifyTokenIdentity };
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  submitTelemetry,
32
32
  telemetryFilePath,
33
33
  validatePack
34
- } from "./chunk-ZZ6LBZV5.js";
34
+ } from "./chunk-HXQNNGSC.js";
35
35
  import {
36
36
  PACK_MANIFEST_FILE,
37
37
  audit,
@@ -52,7 +52,7 @@ import {
52
52
  runChecker,
53
53
  testKinds,
54
54
  validatePackManifest
55
- } from "./chunk-A56IF3UX.js";
55
+ } from "./chunk-2Y6UILTZ.js";
56
56
  import {
57
57
  diffVersions,
58
58
  queryOsv,
@@ -65,6 +65,20 @@ import {
65
65
  renderDriftText,
66
66
  seedPackages
67
67
  } from "./chunk-EAKU3L7F.js";
68
+ import {
69
+ analyzeToken,
70
+ deployerFlagsFrom,
71
+ renderRicoText
72
+ } from "./chunk-FQA5BYWW.js";
73
+ import {
74
+ verifyTokenIdentity
75
+ } from "./chunk-VI2JBH2T.js";
76
+ import {
77
+ CANONICAL_BY_MINT,
78
+ CANONICAL_MINTS,
79
+ canonicalMintForSymbol,
80
+ isCanonicalMint
81
+ } from "./chunk-2XJORJPQ.js";
68
82
  import "./chunk-3RG5ZIWI.js";
69
83
 
70
84
  // src/generate.ts
@@ -81,10 +95,13 @@ function generateTestForResult(result, rule, outPath) {
81
95
  return outPath;
82
96
  }
83
97
  export {
98
+ CANONICAL_BY_MINT,
99
+ CANONICAL_MINTS,
84
100
  DEFAULT_REGISTRY_URL,
85
101
  DEFAULT_TTL_HOURS,
86
102
  PACK_MANIFEST_FILE,
87
103
  analyzeCosts,
104
+ analyzeToken,
88
105
  applyDiffToFile,
89
106
  audit,
90
107
  auditWithRule,
@@ -93,9 +110,11 @@ export {
93
110
  buildTrustGraph,
94
111
  rules as bundledRules,
95
112
  cacheSize,
113
+ canonicalMintForSymbol,
96
114
  checkDrift,
97
115
  checkerKinds,
98
116
  defaultCachePath,
117
+ deployerFlagsFrom,
99
118
  diffVersions,
100
119
  fileChanged,
101
120
  findCandidates,
@@ -108,6 +127,7 @@ export {
108
127
  getUserHash,
109
128
  getWorkingTreeChanges,
110
129
  initPack,
130
+ isCanonicalMint,
111
131
  isEntryExpired,
112
132
  isTelemetryEnabled,
113
133
  isValidSolanaAddress,
@@ -126,6 +146,7 @@ export {
126
146
  renderDiffMd,
127
147
  renderDiffText,
128
148
  renderDriftText,
149
+ renderRicoText,
129
150
  renderTest,
130
151
  renderTrustGraphMd,
131
152
  rentExemptMinimum,
@@ -140,5 +161,6 @@ export {
140
161
  telemetryFilePath,
141
162
  testKinds,
142
163
  validatePack,
143
- validatePackManifest
164
+ validatePackManifest,
165
+ verifyTokenIdentity
144
166
  };
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  audit,
3
3
  resolveRules
4
- } from "./chunk-A56IF3UX.js";
4
+ } from "./chunk-2Y6UILTZ.js";
5
5
  import {
6
6
  diffVersions,
7
7
  queryOsv
8
8
  } from "./chunk-SC6RNNDW.js";
9
+ import "./chunk-2XJORJPQ.js";
9
10
  import "./chunk-3RG5ZIWI.js";
10
11
 
11
12
  // src/mcp.ts
@@ -0,0 +1,11 @@
1
+ import {
2
+ analyzeToken,
3
+ deployerFlagsFrom,
4
+ renderRicoText
5
+ } from "./chunk-FQA5BYWW.js";
6
+ import "./chunk-3RG5ZIWI.js";
7
+ export {
8
+ analyzeToken,
9
+ deployerFlagsFrom,
10
+ renderRicoText
11
+ };
@@ -0,0 +1,17 @@
1
+ id: solana-token-impersonation
2
+ severity: critical
3
+ title: Hardcoded Solana mint address doesn't match the named token's canonical address
4
+ component:
5
+ name: "@solana/web3.js / @solana/spl-token"
6
+ type: SDK
7
+ version: any
8
+ sourceUrl: https://github.com/solana-labs/solana-web3.js
9
+ detect:
10
+ modules: ["@solana/web3.js", "@solana/spl-token"]
11
+ nameRegex: "mint|payment|usdc|usdt|swap|pay|deposit|withdraw|treasury|stablecoin"
12
+ triggerCalls: []
13
+ requiresImport: true
14
+ check:
15
+ kind: solana-mint-identity-mismatch
16
+ test:
17
+ kind: none
@@ -0,0 +1,8 @@
1
+ import {
2
+ verifyTokenIdentity
3
+ } from "./chunk-VI2JBH2T.js";
4
+ import "./chunk-2XJORJPQ.js";
5
+ import "./chunk-3RG5ZIWI.js";
6
+ export {
7
+ verifyTokenIdentity
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainblast",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "type": "module",
5
5
  "description": "Deterministic auditor for catastrophic AI-integration bugs: scan a repo, find the silent money/auth traps, and generate the behavioral test that proves they're fixed.",
6
6
  "keywords": [