brainblast 0.2.0 → 0.4.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/README.md +73 -15
- package/dist/chunk-WVHGN2HR.js +1812 -0
- package/dist/cli.js +171 -7
- package/dist/index.d.ts +230 -2
- package/dist/index.js +39 -1
- package/dist/programs/directory.yaml +179 -0
- package/dist/rules/anchor-init-if-needed-guarded.yaml +47 -0
- package/dist/rules/bags-fee-share-creator-included.yaml +6 -1
- package/dist/rules/metaplex-metadata-immutable.yaml +55 -0
- package/dist/rules/privy-jwt-verification.yaml +18 -2
- package/dist/rules/stripe-webhook-raw-body.yaml +5 -0
- package/dist/rules/token-2022-program-id-pinned.yaml +55 -0
- package/package.json +50 -9
- package/dist/chunk-H2Y75CSH.js +0 -494
package/dist/cli.js
CHANGED
|
@@ -1,28 +1,142 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
analyzeCosts,
|
|
3
4
|
audit,
|
|
5
|
+
buildTrustGraph,
|
|
6
|
+
cacheSize,
|
|
7
|
+
defaultCachePath,
|
|
8
|
+
isValidSolanaAddress,
|
|
9
|
+
loadProgramCache,
|
|
10
|
+
renderCostReportMd,
|
|
11
|
+
renderTrustGraphMd,
|
|
4
12
|
resolveRules
|
|
5
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WVHGN2HR.js";
|
|
6
14
|
|
|
7
15
|
// src/cli.ts
|
|
8
|
-
import { writeFileSync, mkdirSync } from "fs";
|
|
16
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
17
|
+
import { join as join2 } from "path";
|
|
18
|
+
|
|
19
|
+
// src/memory.ts
|
|
20
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
9
21
|
import { join } from "path";
|
|
22
|
+
var EMPTY_MEMORY = { schemaVersion: "1.0", lastRun: [], fixHistory: [] };
|
|
23
|
+
function memoryPath(targetDir2) {
|
|
24
|
+
return join(targetDir2, ".agent-research", "memory.json");
|
|
25
|
+
}
|
|
26
|
+
function loadMemory(targetDir2) {
|
|
27
|
+
const p = memoryPath(targetDir2);
|
|
28
|
+
if (!existsSync(p)) return { ...EMPTY_MEMORY, lastRun: [], fixHistory: [] };
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(readFileSync(p, "utf8"));
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: parsed.schemaVersion ?? "1.0",
|
|
33
|
+
lastRun: Array.isArray(parsed.lastRun) ? parsed.lastRun : [],
|
|
34
|
+
fixHistory: Array.isArray(parsed.fixHistory) ? parsed.fixHistory : []
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return { ...EMPTY_MEMORY, lastRun: [], fixHistory: [] };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveMemory(targetDir2, memory2) {
|
|
41
|
+
mkdirSync(join(targetDir2, ".agent-research"), { recursive: true });
|
|
42
|
+
writeFileSync(memoryPath(targetDir2), JSON.stringify(memory2, null, 2));
|
|
43
|
+
}
|
|
44
|
+
var snapshotKey = (e) => `${e.ruleId}::${e.file}::${e.exportName}`;
|
|
45
|
+
function precedentKey(c) {
|
|
46
|
+
return `${c.ruleId}::${c.file}`;
|
|
47
|
+
}
|
|
48
|
+
function updateMemory(memory2, checks2, now = /* @__PURE__ */ new Date()) {
|
|
49
|
+
const prevByKey = new Map(memory2.lastRun.map((e) => [snapshotKey(e), e]));
|
|
50
|
+
const fixedAt = now.toISOString().slice(0, 10);
|
|
51
|
+
const newFixEvents = [];
|
|
52
|
+
for (const c of checks2) {
|
|
53
|
+
const prev = prevByKey.get(snapshotKey(c));
|
|
54
|
+
if (prev?.result === "fail" && c.result !== "fail") {
|
|
55
|
+
newFixEvents.push({
|
|
56
|
+
ruleId: c.ruleId,
|
|
57
|
+
file: c.file,
|
|
58
|
+
exportName: c.exportName,
|
|
59
|
+
fixedAt,
|
|
60
|
+
detail: prev.detail
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const fixHistory = [...memory2.fixHistory, ...newFixEvents];
|
|
65
|
+
const precedents2 = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const c of checks2) {
|
|
67
|
+
if (c.result !== "fail") continue;
|
|
68
|
+
const pk = precedentKey(c);
|
|
69
|
+
if (precedents2.has(pk)) continue;
|
|
70
|
+
const matches = fixHistory.filter((e) => e.ruleId === c.ruleId && e.file !== c.file).sort((a, b) => a.fixedAt < b.fixedAt ? 1 : a.fixedAt > b.fixedAt ? -1 : 0);
|
|
71
|
+
if (matches[0]) {
|
|
72
|
+
precedents2.set(pk, {
|
|
73
|
+
file: matches[0].file,
|
|
74
|
+
exportName: matches[0].exportName,
|
|
75
|
+
fixedAt: matches[0].fixedAt,
|
|
76
|
+
detail: matches[0].detail
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const lastRun = checks2.map((c) => ({
|
|
81
|
+
ruleId: c.ruleId,
|
|
82
|
+
file: c.file,
|
|
83
|
+
exportName: c.exportName,
|
|
84
|
+
result: c.result,
|
|
85
|
+
detail: c.detail
|
|
86
|
+
}));
|
|
87
|
+
return { memory: { schemaVersion: "1.0", lastRun, fixHistory }, precedents: precedents2 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/cli.ts
|
|
10
91
|
var args = process.argv.slice(2);
|
|
92
|
+
if (args[0] === "trust-graph") {
|
|
93
|
+
await runTrustGraph(args.slice(1));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
11
96
|
var ci = args.includes("--ci");
|
|
12
97
|
var strict = args.includes("--strict");
|
|
13
98
|
var targetDir = args.find((a) => !a.startsWith("--")) ?? process.cwd();
|
|
14
99
|
var rules = resolveRules(targetDir);
|
|
15
100
|
var { checks, report } = audit(targetDir, rules);
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
101
|
+
var memory = loadMemory(targetDir);
|
|
102
|
+
var { memory: nextMemory, precedents } = updateMemory(memory, checks);
|
|
103
|
+
for (const c of checks) {
|
|
104
|
+
const p = precedents.get(precedentKey(c));
|
|
105
|
+
if (p) c.precedent = p;
|
|
106
|
+
}
|
|
107
|
+
for (const rc of report.checks) {
|
|
108
|
+
const p = precedents.get(precedentKey(rc));
|
|
109
|
+
if (p) rc.precedent = p;
|
|
110
|
+
}
|
|
111
|
+
saveMemory(targetDir, nextMemory);
|
|
112
|
+
var costReport = analyzeCosts(targetDir);
|
|
113
|
+
report.costAnalysis = costReport;
|
|
114
|
+
var outDir = join2(targetDir, ".agent-research");
|
|
115
|
+
mkdirSync2(outDir, { recursive: true });
|
|
116
|
+
var reportPath = join2(outDir, "report.json");
|
|
117
|
+
writeFileSync2(reportPath, JSON.stringify(report, null, 2));
|
|
118
|
+
var costMdPath = join2(outDir, "cost-analysis.md");
|
|
119
|
+
writeFileSync2(costMdPath, renderCostReportMd(costReport));
|
|
20
120
|
console.log(`brainblast: scanned ${targetDir} with ${rules.length} rule(s)`);
|
|
21
121
|
if (checks.length === 0) console.log(" (no catastrophic components detected)");
|
|
22
122
|
for (const c of checks) {
|
|
23
123
|
const tag = c.result === "pass" ? "PASS " : c.result === "fail" ? "FAIL " : "WARN ";
|
|
24
124
|
console.log(` [${tag}] ${c.ruleId} ${c.file}:${c.line}`);
|
|
25
125
|
console.log(` ${c.detail}`);
|
|
126
|
+
if (c.precedent) {
|
|
127
|
+
console.log(
|
|
128
|
+
` memory: same issue (${c.ruleId}) was fixed in ${c.precedent.file} on ${c.precedent.fixedAt}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (c.fix) {
|
|
132
|
+
console.log(` fix: ${c.fix.summary}`);
|
|
133
|
+
if (c.fix.diff) {
|
|
134
|
+
for (const line of c.fix.diff.split("\n")) console.log(` ${line}`);
|
|
135
|
+
}
|
|
136
|
+
if (c.fix.suggestion) {
|
|
137
|
+
for (const line of c.fix.suggestion.split("\n")) console.log(` ${line}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
26
140
|
}
|
|
27
141
|
var fails = checks.filter((c) => c.result === "fail").length;
|
|
28
142
|
var cantTell = checks.filter((c) => c.result === "cant_tell").length;
|
|
@@ -30,8 +144,58 @@ console.log(` verdict: ${report.summary.verdict} (fail=${fails}, cant_tell=${c
|
|
|
30
144
|
if (cantTell > 0 && !strict) {
|
|
31
145
|
console.log(` warning: ${cantTell} cant_tell (not gating \u2014 pass --strict to fail on these)`);
|
|
32
146
|
}
|
|
33
|
-
console.log(
|
|
147
|
+
console.log("\n\u2500\u2500 Cost & Rent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
148
|
+
if (!costReport.priorityFee.found) {
|
|
149
|
+
console.log(" [HIGH ] priority fee not configured \u2014 add setComputeUnitPrice to critical paths");
|
|
150
|
+
}
|
|
151
|
+
if (costReport.accountFlows.length === 0) {
|
|
152
|
+
console.log(" (no account-creation flows from tracked modules detected)");
|
|
153
|
+
} else {
|
|
154
|
+
for (const f of costReport.accountFlows) {
|
|
155
|
+
const file = f.file.split("/").slice(-2).join("/");
|
|
156
|
+
const scaleMark = f.scalable ? " [SCALABLE]" : "";
|
|
157
|
+
console.log(` ${f.accountType}${scaleMark} ${file}:${f.line} +${f.lamports.toLocaleString()} lamports (${f.sol} SOL)`);
|
|
158
|
+
}
|
|
159
|
+
if (costReport.totalLockupLamports > 0) {
|
|
160
|
+
console.log(` \u2500\u2500\u2500 static lockup total: ${costReport.totalLockupLamports.toLocaleString()} lamports (~${costReport.totalLockupSol} SOL)`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
console.log(` cost report: ${costMdPath}`);
|
|
164
|
+
console.log(` report: ${reportPath}`);
|
|
34
165
|
if (ci) {
|
|
35
166
|
const gateFail = fails > 0 || strict && cantTell > 0;
|
|
36
167
|
process.exit(gateFail ? 1 : 0);
|
|
37
168
|
}
|
|
169
|
+
async function runTrustGraph(argv) {
|
|
170
|
+
const rpcIdx = argv.indexOf("--rpc");
|
|
171
|
+
const rpcUrl = rpcIdx >= 0 ? argv[rpcIdx + 1] : void 0;
|
|
172
|
+
const noProbe = argv.includes("--no-probe");
|
|
173
|
+
const jsonOut = argv.includes("--json");
|
|
174
|
+
const ids = argv.filter((a) => !a.startsWith("--") && a !== rpcUrl);
|
|
175
|
+
if (ids.length === 0) {
|
|
176
|
+
console.error("usage: brainblast trust-graph <programId> [<programId>...] [--rpc URL] [--no-probe] [--json]");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
for (const id of ids) {
|
|
180
|
+
if (!isValidSolanaAddress(id)) {
|
|
181
|
+
console.error(`error: '${id}' is not a valid Solana address`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const noCache = argv.includes("--no-cache");
|
|
186
|
+
const graph = await buildTrustGraph(ids, {
|
|
187
|
+
rpcUrl,
|
|
188
|
+
probeRpc: !noProbe,
|
|
189
|
+
cachePath: noCache ? null : void 0
|
|
190
|
+
});
|
|
191
|
+
if (jsonOut) {
|
|
192
|
+
console.log(JSON.stringify(graph, null, 2));
|
|
193
|
+
} else {
|
|
194
|
+
console.log(renderTrustGraphMd(graph));
|
|
195
|
+
}
|
|
196
|
+
if (!noCache) {
|
|
197
|
+
const cp = defaultCachePath();
|
|
198
|
+
const count = cacheSize(loadProgramCache(cp));
|
|
199
|
+
console.error(` program-cache: ${count} entries (${cp})`);
|
|
200
|
+
}
|
|
201
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import { FunctionDeclaration, ArrowFunction } from 'ts-morph';
|
|
2
2
|
|
|
3
|
+
declare function rentExemptMinimum(dataLen: number): number;
|
|
4
|
+
declare function lamportsToSol(lamports: number): string;
|
|
5
|
+
type Recoverability = "recoverable" | "non-recoverable" | "conditionally-recoverable";
|
|
6
|
+
interface AccountFlow {
|
|
7
|
+
call: string;
|
|
8
|
+
module: string;
|
|
9
|
+
accountType: string;
|
|
10
|
+
file: string;
|
|
11
|
+
line: number;
|
|
12
|
+
dataLen: number;
|
|
13
|
+
lamports: number;
|
|
14
|
+
sol: string;
|
|
15
|
+
recoverability: Recoverability;
|
|
16
|
+
recoverabilityNote: string;
|
|
17
|
+
/** Call appears inside a loop or .map()/.forEach() — cost scales with N */
|
|
18
|
+
scalable: boolean;
|
|
19
|
+
scalableNote?: string;
|
|
20
|
+
}
|
|
21
|
+
interface PriorityFeePosture {
|
|
22
|
+
/** true = setComputeUnitPrice call detected somewhere in the target */
|
|
23
|
+
found: boolean;
|
|
24
|
+
file?: string;
|
|
25
|
+
line?: number;
|
|
26
|
+
detail: string;
|
|
27
|
+
}
|
|
28
|
+
interface CostReport {
|
|
29
|
+
accountFlows: AccountFlow[];
|
|
30
|
+
priorityFee: PriorityFeePosture;
|
|
31
|
+
/** Sum of lamports across non-scalable flows (static lower bound) */
|
|
32
|
+
totalLockupLamports: number;
|
|
33
|
+
totalLockupSol: string;
|
|
34
|
+
/** Subset of flows that grow with N */
|
|
35
|
+
scalableFlows: AccountFlow[];
|
|
36
|
+
generatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
declare function analyzeCosts(targetDir: string): CostReport;
|
|
39
|
+
declare function renderCostReportMd(r: CostReport): string;
|
|
40
|
+
|
|
3
41
|
type Severity = "critical" | "high" | "medium" | "low";
|
|
4
42
|
type CheckResultKind = "pass" | "fail" | "cant_tell";
|
|
5
43
|
interface Candidate {
|
|
@@ -8,10 +46,49 @@ interface Candidate {
|
|
|
8
46
|
params: string[];
|
|
9
47
|
fn: FunctionDeclaration | ArrowFunction;
|
|
10
48
|
}
|
|
49
|
+
interface RustAccountField {
|
|
50
|
+
/** Rust field identifier, e.g. "counter" */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Raw type text, e.g. "Account<'info, Counter>", "Signer<'info>" */
|
|
53
|
+
typeName: string;
|
|
54
|
+
/** Full text of every #[account(...)] attribute on this field */
|
|
55
|
+
attrText: string;
|
|
56
|
+
/** Whether init_if_needed is present in attrText */
|
|
57
|
+
hasInitIfNeeded: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface RustCandidate {
|
|
60
|
+
/** Source .rs file */
|
|
61
|
+
filePath: string;
|
|
62
|
+
/** Instruction handler name, e.g. "initialize" */
|
|
63
|
+
fnName: string;
|
|
64
|
+
/** Anchor Accounts struct name resolved from Context<X>, e.g. "Initialize" */
|
|
65
|
+
accountStructName: string;
|
|
66
|
+
/** All fields extracted from the Accounts struct */
|
|
67
|
+
accountFields: RustAccountField[];
|
|
68
|
+
/**
|
|
69
|
+
* Raw text of the instruction handler body block `{ ... }` — used by
|
|
70
|
+
* checkers that need to inspect companion calls (require!, data_is_empty, etc.)
|
|
71
|
+
* without a full tree-sitter traversal.
|
|
72
|
+
*/
|
|
73
|
+
fnBodyText: string;
|
|
74
|
+
/** tree-sitter SyntaxNode for the function body — available for precise queries */
|
|
75
|
+
fnBodyNode: any;
|
|
76
|
+
}
|
|
11
77
|
interface CheckOutcome {
|
|
12
78
|
result: CheckResultKind;
|
|
13
79
|
detail: string;
|
|
14
80
|
}
|
|
81
|
+
interface Fix {
|
|
82
|
+
summary: string;
|
|
83
|
+
diff?: string;
|
|
84
|
+
suggestion?: string;
|
|
85
|
+
}
|
|
86
|
+
interface Precedent {
|
|
87
|
+
file: string;
|
|
88
|
+
exportName: string;
|
|
89
|
+
fixedAt: string;
|
|
90
|
+
detail: string;
|
|
91
|
+
}
|
|
15
92
|
interface CheckResult extends CheckOutcome {
|
|
16
93
|
ruleId: string;
|
|
17
94
|
severity: Severity;
|
|
@@ -19,6 +96,10 @@ interface CheckResult extends CheckOutcome {
|
|
|
19
96
|
file: string;
|
|
20
97
|
line: number;
|
|
21
98
|
exportName: string;
|
|
99
|
+
/** Present when result === "fail" and a vetted fixer for check.kind produced one. */
|
|
100
|
+
fix?: Fix;
|
|
101
|
+
/** Present when result === "fail" and the same rule was previously fixed elsewhere. */
|
|
102
|
+
precedent?: Precedent;
|
|
22
103
|
}
|
|
23
104
|
interface Rule {
|
|
24
105
|
id: string;
|
|
@@ -34,6 +115,21 @@ interface Rule {
|
|
|
34
115
|
modules: string[];
|
|
35
116
|
nameRegex: string;
|
|
36
117
|
triggerCalls: string[];
|
|
118
|
+
/** Defaults to "typescript". Set to "rust" for Anchor/Rust checker kinds. */
|
|
119
|
+
lang?: "typescript" | "rust";
|
|
120
|
+
/**
|
|
121
|
+
* When true, a module import from `modules` is a REQUIRED condition for
|
|
122
|
+
* detection: a candidate must be in a file that imports one of the listed
|
|
123
|
+
* modules, AND either its name matches nameRegex or its body calls a
|
|
124
|
+
* triggerCall. This prevents generic name-only matches (e.g. a Fastify
|
|
125
|
+
* middleware named "verifyJwt" that calls `request.jwtVerify()`) from
|
|
126
|
+
* triggering jose-specific rules.
|
|
127
|
+
*
|
|
128
|
+
* When false or omitted (default), detection is: nameRegex match OR
|
|
129
|
+
* triggerCall in body. Module import is not required, so rules like
|
|
130
|
+
* stripe-webhook can still catch handlers that don't import stripe directly.
|
|
131
|
+
*/
|
|
132
|
+
requiresImport?: boolean;
|
|
37
133
|
};
|
|
38
134
|
check: {
|
|
39
135
|
kind: string;
|
|
@@ -82,6 +178,8 @@ declare function audit(targetDir: string, rules: Rule[]): {
|
|
|
82
178
|
low: number;
|
|
83
179
|
};
|
|
84
180
|
checks: {
|
|
181
|
+
precedent?: Precedent | undefined;
|
|
182
|
+
fix?: Fix | undefined;
|
|
85
183
|
ruleId: string;
|
|
86
184
|
severity: Severity;
|
|
87
185
|
result: CheckResultKind;
|
|
@@ -96,6 +194,7 @@ declare function audit(targetDir: string, rules: Rule[]): {
|
|
|
96
194
|
cant_tell: number;
|
|
97
195
|
};
|
|
98
196
|
openQuestions: never[];
|
|
197
|
+
costAnalysis: CostReport | null;
|
|
99
198
|
};
|
|
100
199
|
};
|
|
101
200
|
|
|
@@ -114,9 +213,138 @@ declare function renderTest(kind: string, opts: {
|
|
|
114
213
|
}): string;
|
|
115
214
|
declare const testKinds: string[];
|
|
116
215
|
|
|
117
|
-
declare function runChecker(kind: string, c: Candidate, params: any): CheckOutcome;
|
|
216
|
+
declare function runChecker(kind: string, c: Candidate | RustCandidate, params: any): CheckOutcome;
|
|
118
217
|
declare const checkerKinds: string[];
|
|
119
218
|
|
|
120
219
|
declare function findCandidates(targetDir: string, rule: Rule): Candidate[];
|
|
121
220
|
|
|
122
|
-
|
|
221
|
+
type UpgradeAuthorityKind = "renounced" | "single-key" | "multisig" | "dao" | "unknown";
|
|
222
|
+
type UpgradeAuthoritySource = "directory" | "rpc" | "research";
|
|
223
|
+
interface UpgradeAuthority {
|
|
224
|
+
kind: UpgradeAuthorityKind;
|
|
225
|
+
address: string | null;
|
|
226
|
+
source: UpgradeAuthoritySource;
|
|
227
|
+
checkedAt?: string;
|
|
228
|
+
}
|
|
229
|
+
type VerifiedBuildState = {
|
|
230
|
+
state: "verified";
|
|
231
|
+
registryUrl: string;
|
|
232
|
+
commit?: string;
|
|
233
|
+
} | {
|
|
234
|
+
state: "unverified";
|
|
235
|
+
} | {
|
|
236
|
+
state: "unknown";
|
|
237
|
+
};
|
|
238
|
+
interface AuditRef {
|
|
239
|
+
firm: string;
|
|
240
|
+
date: string;
|
|
241
|
+
reportUrl: string;
|
|
242
|
+
auditedCommit?: string;
|
|
243
|
+
}
|
|
244
|
+
interface ParityNote {
|
|
245
|
+
mainnet: "present" | "absent" | "different" | "unknown";
|
|
246
|
+
devnet: "present" | "absent" | "different" | "unknown";
|
|
247
|
+
testnet?: "present" | "absent" | "different" | "unknown";
|
|
248
|
+
notes?: string;
|
|
249
|
+
}
|
|
250
|
+
interface OnChainProgram {
|
|
251
|
+
programId: string;
|
|
252
|
+
name: string;
|
|
253
|
+
kind?: string;
|
|
254
|
+
upgradeAuthority: UpgradeAuthority;
|
|
255
|
+
verifiedBuild: VerifiedBuildState;
|
|
256
|
+
audits: AuditRef[];
|
|
257
|
+
parity: ParityNote;
|
|
258
|
+
invokes?: string[];
|
|
259
|
+
provenance?: {
|
|
260
|
+
directoryFile?: string;
|
|
261
|
+
rpcUrl?: string;
|
|
262
|
+
researchRun?: string;
|
|
263
|
+
notes?: string;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
interface TrustGraph {
|
|
267
|
+
programs: OnChainProgram[];
|
|
268
|
+
unresolved: Array<{
|
|
269
|
+
programId: string;
|
|
270
|
+
reason: string;
|
|
271
|
+
}>;
|
|
272
|
+
generatedAt: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface RpcOpts {
|
|
276
|
+
rpcUrl?: string;
|
|
277
|
+
timeoutMs?: number;
|
|
278
|
+
fetchImpl?: typeof fetch;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface BuildOpts extends RpcOpts {
|
|
282
|
+
probeRpc?: boolean;
|
|
283
|
+
directoryPath?: string;
|
|
284
|
+
cachePath?: string | null;
|
|
285
|
+
}
|
|
286
|
+
declare function buildTrustGraph(programIds: string[], opts?: BuildOpts): Promise<TrustGraph>;
|
|
287
|
+
|
|
288
|
+
declare function renderTrustGraphMd(g: TrustGraph): string;
|
|
289
|
+
|
|
290
|
+
declare function loadDirectory(path?: string): Map<string, OnChainProgram>;
|
|
291
|
+
|
|
292
|
+
declare function base58Encode(bytes: Uint8Array): string;
|
|
293
|
+
declare function base58Decode(s: string): Uint8Array;
|
|
294
|
+
declare function isValidSolanaAddress(s: string): boolean;
|
|
295
|
+
|
|
296
|
+
declare const DEFAULT_TTL_HOURS = 168;
|
|
297
|
+
declare const SCHEMA_VERSION = "1.0";
|
|
298
|
+
interface ProgramCacheEntry {
|
|
299
|
+
program: OnChainProgram;
|
|
300
|
+
/** ISO 8601 timestamp of when this entry was written. */
|
|
301
|
+
cachedAt: string;
|
|
302
|
+
/** The run.id from the brainblast report that produced this entry. */
|
|
303
|
+
sourceRun: string;
|
|
304
|
+
/** Hours until this entry expires. Defaults to DEFAULT_TTL_HOURS. */
|
|
305
|
+
ttlHours?: number;
|
|
306
|
+
}
|
|
307
|
+
interface ProgramCache {
|
|
308
|
+
schemaVersion: typeof SCHEMA_VERSION;
|
|
309
|
+
entries: Record<string, ProgramCacheEntry>;
|
|
310
|
+
}
|
|
311
|
+
declare function defaultCachePath(): string;
|
|
312
|
+
/**
|
|
313
|
+
* Load the program cache from disk.
|
|
314
|
+
*
|
|
315
|
+
* Returns an empty cache if the file does not exist, is unreadable, or has an
|
|
316
|
+
* incompatible schemaVersion. Never throws on missing/corrupt files — callers
|
|
317
|
+
* can always start fresh.
|
|
318
|
+
*/
|
|
319
|
+
declare function loadProgramCache(cachePath?: string): ProgramCache;
|
|
320
|
+
/**
|
|
321
|
+
* Persist the program cache to disk, creating parent directories as needed.
|
|
322
|
+
* Throws only on unrecoverable write errors (e.g. permission denied on the
|
|
323
|
+
* home directory itself).
|
|
324
|
+
*/
|
|
325
|
+
declare function saveProgramCache(cache: ProgramCache, cachePath?: string): void;
|
|
326
|
+
/**
|
|
327
|
+
* Return the cached OnChainProgram for programId, or null if:
|
|
328
|
+
* - the entry does not exist
|
|
329
|
+
* - the entry has exceeded its TTL
|
|
330
|
+
*
|
|
331
|
+
* @param ttlHoursOverride - optional override for the TTL check (e.g. for tests)
|
|
332
|
+
*/
|
|
333
|
+
declare function getCacheEntry(cache: ProgramCache, programId: string, ttlHoursOverride?: number): OnChainProgram | null;
|
|
334
|
+
/**
|
|
335
|
+
* Write (or overwrite) a cache entry for programId.
|
|
336
|
+
* Mutates the cache object in place and returns it for chaining.
|
|
337
|
+
*/
|
|
338
|
+
declare function putCacheEntry(cache: ProgramCache, programId: string, program: OnChainProgram, sourceRun: string, ttlHours?: number): ProgramCache;
|
|
339
|
+
/**
|
|
340
|
+
* Return the raw ProgramCacheEntry for programId (including metadata), or null.
|
|
341
|
+
* Useful for rendering provenance to users ("cached 3 days ago by run X").
|
|
342
|
+
*/
|
|
343
|
+
declare function getCacheEntryMeta(cache: ProgramCache, programId: string): ProgramCacheEntry | null;
|
|
344
|
+
declare function isEntryExpired(entry: ProgramCacheEntry, ttlHoursOverride?: number): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Count of non-expired entries in the cache (useful for status lines).
|
|
347
|
+
*/
|
|
348
|
+
declare function cacheSize(cache: ProgramCache, ttlHoursOverride?: number): number;
|
|
349
|
+
|
|
350
|
+
export { type AccountFlow, type AuditRef, type BuildOpts, type Candidate, type CheckOutcome, type CheckResult, type CheckResultKind, type CostReport, DEFAULT_TTL_HOURS, type OnChainProgram, type ParityNote, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type Rule, type RustAccountField, type RustCandidate, type Severity, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, analyzeCosts, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, checkerKinds, defaultCachePath, findCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, isEntryExpired, isValidSolanaAddress, lamportsToSol, loadDirectory, loadProgramCache, loadRules, putCacheEntry, renderCostReportMd, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, runChecker, saveProgramCache, testKinds };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_TTL_HOURS,
|
|
3
|
+
analyzeCosts,
|
|
2
4
|
audit,
|
|
3
5
|
auditWithRule,
|
|
6
|
+
base58Decode,
|
|
7
|
+
base58Encode,
|
|
8
|
+
buildTrustGraph,
|
|
9
|
+
cacheSize,
|
|
4
10
|
checkerKinds,
|
|
11
|
+
defaultCachePath,
|
|
5
12
|
findCandidates,
|
|
13
|
+
getCacheEntry,
|
|
14
|
+
getCacheEntryMeta,
|
|
15
|
+
isEntryExpired,
|
|
16
|
+
isValidSolanaAddress,
|
|
17
|
+
lamportsToSol,
|
|
18
|
+
loadDirectory,
|
|
19
|
+
loadProgramCache,
|
|
6
20
|
loadRules,
|
|
21
|
+
putCacheEntry,
|
|
22
|
+
renderCostReportMd,
|
|
7
23
|
renderTest,
|
|
24
|
+
renderTrustGraphMd,
|
|
25
|
+
rentExemptMinimum,
|
|
8
26
|
resolveRules,
|
|
9
27
|
rules,
|
|
10
28
|
runChecker,
|
|
29
|
+
saveProgramCache,
|
|
11
30
|
testKinds
|
|
12
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-WVHGN2HR.js";
|
|
13
32
|
|
|
14
33
|
// src/generate.ts
|
|
15
34
|
import { writeFileSync, mkdirSync } from "fs";
|
|
@@ -25,15 +44,34 @@ function generateTestForResult(result, rule, outPath) {
|
|
|
25
44
|
return outPath;
|
|
26
45
|
}
|
|
27
46
|
export {
|
|
47
|
+
DEFAULT_TTL_HOURS,
|
|
48
|
+
analyzeCosts,
|
|
28
49
|
audit,
|
|
29
50
|
auditWithRule,
|
|
51
|
+
base58Decode,
|
|
52
|
+
base58Encode,
|
|
53
|
+
buildTrustGraph,
|
|
30
54
|
rules as bundledRules,
|
|
55
|
+
cacheSize,
|
|
31
56
|
checkerKinds,
|
|
57
|
+
defaultCachePath,
|
|
32
58
|
findCandidates,
|
|
33
59
|
generateTestForResult,
|
|
60
|
+
getCacheEntry,
|
|
61
|
+
getCacheEntryMeta,
|
|
62
|
+
isEntryExpired,
|
|
63
|
+
isValidSolanaAddress,
|
|
64
|
+
lamportsToSol,
|
|
65
|
+
loadDirectory,
|
|
66
|
+
loadProgramCache,
|
|
34
67
|
loadRules,
|
|
68
|
+
putCacheEntry,
|
|
69
|
+
renderCostReportMd,
|
|
35
70
|
renderTest,
|
|
71
|
+
renderTrustGraphMd,
|
|
72
|
+
rentExemptMinimum,
|
|
36
73
|
resolveRules,
|
|
37
74
|
runChecker,
|
|
75
|
+
saveProgramCache,
|
|
38
76
|
testKinds
|
|
39
77
|
};
|