brainblast 0.6.3 → 0.7.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.
@@ -1,3 +1,10 @@
1
+ import {
2
+ CANONICAL_MINTS
3
+ } from "./chunk-2XJORJPQ.js";
4
+ import {
5
+ toSnakeCase
6
+ } from "./chunk-O5Z4ZJHC.js";
7
+
1
8
  // src/finder.ts
2
9
  import { Project, SyntaxKind } from "ts-morph";
3
10
 
@@ -789,6 +796,123 @@ var forbiddenCallReplacement = (c, p) => {
789
796
  };
790
797
  };
791
798
 
799
+ // src/checkers/solanaMintIdentity.ts
800
+ import { Node, SyntaxKind as SyntaxKind11 } from "ts-morph";
801
+ var BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
802
+ function tokenize(name) {
803
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").split(/[_\s]+/).map((t) => t.toUpperCase()).filter(Boolean);
804
+ }
805
+ function symbolFromName(name) {
806
+ return tokenize(name).find((t) => t in CANONICAL_MINTS);
807
+ }
808
+ function addressFromInitializer(init) {
809
+ if (Node.isStringLiteral(init)) {
810
+ return init.getLiteralValue();
811
+ }
812
+ if (Node.isNewExpression(init)) {
813
+ const args = init.getArguments();
814
+ if (args.length > 0 && Node.isStringLiteral(args[0])) {
815
+ return args[0].getLiteralValue?.() ?? args[0].getText().replace(/['"]/g, "");
816
+ }
817
+ }
818
+ return void 0;
819
+ }
820
+ var solanaMintIdentity = (c, _params) => {
821
+ const sf = c.fn.getSourceFile?.();
822
+ if (!sf) return { result: "cant_tell", detail: "Could not access source file" };
823
+ let foundWrong = false;
824
+ let foundRight = false;
825
+ for (const vd of sf.getDescendantsOfKind(SyntaxKind11.VariableDeclaration)) {
826
+ const sym = symbolFromName(vd.getName());
827
+ if (!sym) continue;
828
+ const init = vd.getInitializer();
829
+ if (!init) continue;
830
+ const addr = addressFromInitializer(init);
831
+ if (!addr || !BASE58_RE.test(addr)) continue;
832
+ if (addr === CANONICAL_MINTS[sym].mint) {
833
+ foundRight = true;
834
+ } else {
835
+ foundWrong = true;
836
+ }
837
+ }
838
+ for (const pa of sf.getDescendantsOfKind(SyntaxKind11.PropertyAssignment)) {
839
+ const nameNode = pa.getNameNode();
840
+ const name = Node.isIdentifier(nameNode) ? nameNode.getText() : nameNode.getText().replace(/['"]/g, "");
841
+ const sym = symbolFromName(name);
842
+ if (!sym) continue;
843
+ const init = pa.getInitializer();
844
+ if (!init) continue;
845
+ const addr = addressFromInitializer(init);
846
+ if (!addr || !BASE58_RE.test(addr)) continue;
847
+ if (addr === CANONICAL_MINTS[sym].mint) {
848
+ foundRight = true;
849
+ } else {
850
+ foundWrong = true;
851
+ }
852
+ }
853
+ if (foundWrong) return { result: "fail", detail: "Mint constant has wrong address for its symbol" };
854
+ if (foundRight) return { result: "pass", detail: "All symbol-named mint constants have canonical addresses" };
855
+ return { result: "cant_tell", detail: "No symbol-named mint constants found" };
856
+ };
857
+
858
+ // src/checkers/anchorIdlAccount.ts
859
+ function fieldIsSigner(f) {
860
+ if (/\bSigner\s*</.test(f.typeName)) return true;
861
+ if (/\bsigner\b/.test(f.attrText)) return true;
862
+ return false;
863
+ }
864
+ function fieldIsMut(f) {
865
+ if (/\bmut\b/.test(f.attrText)) return true;
866
+ if (/\binit\b/.test(f.attrText)) return true;
867
+ if (f.hasInitIfNeeded) return true;
868
+ return false;
869
+ }
870
+ function anchorIdlAccount(c, params) {
871
+ const handlerName = toSnakeCase(c.fnName);
872
+ const spec = params?.instructions?.find((i) => i.name === handlerName);
873
+ if (!spec) {
874
+ return {
875
+ result: "cant_tell",
876
+ detail: `Handler '${c.fnName}' is not declared in the ${params?.idlName ?? "IDL"}; constraint rule does not apply.`
877
+ };
878
+ }
879
+ if (spec.signers.length === 0 && spec.mutable.length === 0) {
880
+ return {
881
+ result: "cant_tell",
882
+ detail: `Instruction '${spec.name}' declares no signer or mutable accounts in the IDL; nothing to verify.`
883
+ };
884
+ }
885
+ const byName = /* @__PURE__ */ new Map();
886
+ for (const f of c.accountFields) byName.set(toSnakeCase(f.name), f);
887
+ const violations = [];
888
+ for (const acct of spec.signers) {
889
+ const f = byName.get(acct);
890
+ if (!f) {
891
+ violations.push(`'${acct}' (IDL signer) is not present in the Accounts struct`);
892
+ } else if (!fieldIsSigner(f)) {
893
+ violations.push(`'${acct}' must be a Signer (IDL marks it isSigner) but the Rust field has no signer constraint`);
894
+ }
895
+ }
896
+ for (const acct of spec.mutable) {
897
+ const f = byName.get(acct);
898
+ if (!f) {
899
+ violations.push(`'${acct}' (IDL mutable) is not present in the Accounts struct`);
900
+ } else if (!fieldIsMut(f)) {
901
+ violations.push(`'${acct}' must be mutable (IDL marks it isMut) but the Rust field has no mut/init constraint`);
902
+ }
903
+ }
904
+ if (violations.length > 0) {
905
+ return {
906
+ result: "fail",
907
+ detail: `Handler '${c.fnName}' diverges from the ${params.idlName} IDL: ` + violations.join("; ") + ". A missing signer/mut constraint is a silent authorization hole."
908
+ };
909
+ }
910
+ return {
911
+ result: "pass",
912
+ detail: `Handler '${c.fnName}' declares all ${spec.signers.length} signer and ${spec.mutable.length} mutable account(s) the ${params.idlName} IDL requires.`
913
+ };
914
+ }
915
+
792
916
  // src/checkers/index.ts
793
917
  var registry = {
794
918
  "positional-arg-identity": positionalArgIdentity,
@@ -801,7 +925,9 @@ var registry = {
801
925
  "env-secrets-committed": envSecretsCommitted,
802
926
  "taint-to-sink": taintToSink,
803
927
  "literal-multiplier-wrong-constant": literalMultiplierWrongConstant,
804
- "forbidden-call-replacement": forbiddenCallReplacement
928
+ "forbidden-call-replacement": forbiddenCallReplacement,
929
+ "solana-mint-identity-mismatch": solanaMintIdentity,
930
+ "anchor-account-matches-idl": anchorIdlAccount
805
931
  };
806
932
  function runChecker(kind, c, params) {
807
933
  const fn = registry[kind];
@@ -1017,7 +1143,7 @@ function findRustCandidates(targetDir, rule) {
1017
1143
  }
1018
1144
 
1019
1145
  // src/fixers/positionalArgIdentity.ts
1020
- import { SyntaxKind as SyntaxKind11 } from "ts-morph";
1146
+ import { SyntaxKind as SyntaxKind12 } from "ts-morph";
1021
1147
 
1022
1148
  // src/fixers/diffUtil.ts
1023
1149
  function buildDiff(node, replacement) {
@@ -1042,9 +1168,9 @@ function buildDiff(node, replacement) {
1042
1168
  // src/fixers/positionalArgIdentity.ts
1043
1169
  var fixPositionalArgIdentity = (c, p, outcome) => {
1044
1170
  if (outcome.result !== "fail") return void 0;
1045
- const calls = c.fn.getDescendantsOfKind(SyntaxKind11.CallExpression).filter((call) => {
1171
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression).filter((call) => {
1046
1172
  const exp = call.getExpression();
1047
- return exp.getKind() === SyntaxKind11.PropertyAccessExpression && exp.asKind(SyntaxKind11.PropertyAccessExpression).getName() === p.call;
1173
+ return exp.getKind() === SyntaxKind12.PropertyAccessExpression && exp.asKind(SyntaxKind12.PropertyAccessExpression).getName() === p.call;
1048
1174
  });
1049
1175
  if (calls.length === 0) {
1050
1176
  const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
@@ -1059,7 +1185,7 @@ Do not call JSON.parse() on the body before this verification step.`
1059
1185
  }
1060
1186
  const arg = calls[0].getArguments()[p.argIndex];
1061
1187
  const wantParam = c.params[p.paramIndex];
1062
- if (arg && wantParam && arg.getKind() === SyntaxKind11.CallExpression) {
1188
+ if (arg && wantParam && arg.getKind() === SyntaxKind12.CallExpression) {
1063
1189
  return {
1064
1190
  summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
1065
1191
  diff: buildDiff(arg, wantParam)
@@ -1069,12 +1195,12 @@ Do not call JSON.parse() on the body before this verification step.`
1069
1195
  };
1070
1196
 
1071
1197
  // src/fixers/requiredCallWithOptions.ts
1072
- import { SyntaxKind as SyntaxKind12 } from "ts-morph";
1198
+ import { SyntaxKind as SyntaxKind13 } from "ts-morph";
1073
1199
  function callName6(call) {
1074
1200
  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();
1201
+ if (exp.getKind() === SyntaxKind13.Identifier) return exp.getText();
1202
+ if (exp.getKind() === SyntaxKind13.PropertyAccessExpression) {
1203
+ return exp.asKind(SyntaxKind13.PropertyAccessExpression).getName();
1078
1204
  }
1079
1205
  return "";
1080
1206
  }
@@ -1092,15 +1218,15 @@ function placeholderFor(propName) {
1092
1218
  }
1093
1219
  var fixRequiredCallWithOptions = (c, p, outcome) => {
1094
1220
  if (outcome.result !== "fail") return void 0;
1095
- const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression);
1221
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression);
1096
1222
  const verify = calls.filter((x) => p.verifyCalls.includes(callName6(x)));
1097
1223
  if (verify.length > 0) {
1098
1224
  const call = verify[0];
1099
1225
  const args = call.getArguments();
1100
1226
  const lastArg = args[args.length - 1];
1101
- const obj = lastArg?.asKind(SyntaxKind12.ObjectLiteralExpression);
1227
+ const obj = lastArg?.asKind(SyntaxKind13.ObjectLiteralExpression);
1102
1228
  const presentNames = obj ? obj.getProperties().map((pr) => {
1103
- const pa = pr.asKind(SyntaxKind12.PropertyAssignment) ?? pr.asKind(SyntaxKind12.ShorthandPropertyAssignment);
1229
+ const pa = pr.asKind(SyntaxKind13.PropertyAssignment) ?? pr.asKind(SyntaxKind13.ShorthandPropertyAssignment);
1104
1230
  return pa?.getName() ?? "";
1105
1231
  }) : [];
1106
1232
  const missingGroups = p.requiredProps.filter(
@@ -1134,22 +1260,22 @@ JWKS must come from Privy's published JWKS endpoint for your app.`
1134
1260
  };
1135
1261
 
1136
1262
  // src/fixers/literalMultiplierWrongConstant.ts
1137
- import { SyntaxKind as SyntaxKind13 } from "ts-morph";
1263
+ import { SyntaxKind as SyntaxKind14 } from "ts-morph";
1138
1264
  function callName7(call) {
1139
1265
  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();
1266
+ if (exp.getKind() === SyntaxKind14.Identifier) return exp.getText();
1267
+ if (exp.getKind() === SyntaxKind14.PropertyAccessExpression) {
1268
+ return exp.asKind(SyntaxKind14.PropertyAccessExpression).getName();
1143
1269
  }
1144
1270
  return "";
1145
1271
  }
1146
1272
  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);
1273
+ if (node.getKind() === SyntaxKind14.Identifier && node.getText() === name) return node;
1274
+ return node.getDescendantsOfKind(SyntaxKind14.Identifier).find((id) => id.getText() === name);
1149
1275
  }
1150
1276
  var fixLiteralMultiplierWrongConstant = (c, p, outcome) => {
1151
1277
  if (outcome.result !== "fail") return void 0;
1152
- const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression).filter((call) => callName7(call) === p.call);
1278
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind14.CallExpression).filter((call) => callName7(call) === p.call);
1153
1279
  if (calls.length === 0) return void 0;
1154
1280
  const arg = calls[0].getArguments()[p.argIndex];
1155
1281
  if (!arg) return void 0;
@@ -0,0 +1,111 @@
1
+ import {
2
+ DEFAULT_RPC,
3
+ probeUpgradeAuthority
4
+ } from "./chunk-XSVQSK53.js";
5
+
6
+ // src/watchChain.ts
7
+ async function getSignaturesForAddress(programId, until, opts) {
8
+ const url = opts.rpcUrl ?? DEFAULT_RPC;
9
+ const fetchImpl = opts.fetchImpl ?? fetch;
10
+ const params = [programId, { limit: opts.limit ?? 25 }];
11
+ if (until) params[1].until = until;
12
+ const ac = new AbortController();
13
+ const t = setTimeout(() => ac.abort(), opts.timeoutMs ?? 1e4);
14
+ try {
15
+ const res = await fetchImpl(url, {
16
+ method: "POST",
17
+ headers: { "content-type": "application/json" },
18
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getSignaturesForAddress", params }),
19
+ signal: ac.signal
20
+ });
21
+ if (!res.ok) throw new Error(`getSignaturesForAddress: HTTP ${res.status}`);
22
+ const body = await res.json();
23
+ if (body.error) throw new Error(`getSignaturesForAddress: ${body.error.message}`);
24
+ return body.result ?? [];
25
+ } finally {
26
+ clearTimeout(t);
27
+ }
28
+ }
29
+ async function pollChainOnce(programId, state, opts = {}) {
30
+ const events = [];
31
+ const ts = () => (/* @__PURE__ */ new Date()).toISOString();
32
+ let sigs;
33
+ try {
34
+ sigs = await getSignaturesForAddress(programId, state.lastSignature, opts);
35
+ } catch (e) {
36
+ events.push({ type: "poll_error", programId, message: e?.message ?? String(e), ts: ts() });
37
+ return { events, state };
38
+ }
39
+ const probe = opts.probeAuthority ?? ((id, o) => probeUpgradeAuthority(id, o).then((ua) => ({ address: ua.address })));
40
+ let currentAuthority = state.baselineAuthority;
41
+ try {
42
+ const ua = await probe(programId, opts);
43
+ currentAuthority = ua.address;
44
+ } catch (e) {
45
+ events.push({ type: "poll_error", programId, message: `authority probe: ${e?.message ?? String(e)}`, ts: ts() });
46
+ }
47
+ if (!state.initialized) {
48
+ const head = sigs[0]?.signature ?? null;
49
+ events.push({ type: "watch_started", programId, headSignature: head, baselineAuthority: currentAuthority, ts: ts() });
50
+ return {
51
+ events,
52
+ state: { lastSignature: head, baselineAuthority: currentAuthority, initialized: true }
53
+ };
54
+ }
55
+ if (sigs.length > 0) {
56
+ events.push({
57
+ type: "new_activity",
58
+ programId,
59
+ newCount: sigs.length,
60
+ signatures: sigs.slice(0, 10).map((s) => s.signature),
61
+ ts: ts()
62
+ });
63
+ }
64
+ if (currentAuthority !== state.baselineAuthority) {
65
+ events.push({
66
+ type: "authority_changed",
67
+ programId,
68
+ from: state.baselineAuthority,
69
+ to: currentAuthority,
70
+ ts: ts()
71
+ });
72
+ }
73
+ return {
74
+ events,
75
+ state: {
76
+ lastSignature: sigs[0]?.signature ?? state.lastSignature,
77
+ baselineAuthority: currentAuthority,
78
+ initialized: true
79
+ }
80
+ };
81
+ }
82
+ function initialChainWatchState() {
83
+ return { lastSignature: null, baselineAuthority: null, initialized: false };
84
+ }
85
+ function startChainWatch(programId, opts = {}) {
86
+ const emit = opts.emit ?? ((e) => process.stdout.write(JSON.stringify(e) + "\n"));
87
+ const intervalMs = opts.intervalMs ?? 3e4;
88
+ let state = initialChainWatchState();
89
+ let stopped = false;
90
+ let timer;
91
+ const tick = async () => {
92
+ if (stopped) return;
93
+ const { events, state: next } = await pollChainOnce(programId, state, opts);
94
+ state = next;
95
+ for (const e of events) emit(e);
96
+ if (!stopped) timer = setTimeout(tick, intervalMs);
97
+ };
98
+ void tick();
99
+ return {
100
+ stop: () => {
101
+ stopped = true;
102
+ if (timer) clearTimeout(timer);
103
+ }
104
+ };
105
+ }
106
+
107
+ export {
108
+ pollChainOnce,
109
+ initialChainWatchState,
110
+ startChainWatch
111
+ };
@@ -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
+ };