brainblast 0.6.4 → 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.
- package/dist/batchScan-JR2G5JCF.js +14 -0
- package/dist/chunk-2UZGWXIX.js +77 -0
- package/dist/{chunk-HXQNNGSC.js → chunk-34VXOLJF.js} +32 -433
- package/dist/{chunk-2Y6UILTZ.js → chunk-CRYFCQYM.js} +63 -1
- package/dist/chunk-DQ4KAYKQ.js +111 -0
- package/dist/chunk-HL7NVANZ.js +331 -0
- package/dist/chunk-O5Z4ZJHC.js +89 -0
- package/dist/chunk-QC27GNQ7.js +101 -0
- package/dist/chunk-SVSVVW6U.js +187 -0
- package/dist/chunk-UWE6HAGS.js +176 -0
- package/dist/chunk-VG5FMOLW.js +61 -0
- package/dist/chunk-WX3IR7LK.js +148 -0
- package/dist/chunk-XSVQSK53.js +100 -0
- package/dist/cli.js +238 -9
- package/dist/firewall-HN5XJLGC.js +18 -0
- package/dist/idlRules-3KZML4NL.js +17 -0
- package/dist/index.d.ts +238 -1
- package/dist/index.js +105 -34
- package/dist/{mcp-FCKMS2MQ.js → mcp-ML2X44WE.js} +3 -2
- package/dist/pumpCheck-K2ESOBNU.js +16 -0
- package/dist/rpc-W5F4KXS2.js +18 -0
- package/dist/score-VLKER37D.js +18 -0
- package/dist/trustGraph-4SSJOQKT.js +49 -0
- package/dist/watchChain-F6INXAPA.js +13 -0
- package/package.json +2 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
batchScan,
|
|
3
|
+
parseMintList,
|
|
4
|
+
renderBatchText
|
|
5
|
+
} from "./chunk-QC27GNQ7.js";
|
|
6
|
+
import "./chunk-FQA5BYWW.js";
|
|
7
|
+
import "./chunk-VI2JBH2T.js";
|
|
8
|
+
import "./chunk-2XJORJPQ.js";
|
|
9
|
+
import "./chunk-3RG5ZIWI.js";
|
|
10
|
+
export {
|
|
11
|
+
batchScan,
|
|
12
|
+
parseMintList,
|
|
13
|
+
renderBatchText
|
|
14
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/trustGraph/render.ts
|
|
2
|
+
function renderAuthority(p) {
|
|
3
|
+
const a = p.upgradeAuthority;
|
|
4
|
+
switch (a.kind) {
|
|
5
|
+
case "renounced":
|
|
6
|
+
return "\u{1F512} **Renounced** \u2014 program is frozen; no key can upgrade it.";
|
|
7
|
+
case "single-key":
|
|
8
|
+
return `\u26A0\uFE0F **Single key** \`${a.address}\` \u2014 one private key can replace this program at any time.`;
|
|
9
|
+
case "multisig":
|
|
10
|
+
return `\u{1F510} **Multisig** \`${a.address}\` \u2014 a threshold of signers can upgrade.`;
|
|
11
|
+
case "dao":
|
|
12
|
+
return `\u{1F3DB} **DAO** \`${a.address}\` \u2014 governance program controls upgrades.`;
|
|
13
|
+
case "unknown":
|
|
14
|
+
return a.address ? `\u2753 **Unclassified authority** \`${a.address}\` \u2014 needs research to confirm single-key vs multisig/DAO.` : "\u2753 **Unknown** \u2014 could not determine upgrade authority.";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function renderVerified(p) {
|
|
18
|
+
const v = p.verifiedBuild;
|
|
19
|
+
switch (v.state) {
|
|
20
|
+
case "verified":
|
|
21
|
+
return `\u2705 Verified build${v.commit ? ` @ \`${v.commit.slice(0, 12)}\`` : ""} \u2014 [registry](${v.registryUrl})`;
|
|
22
|
+
case "unverified":
|
|
23
|
+
return "\u274C Unverified \u2014 on-chain bytecode does not match any source we trust.";
|
|
24
|
+
case "unknown":
|
|
25
|
+
return "\u2753 Verified-build status not checked.";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function renderAudits(p) {
|
|
29
|
+
if (!p.audits.length) return "_No audits on file._";
|
|
30
|
+
return p.audits.map((a) => `- ${a.firm} (${a.date}) \u2014 [report](${a.reportUrl})${a.auditedCommit ? ` @ \`${a.auditedCommit.slice(0, 12)}\`` : ""}`).join("\n");
|
|
31
|
+
}
|
|
32
|
+
function renderParity(p) {
|
|
33
|
+
const { mainnet, devnet, testnet, notes } = p.parity;
|
|
34
|
+
const cells = [`mainnet=\`${mainnet}\``, `devnet=\`${devnet}\``];
|
|
35
|
+
if (testnet) cells.push(`testnet=\`${testnet}\``);
|
|
36
|
+
return cells.join(" \xB7 ") + (notes ? `
|
|
37
|
+
_${notes}_` : "");
|
|
38
|
+
}
|
|
39
|
+
function renderProgram(p) {
|
|
40
|
+
return [
|
|
41
|
+
`### ${p.name}`,
|
|
42
|
+
"",
|
|
43
|
+
`\`${p.programId}\`${p.kind ? ` \xB7 kind: \`${p.kind}\`` : ""}`,
|
|
44
|
+
"",
|
|
45
|
+
`- **Upgrade authority:** ${renderAuthority(p)}`,
|
|
46
|
+
`- **Verified build:** ${renderVerified(p)}`,
|
|
47
|
+
`- **Parity:** ${renderParity(p)}`,
|
|
48
|
+
`- **Audits:**
|
|
49
|
+
${renderAudits(p).split("\n").map((l) => " " + l).join("\n")}`,
|
|
50
|
+
p.invokes && p.invokes.length ? `- **Invokes (CPI):** ${p.invokes.map((id) => `\`${id}\``).join(", ")}` : ""
|
|
51
|
+
].filter(Boolean).join("\n");
|
|
52
|
+
}
|
|
53
|
+
function renderTrustGraphMd(g) {
|
|
54
|
+
const head = [
|
|
55
|
+
"# Trust Graph",
|
|
56
|
+
"",
|
|
57
|
+
`_Generated ${g.generatedAt}._`,
|
|
58
|
+
"",
|
|
59
|
+
"Every program your code transitively invokes, with the authority that controls it, the build-verification status, and the audits we found.",
|
|
60
|
+
""
|
|
61
|
+
].join("\n");
|
|
62
|
+
const body = g.programs.map(renderProgram).join("\n\n---\n\n");
|
|
63
|
+
const tail = g.unresolved.length ? [
|
|
64
|
+
"",
|
|
65
|
+
"---",
|
|
66
|
+
"",
|
|
67
|
+
"## Unresolved",
|
|
68
|
+
"",
|
|
69
|
+
...g.unresolved.map((u) => `- \`${u.programId}\` \u2014 ${u.reason}`)
|
|
70
|
+
].join("\n") : "";
|
|
71
|
+
return head + body + tail + "\n";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
renderProgram,
|
|
76
|
+
renderTrustGraphMd
|
|
77
|
+
};
|
|
@@ -7,393 +7,7 @@ import {
|
|
|
7
7
|
loadPack,
|
|
8
8
|
resolveRules,
|
|
9
9
|
walk
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
|
|
12
|
-
// src/trustGraph/directory.ts
|
|
13
|
-
import { readFileSync, existsSync } from "fs";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
import { join } from "path";
|
|
16
|
-
import { parse } from "yaml";
|
|
17
|
-
var cache = null;
|
|
18
|
-
function bundledPath() {
|
|
19
|
-
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
20
|
-
const candidates = [
|
|
21
|
-
join(here, "programs", "directory.yaml"),
|
|
22
|
-
// dist/programs/directory.yaml
|
|
23
|
-
join(here, "..", "..", "programs", "directory.yaml"),
|
|
24
|
-
// src/../../programs/
|
|
25
|
-
join(here, "..", "programs", "directory.yaml")
|
|
26
|
-
// fallback
|
|
27
|
-
];
|
|
28
|
-
for (const c of candidates) {
|
|
29
|
-
if (existsSync(c)) return c;
|
|
30
|
-
}
|
|
31
|
-
return candidates[0];
|
|
32
|
-
}
|
|
33
|
-
function loadDirectory(path = bundledPath()) {
|
|
34
|
-
if (cache && path === bundledPath()) return cache;
|
|
35
|
-
const raw = parse(readFileSync(path, "utf8"));
|
|
36
|
-
if (!raw || !Array.isArray(raw.programs)) {
|
|
37
|
-
throw new Error(`invalid program directory at ${path}: missing 'programs' array`);
|
|
38
|
-
}
|
|
39
|
-
const m = /* @__PURE__ */ new Map();
|
|
40
|
-
for (const p of raw.programs) {
|
|
41
|
-
if (!p.programId || !p.name) throw new Error(`directory entry missing programId/name: ${JSON.stringify(p)}`);
|
|
42
|
-
if (m.has(p.programId)) throw new Error(`directory has duplicate programId ${p.programId}`);
|
|
43
|
-
m.set(p.programId, { ...p, provenance: { ...p.provenance ?? {}, directoryFile: path } });
|
|
44
|
-
}
|
|
45
|
-
if (path === bundledPath()) cache = m;
|
|
46
|
-
return m;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/trustGraph/base58.ts
|
|
50
|
-
var ALPHA = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
51
|
-
var MAP = {};
|
|
52
|
-
for (let i = 0; i < ALPHA.length; i++) MAP[ALPHA[i]] = i;
|
|
53
|
-
function base58Encode(bytes) {
|
|
54
|
-
let zeros = 0;
|
|
55
|
-
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
56
|
-
const buf = Array.from(bytes);
|
|
57
|
-
const out = [];
|
|
58
|
-
let start = zeros;
|
|
59
|
-
while (start < buf.length) {
|
|
60
|
-
let rem = 0;
|
|
61
|
-
for (let i = start; i < buf.length; i++) {
|
|
62
|
-
const acc = rem * 256 + buf[i];
|
|
63
|
-
buf[i] = Math.floor(acc / 58);
|
|
64
|
-
rem = acc % 58;
|
|
65
|
-
}
|
|
66
|
-
out.push(rem);
|
|
67
|
-
if (buf[start] === 0) start++;
|
|
68
|
-
}
|
|
69
|
-
let s = "";
|
|
70
|
-
for (let i = 0; i < zeros; i++) s += "1";
|
|
71
|
-
for (let i = out.length - 1; i >= 0; i--) s += ALPHA[out[i]];
|
|
72
|
-
return s;
|
|
73
|
-
}
|
|
74
|
-
function base58Decode(s) {
|
|
75
|
-
let zeros = 0;
|
|
76
|
-
while (zeros < s.length && s[zeros] === "1") zeros++;
|
|
77
|
-
const buf = [];
|
|
78
|
-
for (let i = zeros; i < s.length; i++) {
|
|
79
|
-
const v = MAP[s[i]];
|
|
80
|
-
if (v === void 0) throw new Error(`base58: invalid char '${s[i]}' at ${i}`);
|
|
81
|
-
let carry = v;
|
|
82
|
-
for (let j = 0; j < buf.length; j++) {
|
|
83
|
-
const acc = buf[j] * 58 + carry;
|
|
84
|
-
buf[j] = acc & 255;
|
|
85
|
-
carry = acc >>> 8;
|
|
86
|
-
}
|
|
87
|
-
while (carry > 0) {
|
|
88
|
-
buf.push(carry & 255);
|
|
89
|
-
carry >>>= 8;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const out = new Uint8Array(zeros + buf.length);
|
|
93
|
-
for (let i = 0; i < buf.length; i++) out[zeros + buf.length - 1 - i] = buf[i];
|
|
94
|
-
return out;
|
|
95
|
-
}
|
|
96
|
-
function isValidSolanaAddress(s) {
|
|
97
|
-
if (typeof s !== "string" || s.length < 32 || s.length > 44) return false;
|
|
98
|
-
try {
|
|
99
|
-
return base58Decode(s).length === 32;
|
|
100
|
-
} catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// src/trustGraph/programCache.ts
|
|
106
|
-
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
107
|
-
import { join as join2, dirname } from "path";
|
|
108
|
-
import { homedir } from "os";
|
|
109
|
-
var DEFAULT_TTL_HOURS = 168;
|
|
110
|
-
var SCHEMA_VERSION = "1.0";
|
|
111
|
-
function defaultCachePath() {
|
|
112
|
-
const envOverride = process.env["BRAINBLAST_CACHE_PATH"];
|
|
113
|
-
return envOverride ?? join2(homedir(), ".brainblast", "program-cache.json");
|
|
114
|
-
}
|
|
115
|
-
function emptyCache() {
|
|
116
|
-
return { schemaVersion: SCHEMA_VERSION, entries: {} };
|
|
117
|
-
}
|
|
118
|
-
function loadProgramCache(cachePath) {
|
|
119
|
-
const path = cachePath ?? defaultCachePath();
|
|
120
|
-
if (!existsSync2(path)) return emptyCache();
|
|
121
|
-
try {
|
|
122
|
-
const raw = JSON.parse(readFileSync2(path, "utf8"));
|
|
123
|
-
if (raw?.schemaVersion !== SCHEMA_VERSION) {
|
|
124
|
-
return emptyCache();
|
|
125
|
-
}
|
|
126
|
-
if (!raw.entries || typeof raw.entries !== "object") return emptyCache();
|
|
127
|
-
return { schemaVersion: SCHEMA_VERSION, entries: raw.entries };
|
|
128
|
-
} catch {
|
|
129
|
-
return emptyCache();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function saveProgramCache(cache2, cachePath) {
|
|
133
|
-
const path = cachePath ?? defaultCachePath();
|
|
134
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
135
|
-
writeFileSync(path, JSON.stringify(cache2, null, 2), "utf8");
|
|
136
|
-
}
|
|
137
|
-
function getCacheEntry(cache2, programId, ttlHoursOverride) {
|
|
138
|
-
const entry = cache2.entries[programId];
|
|
139
|
-
if (!entry) return null;
|
|
140
|
-
if (isEntryExpired(entry, ttlHoursOverride)) return null;
|
|
141
|
-
return entry.program;
|
|
142
|
-
}
|
|
143
|
-
function putCacheEntry(cache2, programId, program, sourceRun, ttlHours = DEFAULT_TTL_HOURS) {
|
|
144
|
-
cache2.entries[programId] = {
|
|
145
|
-
program,
|
|
146
|
-
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
147
|
-
sourceRun,
|
|
148
|
-
ttlHours
|
|
149
|
-
};
|
|
150
|
-
return cache2;
|
|
151
|
-
}
|
|
152
|
-
function getCacheEntryMeta(cache2, programId) {
|
|
153
|
-
return cache2.entries[programId] ?? null;
|
|
154
|
-
}
|
|
155
|
-
function isEntryExpired(entry, ttlHoursOverride) {
|
|
156
|
-
const ttl = ttlHoursOverride ?? entry.ttlHours ?? DEFAULT_TTL_HOURS;
|
|
157
|
-
if (ttl <= 0) return true;
|
|
158
|
-
const cachedMs = Date.parse(entry.cachedAt);
|
|
159
|
-
if (Number.isNaN(cachedMs)) return true;
|
|
160
|
-
const ageMs = Date.now() - cachedMs;
|
|
161
|
-
return ageMs >= ttl * 36e5;
|
|
162
|
-
}
|
|
163
|
-
function cacheSize(cache2, ttlHoursOverride) {
|
|
164
|
-
return Object.values(cache2.entries).filter((e) => !isEntryExpired(e, ttlHoursOverride)).length;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/trustGraph/rpc.ts
|
|
168
|
-
var BPF_UPGRADEABLE_LOADER = "BPFLoaderUpgradeab1e11111111111111111111111";
|
|
169
|
-
var BPF_LOADER_2 = "BPFLoader2111111111111111111111111111111111";
|
|
170
|
-
var NATIVE_LOADER = "NativeLoader1111111111111111111111111111111";
|
|
171
|
-
var DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
|
|
172
|
-
async function rpc(method, params, opts) {
|
|
173
|
-
const url = opts.rpcUrl ?? DEFAULT_RPC;
|
|
174
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
175
|
-
const ac = new AbortController();
|
|
176
|
-
const t = setTimeout(() => ac.abort(), opts.timeoutMs ?? 1e4);
|
|
177
|
-
try {
|
|
178
|
-
const res = await fetchImpl(url, {
|
|
179
|
-
method: "POST",
|
|
180
|
-
headers: { "content-type": "application/json" },
|
|
181
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
182
|
-
signal: ac.signal
|
|
183
|
-
});
|
|
184
|
-
if (!res.ok) throw new Error(`rpc ${method}: HTTP ${res.status}`);
|
|
185
|
-
const body = await res.json();
|
|
186
|
-
if (body.error) throw new Error(`rpc ${method}: ${body.error.message}`);
|
|
187
|
-
if (body.result === void 0) throw new Error(`rpc ${method}: empty result`);
|
|
188
|
-
return body.result;
|
|
189
|
-
} finally {
|
|
190
|
-
clearTimeout(t);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function getAccountInfo(address, opts = {}) {
|
|
194
|
-
if (!isValidSolanaAddress(address)) throw new Error(`invalid Solana address: ${address}`);
|
|
195
|
-
const result = await rpc(
|
|
196
|
-
"getAccountInfo",
|
|
197
|
-
[address, { encoding: "base64", commitment: "confirmed" }],
|
|
198
|
-
opts
|
|
199
|
-
);
|
|
200
|
-
if (!result || !result.value) return null;
|
|
201
|
-
const v = result.value;
|
|
202
|
-
const [b64] = v.data;
|
|
203
|
-
return {
|
|
204
|
-
owner: v.owner,
|
|
205
|
-
data: Buffer.from(b64, "base64"),
|
|
206
|
-
executable: v.executable,
|
|
207
|
-
lamports: v.lamports
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
async function probeUpgradeAuthority(programId, opts = {}) {
|
|
211
|
-
const acct = await getAccountInfo(programId, opts);
|
|
212
|
-
if (!acct) {
|
|
213
|
-
return {
|
|
214
|
-
kind: "unknown",
|
|
215
|
-
address: null,
|
|
216
|
-
source: "rpc",
|
|
217
|
-
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (acct.owner === BPF_LOADER_2 || acct.owner === NATIVE_LOADER) {
|
|
221
|
-
return {
|
|
222
|
-
kind: "renounced",
|
|
223
|
-
address: null,
|
|
224
|
-
source: "rpc",
|
|
225
|
-
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
if (acct.owner !== BPF_UPGRADEABLE_LOADER) {
|
|
229
|
-
throw new Error(
|
|
230
|
-
`program ${programId} is owned by ${acct.owner}, not a known loader; not a deployed program?`
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
if (acct.data.length < 36) throw new Error(`program account too small: ${acct.data.length}`);
|
|
234
|
-
const tag = acct.data[0] | acct.data[1] << 8 | acct.data[2] << 16 | acct.data[3] << 24;
|
|
235
|
-
if (tag !== 2) throw new Error(`expected Program (tag=2) state, got tag=${tag}`);
|
|
236
|
-
const programDataAddr = base58Encode(acct.data.subarray(4, 36));
|
|
237
|
-
const pd = await getAccountInfo(programDataAddr, opts);
|
|
238
|
-
if (!pd) {
|
|
239
|
-
throw new Error(`program ${programId} ProgramData ${programDataAddr} not found`);
|
|
240
|
-
}
|
|
241
|
-
if (pd.data.length < 45) throw new Error(`ProgramData account too small: ${pd.data.length}`);
|
|
242
|
-
const pdTag = pd.data[0] | pd.data[1] << 8 | pd.data[2] << 16 | pd.data[3] << 24;
|
|
243
|
-
if (pdTag !== 3) throw new Error(`expected ProgramData (tag=3), got tag=${pdTag}`);
|
|
244
|
-
const optionTag = pd.data[12];
|
|
245
|
-
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
246
|
-
if (optionTag === 0) {
|
|
247
|
-
return { kind: "renounced", address: null, source: "rpc", checkedAt };
|
|
248
|
-
}
|
|
249
|
-
if (optionTag !== 1) throw new Error(`unexpected Option tag in ProgramData: ${optionTag}`);
|
|
250
|
-
const authority = base58Encode(pd.data.subarray(13, 45));
|
|
251
|
-
return { kind: "unknown", address: authority, source: "rpc", checkedAt };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// src/trustGraph/build.ts
|
|
255
|
-
async function buildTrustGraph(programIds, opts = {}) {
|
|
256
|
-
const dir = loadDirectory(opts.directoryPath);
|
|
257
|
-
const programs = [];
|
|
258
|
-
const unresolved = [];
|
|
259
|
-
const cacheEnabled = opts.cachePath !== null;
|
|
260
|
-
const cachePathArg = opts.cachePath === null ? void 0 : opts.cachePath;
|
|
261
|
-
const cache2 = cacheEnabled ? loadProgramCache(cachePathArg) : null;
|
|
262
|
-
const newFromRpc = [];
|
|
263
|
-
const runId = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
264
|
-
const seen = /* @__PURE__ */ new Set();
|
|
265
|
-
const ordered = programIds.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
266
|
-
for (const id of ordered) {
|
|
267
|
-
const directoryHit = dir.get(id);
|
|
268
|
-
if (directoryHit) {
|
|
269
|
-
programs.push(directoryHit);
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (cache2) {
|
|
273
|
-
const cached = getCacheEntry(cache2, id);
|
|
274
|
-
if (cached) {
|
|
275
|
-
const meta = getCacheEntryMeta(cache2, id);
|
|
276
|
-
programs.push({
|
|
277
|
-
...cached,
|
|
278
|
-
provenance: {
|
|
279
|
-
...cached.provenance ?? {},
|
|
280
|
-
notes: [
|
|
281
|
-
cached.provenance?.notes,
|
|
282
|
-
`cache-hit: cachedAt=${meta.cachedAt} sourceRun=${meta.sourceRun}`
|
|
283
|
-
].filter(Boolean).join("; ")
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (opts.probeRpc === false) {
|
|
290
|
-
unresolved.push({
|
|
291
|
-
programId: id,
|
|
292
|
-
reason: "not_in_directory_or_cache_and_rpc_disabled"
|
|
293
|
-
});
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
let authority;
|
|
297
|
-
try {
|
|
298
|
-
authority = await probeUpgradeAuthority(id, opts);
|
|
299
|
-
} catch (e) {
|
|
300
|
-
unresolved.push({ programId: id, reason: `rpc_error: ${e?.message ?? String(e)}` });
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
const probed = {
|
|
304
|
-
programId: id,
|
|
305
|
-
name: `Unknown program (${id.slice(0, 8)}\u2026)`,
|
|
306
|
-
kind: "app",
|
|
307
|
-
upgradeAuthority: authority,
|
|
308
|
-
verifiedBuild: { state: "unknown" },
|
|
309
|
-
audits: [],
|
|
310
|
-
parity: { mainnet: "unknown", devnet: "unknown" },
|
|
311
|
-
provenance: { rpcUrl: opts.rpcUrl, notes: "live-probed; not in curated directory" }
|
|
312
|
-
};
|
|
313
|
-
programs.push(probed);
|
|
314
|
-
newFromRpc.push(id);
|
|
315
|
-
if (cache2) {
|
|
316
|
-
putCacheEntry(cache2, id, probed, runId);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (cache2 && newFromRpc.length > 0) {
|
|
320
|
-
saveProgramCache(cache2, cachePathArg);
|
|
321
|
-
}
|
|
322
|
-
return { programs, unresolved, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/trustGraph/render.ts
|
|
326
|
-
function renderAuthority(p) {
|
|
327
|
-
const a = p.upgradeAuthority;
|
|
328
|
-
switch (a.kind) {
|
|
329
|
-
case "renounced":
|
|
330
|
-
return "\u{1F512} **Renounced** \u2014 program is frozen; no key can upgrade it.";
|
|
331
|
-
case "single-key":
|
|
332
|
-
return `\u26A0\uFE0F **Single key** \`${a.address}\` \u2014 one private key can replace this program at any time.`;
|
|
333
|
-
case "multisig":
|
|
334
|
-
return `\u{1F510} **Multisig** \`${a.address}\` \u2014 a threshold of signers can upgrade.`;
|
|
335
|
-
case "dao":
|
|
336
|
-
return `\u{1F3DB} **DAO** \`${a.address}\` \u2014 governance program controls upgrades.`;
|
|
337
|
-
case "unknown":
|
|
338
|
-
return a.address ? `\u2753 **Unclassified authority** \`${a.address}\` \u2014 needs research to confirm single-key vs multisig/DAO.` : "\u2753 **Unknown** \u2014 could not determine upgrade authority.";
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function renderVerified(p) {
|
|
342
|
-
const v = p.verifiedBuild;
|
|
343
|
-
switch (v.state) {
|
|
344
|
-
case "verified":
|
|
345
|
-
return `\u2705 Verified build${v.commit ? ` @ \`${v.commit.slice(0, 12)}\`` : ""} \u2014 [registry](${v.registryUrl})`;
|
|
346
|
-
case "unverified":
|
|
347
|
-
return "\u274C Unverified \u2014 on-chain bytecode does not match any source we trust.";
|
|
348
|
-
case "unknown":
|
|
349
|
-
return "\u2753 Verified-build status not checked.";
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
function renderAudits(p) {
|
|
353
|
-
if (!p.audits.length) return "_No audits on file._";
|
|
354
|
-
return p.audits.map((a) => `- ${a.firm} (${a.date}) \u2014 [report](${a.reportUrl})${a.auditedCommit ? ` @ \`${a.auditedCommit.slice(0, 12)}\`` : ""}`).join("\n");
|
|
355
|
-
}
|
|
356
|
-
function renderParity(p) {
|
|
357
|
-
const { mainnet, devnet, testnet, notes } = p.parity;
|
|
358
|
-
const cells = [`mainnet=\`${mainnet}\``, `devnet=\`${devnet}\``];
|
|
359
|
-
if (testnet) cells.push(`testnet=\`${testnet}\``);
|
|
360
|
-
return cells.join(" \xB7 ") + (notes ? `
|
|
361
|
-
_${notes}_` : "");
|
|
362
|
-
}
|
|
363
|
-
function renderProgram(p) {
|
|
364
|
-
return [
|
|
365
|
-
`### ${p.name}`,
|
|
366
|
-
"",
|
|
367
|
-
`\`${p.programId}\`${p.kind ? ` \xB7 kind: \`${p.kind}\`` : ""}`,
|
|
368
|
-
"",
|
|
369
|
-
`- **Upgrade authority:** ${renderAuthority(p)}`,
|
|
370
|
-
`- **Verified build:** ${renderVerified(p)}`,
|
|
371
|
-
`- **Parity:** ${renderParity(p)}`,
|
|
372
|
-
`- **Audits:**
|
|
373
|
-
${renderAudits(p).split("\n").map((l) => " " + l).join("\n")}`,
|
|
374
|
-
p.invokes && p.invokes.length ? `- **Invokes (CPI):** ${p.invokes.map((id) => `\`${id}\``).join(", ")}` : ""
|
|
375
|
-
].filter(Boolean).join("\n");
|
|
376
|
-
}
|
|
377
|
-
function renderTrustGraphMd(g) {
|
|
378
|
-
const head = [
|
|
379
|
-
"# Trust Graph",
|
|
380
|
-
"",
|
|
381
|
-
`_Generated ${g.generatedAt}._`,
|
|
382
|
-
"",
|
|
383
|
-
"Every program your code transitively invokes, with the authority that controls it, the build-verification status, and the audits we found.",
|
|
384
|
-
""
|
|
385
|
-
].join("\n");
|
|
386
|
-
const body = g.programs.map(renderProgram).join("\n\n---\n\n");
|
|
387
|
-
const tail = g.unresolved.length ? [
|
|
388
|
-
"",
|
|
389
|
-
"---",
|
|
390
|
-
"",
|
|
391
|
-
"## Unresolved",
|
|
392
|
-
"",
|
|
393
|
-
...g.unresolved.map((u) => `- \`${u.programId}\` \u2014 ${u.reason}`)
|
|
394
|
-
].join("\n") : "";
|
|
395
|
-
return head + body + tail + "\n";
|
|
396
|
-
}
|
|
10
|
+
} from "./chunk-CRYFCQYM.js";
|
|
397
11
|
|
|
398
12
|
// src/costAnalysis.ts
|
|
399
13
|
import { Project, SyntaxKind } from "ts-morph";
|
|
@@ -696,7 +310,7 @@ function startWatch(targetDir, opts = {}) {
|
|
|
696
310
|
}
|
|
697
311
|
|
|
698
312
|
// src/fixers/applyDiff.ts
|
|
699
|
-
import { readFileSync
|
|
313
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
700
314
|
function parseDiff(diff) {
|
|
701
315
|
const lines = diff.split("\n");
|
|
702
316
|
const fileLine = lines.find((l) => l.startsWith("+++ b"));
|
|
@@ -713,21 +327,21 @@ function parseDiff(diff) {
|
|
|
713
327
|
}
|
|
714
328
|
function applyDiffToFile(diff) {
|
|
715
329
|
const { filePath, oldStart, oldCount, newLines } = parseDiff(diff);
|
|
716
|
-
const content =
|
|
330
|
+
const content = readFileSync(filePath, "utf8");
|
|
717
331
|
const fileLines = content.split("\n");
|
|
718
332
|
const removedLines = diff.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).map((l) => l.slice(1));
|
|
719
333
|
const actual = fileLines.slice(oldStart - 1, oldStart - 1 + oldCount);
|
|
720
334
|
if (JSON.stringify(actual) !== JSON.stringify(removedLines)) return false;
|
|
721
335
|
fileLines.splice(oldStart - 1, oldCount, ...newLines);
|
|
722
|
-
|
|
336
|
+
writeFileSync(filePath, fileLines.join("\n"));
|
|
723
337
|
return true;
|
|
724
338
|
}
|
|
725
339
|
|
|
726
340
|
// src/pack.ts
|
|
727
|
-
import { existsSync
|
|
728
|
-
import { join
|
|
341
|
+
import { existsSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
342
|
+
import { join } from "path";
|
|
729
343
|
function initPack(dir, opts) {
|
|
730
|
-
if (
|
|
344
|
+
if (existsSync(join(dir, PACK_MANIFEST_FILE))) {
|
|
731
345
|
throw new Error(`${dir} already contains a ${PACK_MANIFEST_FILE}`);
|
|
732
346
|
}
|
|
733
347
|
const manifest = {
|
|
@@ -737,9 +351,9 @@ function initPack(dir, opts) {
|
|
|
737
351
|
author: opts.author ?? "unknown",
|
|
738
352
|
...opts.description ? { description: opts.description } : {}
|
|
739
353
|
};
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
354
|
+
mkdirSync(dir, { recursive: true });
|
|
355
|
+
mkdirSync(join(dir, "rules"), { recursive: true });
|
|
356
|
+
mkdirSync(join(dir, "fixtures"), { recursive: true });
|
|
743
357
|
const manifestYaml = [
|
|
744
358
|
`id: ${manifest.id}`,
|
|
745
359
|
`name: ${manifest.name}`,
|
|
@@ -748,18 +362,18 @@ function initPack(dir, opts) {
|
|
|
748
362
|
...manifest.description ? [`description: ${manifest.description}`] : [],
|
|
749
363
|
""
|
|
750
364
|
].join("\n");
|
|
751
|
-
const manifestFile =
|
|
752
|
-
|
|
365
|
+
const manifestFile = join(dir, PACK_MANIFEST_FILE);
|
|
366
|
+
writeFileSync2(manifestFile, manifestYaml, "utf8");
|
|
753
367
|
return manifestFile;
|
|
754
368
|
}
|
|
755
369
|
function validatePack(dir) {
|
|
756
370
|
const { manifest, rules } = loadPack(dir);
|
|
757
|
-
const fixturesRoot =
|
|
371
|
+
const fixturesRoot = join(dir, "fixtures");
|
|
758
372
|
const ruleResults = rules.map((rule) => {
|
|
759
|
-
const ruleFixturesDir =
|
|
760
|
-
const vulnerableDir =
|
|
761
|
-
const fixedDir =
|
|
762
|
-
if (!
|
|
373
|
+
const ruleFixturesDir = join(fixturesRoot, rule.id);
|
|
374
|
+
const vulnerableDir = join(ruleFixturesDir, "vulnerable");
|
|
375
|
+
const fixedDir = join(ruleFixturesDir, "fixed");
|
|
376
|
+
if (!existsSync(vulnerableDir) || !existsSync(fixedDir)) {
|
|
763
377
|
return {
|
|
764
378
|
ruleId: rule.id,
|
|
765
379
|
status: "missing-fixtures",
|
|
@@ -792,10 +406,10 @@ function validatePack(dir) {
|
|
|
792
406
|
|
|
793
407
|
// src/telemetry.ts
|
|
794
408
|
import { createHash, randomUUID } from "crypto";
|
|
795
|
-
import { appendFileSync, existsSync as
|
|
409
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
796
410
|
import { execFileSync } from "child_process";
|
|
797
|
-
import { homedir
|
|
798
|
-
import { dirname
|
|
411
|
+
import { homedir } from "os";
|
|
412
|
+
import { dirname, join as join2, resolve } from "path";
|
|
799
413
|
function sha256Hex(s) {
|
|
800
414
|
return createHash("sha256").update(s).digest("hex");
|
|
801
415
|
}
|
|
@@ -803,24 +417,24 @@ function isTelemetryEnabled(targetDir) {
|
|
|
803
417
|
const env = process.env.BRAINBLAST_TELEMETRY;
|
|
804
418
|
if (env === "1" || env === "true") return true;
|
|
805
419
|
if (env === "0" || env === "false") return false;
|
|
806
|
-
const configPath =
|
|
807
|
-
if (!
|
|
420
|
+
const configPath = join2(targetDir, ".agent-research", "config.json");
|
|
421
|
+
if (!existsSync2(configPath)) return false;
|
|
808
422
|
try {
|
|
809
|
-
const cfg = JSON.parse(
|
|
423
|
+
const cfg = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
810
424
|
return cfg?.telemetry === true;
|
|
811
425
|
} catch {
|
|
812
426
|
return false;
|
|
813
427
|
}
|
|
814
428
|
}
|
|
815
429
|
function getUserHash() {
|
|
816
|
-
const idPath =
|
|
430
|
+
const idPath = join2(homedir(), ".brainblast", "telemetry-id");
|
|
817
431
|
let id;
|
|
818
|
-
if (
|
|
819
|
-
id =
|
|
432
|
+
if (existsSync2(idPath)) {
|
|
433
|
+
id = readFileSync2(idPath, "utf8").trim();
|
|
820
434
|
} else {
|
|
821
435
|
id = randomUUID();
|
|
822
|
-
|
|
823
|
-
|
|
436
|
+
mkdirSync2(dirname(idPath), { recursive: true });
|
|
437
|
+
writeFileSync3(idPath, id, "utf8");
|
|
824
438
|
}
|
|
825
439
|
return sha256Hex(id).slice(0, 16);
|
|
826
440
|
}
|
|
@@ -838,15 +452,15 @@ function getRepoHash(targetDir) {
|
|
|
838
452
|
return sha256Hex(key).slice(0, 16);
|
|
839
453
|
}
|
|
840
454
|
function telemetryFilePath(targetDir) {
|
|
841
|
-
return
|
|
455
|
+
return join2(targetDir, ".agent-research", "telemetry.ndjson");
|
|
842
456
|
}
|
|
843
457
|
var DEFAULT_REGISTRY_URL = "https://registry.brainblast.tech";
|
|
844
458
|
async function submitTelemetry(targetDir, registryUrl = process.env.BRAINBLAST_REGISTRY_URL || DEFAULT_REGISTRY_URL) {
|
|
845
459
|
const file = telemetryFilePath(targetDir);
|
|
846
|
-
if (!
|
|
460
|
+
if (!existsSync2(file)) {
|
|
847
461
|
return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
|
|
848
462
|
}
|
|
849
|
-
const events =
|
|
463
|
+
const events = readFileSync2(file, "utf8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
850
464
|
if (events.length === 0) {
|
|
851
465
|
return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
|
|
852
466
|
}
|
|
@@ -865,7 +479,7 @@ async function submitTelemetry(targetDir, registryUrl = process.env.BRAINBLAST_R
|
|
|
865
479
|
function recordGraduationEvents(targetDir, events) {
|
|
866
480
|
if (events.length === 0) return;
|
|
867
481
|
const file = telemetryFilePath(targetDir);
|
|
868
|
-
|
|
482
|
+
mkdirSync2(dirname(file), { recursive: true });
|
|
869
483
|
const repo_hash = getRepoHash(targetDir);
|
|
870
484
|
const user_hash = getUserHash();
|
|
871
485
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -874,21 +488,6 @@ function recordGraduationEvents(targetDir, events) {
|
|
|
874
488
|
}
|
|
875
489
|
|
|
876
490
|
export {
|
|
877
|
-
loadDirectory,
|
|
878
|
-
base58Encode,
|
|
879
|
-
base58Decode,
|
|
880
|
-
isValidSolanaAddress,
|
|
881
|
-
DEFAULT_TTL_HOURS,
|
|
882
|
-
defaultCachePath,
|
|
883
|
-
loadProgramCache,
|
|
884
|
-
saveProgramCache,
|
|
885
|
-
getCacheEntry,
|
|
886
|
-
putCacheEntry,
|
|
887
|
-
getCacheEntryMeta,
|
|
888
|
-
isEntryExpired,
|
|
889
|
-
cacheSize,
|
|
890
|
-
buildTrustGraph,
|
|
891
|
-
renderTrustGraphMd,
|
|
892
491
|
rentExemptMinimum,
|
|
893
492
|
lamportsToSol,
|
|
894
493
|
analyzeCosts,
|