brainblast 0.7.4 → 0.7.6
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/{chunk-5VYTURTO.js → chunk-A3U6JDMN.js} +64 -12
- package/dist/chunk-CSYGLMZR.js +129 -0
- package/dist/{chunk-5H5JQXXU.js → chunk-Q5DMQN67.js} +109 -25
- package/dist/cli.js +84 -5
- package/dist/feeConfigs-S4E5GQ7Q.js +19 -0
- package/dist/index.d.ts +39 -1
- package/dist/index.js +27 -7
- package/dist/{mcp-EOTWSQK7.js → mcp-O45PSOVU.js} +1 -1
- package/dist/packs/README.md +33 -0
- package/dist/packs/jito-bundle-zero-tip/README.md +33 -0
- package/dist/packs/jito-bundle-zero-tip/brainblast-pack.yaml +5 -0
- package/dist/packs/jito-bundle-zero-tip/fixtures/jito-bundle-zero-tip/fixed/bundle.ts +13 -0
- package/dist/packs/jito-bundle-zero-tip/fixtures/jito-bundle-zero-tip/vulnerable/bundle.ts +16 -0
- package/dist/packs/jito-bundle-zero-tip/rules/jito-bundle-zero-tip.yaml +54 -0
- package/dist/packs/jupiter-quote-zero-slippage/brainblast-pack.yaml +5 -0
- package/dist/packs/jupiter-quote-zero-slippage/fixtures/jupiter-quote-zero-slippage/fixed/arb.ts +17 -0
- package/dist/packs/jupiter-quote-zero-slippage/fixtures/jupiter-quote-zero-slippage/vulnerable/arb.ts +17 -0
- package/dist/packs/jupiter-quote-zero-slippage/rules/jupiter-quote-zero-slippage.yaml +51 -0
- package/dist/packs/metaplex-nft-royalty-zero/README.md +39 -0
- package/dist/packs/metaplex-nft-royalty-zero/brainblast-pack.yaml +5 -0
- package/dist/packs/metaplex-nft-royalty-zero/fixtures/metaplex-nft-royalty-zero/fixed/mint.ts +11 -0
- package/dist/packs/metaplex-nft-royalty-zero/fixtures/metaplex-nft-royalty-zero/vulnerable/mint.ts +11 -0
- package/dist/packs/metaplex-nft-royalty-zero/rules/metaplex-nft-royalty-zero.yaml +39 -0
- package/dist/packs/meteora-dlmm-zero-min-out/README.md +32 -0
- package/dist/packs/meteora-dlmm-zero-min-out/brainblast-pack.yaml +5 -0
- package/dist/packs/meteora-dlmm-zero-min-out/fixtures/meteora-dlmm-zero-min-out/fixed/swap.ts +19 -0
- package/dist/packs/meteora-dlmm-zero-min-out/fixtures/meteora-dlmm-zero-min-out/vulnerable/swap.ts +19 -0
- package/dist/packs/meteora-dlmm-zero-min-out/rules/meteora-dlmm-zero-min-out.yaml +47 -0
- package/dist/packs/pyth-price-unchecked-staleness/README.md +34 -0
- package/dist/packs/pyth-price-unchecked-staleness/brainblast-pack.yaml +5 -0
- package/dist/packs/pyth-price-unchecked-staleness/fixtures/pyth-price-unchecked-staleness/fixed/price.ts +14 -0
- package/dist/packs/pyth-price-unchecked-staleness/fixtures/pyth-price-unchecked-staleness/vulnerable/price.ts +14 -0
- package/dist/packs/pyth-price-unchecked-staleness/rules/pyth-price-unchecked-staleness.yaml +47 -0
- package/dist/packs/raydium-compute-zero-slippage/README.md +43 -0
- package/dist/packs/raydium-compute-zero-slippage/brainblast-pack.yaml +5 -0
- package/dist/packs/raydium-compute-zero-slippage/fixtures/raydium-compute-zero-slippage/fixed/swap.ts +12 -0
- package/dist/packs/raydium-compute-zero-slippage/fixtures/raydium-compute-zero-slippage/vulnerable/swap.ts +12 -0
- package/dist/packs/raydium-compute-zero-slippage/rules/raydium-compute-zero-slippage.yaml +40 -0
- package/dist/packs/solana-sendtx-unconfirmed/README.md +34 -0
- package/dist/packs/solana-sendtx-unconfirmed/brainblast-pack.yaml +5 -0
- package/dist/packs/solana-sendtx-unconfirmed/fixtures/solana-sendtx-unconfirmed/fixed/payment.ts +10 -0
- package/dist/packs/solana-sendtx-unconfirmed/fixtures/solana-sendtx-unconfirmed/vulnerable/payment.ts +10 -0
- package/dist/packs/solana-sendtx-unconfirmed/rules/solana-sendtx-unconfirmed.yaml +40 -0
- package/dist/packs/spl-transfer-not-checked-in-payout/brainblast-pack.yaml +5 -0
- package/dist/packs/spl-transfer-not-checked-in-payout/fixtures/spl-transfer-not-checked-in-payout/fixed/payout-processor.ts +29 -0
- package/dist/packs/spl-transfer-not-checked-in-payout/fixtures/spl-transfer-not-checked-in-payout/vulnerable/payout-processor.ts +22 -0
- package/dist/packs/spl-transfer-not-checked-in-payout/rules/spl-transfer-not-checked-in-payout.yaml +49 -0
- package/dist/rules/metaplex-seller-fee-zero.yaml +41 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadPack,
|
|
8
8
|
resolveRules,
|
|
9
9
|
walk
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Q5DMQN67.js";
|
|
11
11
|
|
|
12
12
|
// src/costAnalysis.ts
|
|
13
13
|
import { Project, SyntaxKind } from "ts-morph";
|
|
@@ -521,12 +521,62 @@ function validatePack(dir) {
|
|
|
521
521
|
return { manifest, rules, ruleResults, ok };
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
// src/bundledPacks.ts
|
|
525
|
+
import { existsSync as existsSync2, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
|
|
526
|
+
import { join as join2 } from "path";
|
|
527
|
+
import { fileURLToPath } from "url";
|
|
528
|
+
import { parse } from "yaml";
|
|
529
|
+
function packsRoot() {
|
|
530
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
531
|
+
const candidates = [
|
|
532
|
+
join2(here, "packs"),
|
|
533
|
+
// dist/packs (npm — copied by postbuild)
|
|
534
|
+
join2(here, "..", "..", "..", "packs"),
|
|
535
|
+
// src/../../../packs (dev — repo root)
|
|
536
|
+
join2(here, "..", "packs")
|
|
537
|
+
// fallback
|
|
538
|
+
];
|
|
539
|
+
return candidates.find((p) => existsSync2(p)) ?? null;
|
|
540
|
+
}
|
|
541
|
+
function listBundledPacks() {
|
|
542
|
+
const root = packsRoot();
|
|
543
|
+
if (!root) return [];
|
|
544
|
+
const out = [];
|
|
545
|
+
for (const entry of readdirSync(root)) {
|
|
546
|
+
const dir = join2(root, entry);
|
|
547
|
+
const manifestPath = join2(dir, PACK_MANIFEST_FILE);
|
|
548
|
+
let isDir = false;
|
|
549
|
+
try {
|
|
550
|
+
isDir = statSync(dir).isDirectory();
|
|
551
|
+
} catch {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (!isDir || !existsSync2(manifestPath)) continue;
|
|
555
|
+
try {
|
|
556
|
+
const manifest = parse(readFileSync2(manifestPath, "utf8"));
|
|
557
|
+
if (manifest?.id) out.push({ id: manifest.id, dir, manifest });
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
562
|
+
}
|
|
563
|
+
function resolveBundledPackToken(token) {
|
|
564
|
+
const packs = listBundledPacks();
|
|
565
|
+
const exact = packs.find((p) => p.id === token);
|
|
566
|
+
if (exact) return exact.dir;
|
|
567
|
+
const lead = packs.filter((p) => p.id === token || p.id.startsWith(token + "-"));
|
|
568
|
+
if (lead.length === 1) return lead[0].dir;
|
|
569
|
+
const sub = packs.filter((p) => p.id.includes(token));
|
|
570
|
+
if (sub.length === 1) return sub[0].dir;
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
|
|
524
574
|
// src/telemetry.ts
|
|
525
575
|
import { createHash, randomUUID } from "crypto";
|
|
526
|
-
import { appendFileSync, existsSync as
|
|
576
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
527
577
|
import { execFileSync } from "child_process";
|
|
528
578
|
import { homedir } from "os";
|
|
529
|
-
import { dirname, join as
|
|
579
|
+
import { dirname, join as join3, resolve } from "path";
|
|
530
580
|
function sha256Hex(s) {
|
|
531
581
|
return createHash("sha256").update(s).digest("hex");
|
|
532
582
|
}
|
|
@@ -534,20 +584,20 @@ function isTelemetryEnabled(targetDir) {
|
|
|
534
584
|
const env = process.env.BRAINBLAST_TELEMETRY;
|
|
535
585
|
if (env === "1" || env === "true") return true;
|
|
536
586
|
if (env === "0" || env === "false") return false;
|
|
537
|
-
const configPath =
|
|
538
|
-
if (!
|
|
587
|
+
const configPath = join3(targetDir, ".agent-research", "config.json");
|
|
588
|
+
if (!existsSync3(configPath)) return false;
|
|
539
589
|
try {
|
|
540
|
-
const cfg = JSON.parse(
|
|
590
|
+
const cfg = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
541
591
|
return cfg?.telemetry === true;
|
|
542
592
|
} catch {
|
|
543
593
|
return false;
|
|
544
594
|
}
|
|
545
595
|
}
|
|
546
596
|
function getUserHash() {
|
|
547
|
-
const idPath =
|
|
597
|
+
const idPath = join3(homedir(), ".brainblast", "telemetry-id");
|
|
548
598
|
let id;
|
|
549
|
-
if (
|
|
550
|
-
id =
|
|
599
|
+
if (existsSync3(idPath)) {
|
|
600
|
+
id = readFileSync3(idPath, "utf8").trim();
|
|
551
601
|
} else {
|
|
552
602
|
id = randomUUID();
|
|
553
603
|
mkdirSync2(dirname(idPath), { recursive: true });
|
|
@@ -569,15 +619,15 @@ function getRepoHash(targetDir) {
|
|
|
569
619
|
return sha256Hex(key).slice(0, 16);
|
|
570
620
|
}
|
|
571
621
|
function telemetryFilePath(targetDir) {
|
|
572
|
-
return
|
|
622
|
+
return join3(targetDir, ".agent-research", "telemetry.ndjson");
|
|
573
623
|
}
|
|
574
624
|
var DEFAULT_REGISTRY_URL = "https://registry.brainblast.tech";
|
|
575
625
|
async function submitTelemetry(targetDir, registryUrl = process.env.BRAINBLAST_REGISTRY_URL || DEFAULT_REGISTRY_URL) {
|
|
576
626
|
const file = telemetryFilePath(targetDir);
|
|
577
|
-
if (!
|
|
627
|
+
if (!existsSync3(file)) {
|
|
578
628
|
return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
|
|
579
629
|
}
|
|
580
|
-
const events =
|
|
630
|
+
const events = readFileSync3(file, "utf8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
581
631
|
if (events.length === 0) {
|
|
582
632
|
return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
|
|
583
633
|
}
|
|
@@ -622,6 +672,8 @@ export {
|
|
|
622
672
|
applyDiffToFile,
|
|
623
673
|
initPack,
|
|
624
674
|
validatePack,
|
|
675
|
+
listBundledPacks,
|
|
676
|
+
resolveBundledPackToken,
|
|
625
677
|
isTelemetryEnabled,
|
|
626
678
|
getUserHash,
|
|
627
679
|
getRepoHash,
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/feeConfigs.ts
|
|
2
|
+
var FEE_CONFIGS = [
|
|
3
|
+
{
|
|
4
|
+
id: "metaplex-seller-fee",
|
|
5
|
+
category: "royalty",
|
|
6
|
+
sdk: "Metaplex Token Metadata",
|
|
7
|
+
call: "createV1 / createNft / createFungible",
|
|
8
|
+
field: "sellerFeeBasisPoints",
|
|
9
|
+
whatZeroMeans: "Omitted \u2192 defaults to 0. Creators earn no royalty on any secondary sale, permanently \u2014 the token mints fine and looks correct on-chain.",
|
|
10
|
+
fix: "Set sellerFeeBasisPoints explicitly at mint time (e.g. 500 = 5%). There is no after-the-fact migration once minted.",
|
|
11
|
+
ruleId: "metaplex-seller-fee-zero",
|
|
12
|
+
status: "enforced",
|
|
13
|
+
docsUrl: "https://developers.metaplex.com/token-metadata/mint"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "bags-fee-share-creator",
|
|
17
|
+
category: "fee",
|
|
18
|
+
sdk: "Bags",
|
|
19
|
+
call: "createBagsFeeShareConfig",
|
|
20
|
+
field: "feeClaimers[].userBps (creator inclusion)",
|
|
21
|
+
whatZeroMeans: "The creator wallet omitted from feeClaimers, or the userBps not summing to 10000 \u2192 the creator's share of trading fees is silently zero. This is the original Bags trap.",
|
|
22
|
+
fix: "Include the creator wallet as a feeClaimers entry and ensure every userBps sums to 10000.",
|
|
23
|
+
ruleId: "bags-fee-share-creator-included",
|
|
24
|
+
status: "enforced",
|
|
25
|
+
docsUrl: "https://bags.fm"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "token2022-transfer-fee",
|
|
29
|
+
category: "fee",
|
|
30
|
+
sdk: "SPL Token-2022 (Transfer Fee extension)",
|
|
31
|
+
call: "createInitializeTransferFeeConfigInstruction",
|
|
32
|
+
field: "transferFeeBasisPoints",
|
|
33
|
+
whatZeroMeans: "Initializing the transfer-fee extension with 0 basis points \u2192 no fee is ever withheld on transfers. The extension is 'configured' but collects nothing.",
|
|
34
|
+
fix: "Pass a non-zero transferFeeBasisPoints (and a sensible maximumFee) when initializing the extension.",
|
|
35
|
+
// Positional-argument call — not an object-literal field, so the bundled
|
|
36
|
+
// object-field checker doesn't cover it yet. Advisory until a positional
|
|
37
|
+
// variant ships.
|
|
38
|
+
ruleId: null,
|
|
39
|
+
status: "advisory",
|
|
40
|
+
docsUrl: "https://www.solana-program.com/docs/token-2022/extensions#transfer-fee"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "reward-rate-zero",
|
|
44
|
+
category: "reward",
|
|
45
|
+
sdk: "Staking / LP reward distributors (generic)",
|
|
46
|
+
call: "initialize / configureReward (varies)",
|
|
47
|
+
field: "rewardRate / emissionsPerSecond",
|
|
48
|
+
whatZeroMeans: "A reward-rate or emissions field omitted/zeroed \u2192 stakers and LPs accrue nothing while the pool looks live. A silent, ongoing zero-yield misconfiguration.",
|
|
49
|
+
fix: "Set the reward-rate field explicitly and assert it is non-zero in your deploy script; add a project-local fee-configs-zero-or-missing rule for your SDK's call shape.",
|
|
50
|
+
ruleId: null,
|
|
51
|
+
status: "advisory"
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
function getFeeConfig(id) {
|
|
55
|
+
return FEE_CONFIGS.find((e) => e.id === id || e.ruleId === id);
|
|
56
|
+
}
|
|
57
|
+
function feeConfigsByCategory(cat) {
|
|
58
|
+
return FEE_CONFIGS.filter((e) => e.category === cat);
|
|
59
|
+
}
|
|
60
|
+
function enforcedCount(patterns = FEE_CONFIGS) {
|
|
61
|
+
return patterns.filter((e) => e.status === "enforced").length;
|
|
62
|
+
}
|
|
63
|
+
var CATEGORY_LABEL = {
|
|
64
|
+
royalty: "Royalties",
|
|
65
|
+
fee: "Fees",
|
|
66
|
+
reward: "Reward distribution"
|
|
67
|
+
};
|
|
68
|
+
function renderFeeConfigsMd(patterns = FEE_CONFIGS) {
|
|
69
|
+
const L = ["## Fee Configs \u2014 silent zero-revenue class\n"];
|
|
70
|
+
L.push(
|
|
71
|
+
"The Bags exploit generalized: a revenue-bearing field that, if omitted or zeroed, silently collects nothing \u2014 forever. Watch these across every protocol that touches fees, royalties, or rewards.\n"
|
|
72
|
+
);
|
|
73
|
+
L.push("| Category | SDK | Field | Status | Rule |");
|
|
74
|
+
L.push("|----------|-----|-------|--------|------|");
|
|
75
|
+
for (const e of patterns) {
|
|
76
|
+
const status = e.status === "enforced" ? "\u2705 enforced" : "\u26A0\uFE0F advisory";
|
|
77
|
+
L.push(`| ${CATEGORY_LABEL[e.category]} | ${e.sdk} | \`${e.field}\` | ${status} | ${e.ruleId ? `\`${e.ruleId}\`` : "\u2014"} |`);
|
|
78
|
+
}
|
|
79
|
+
L.push("");
|
|
80
|
+
L.push(`**${enforcedCount(patterns)} of ${patterns.length} enforced by a bundled rule; the rest are advisories (grep targets / project-local rule candidates).**
|
|
81
|
+
`);
|
|
82
|
+
for (const e of patterns) {
|
|
83
|
+
L.push(`### ${e.sdk} \u2014 \`${e.field}\` (${CATEGORY_LABEL[e.category]})
|
|
84
|
+
`);
|
|
85
|
+
L.push(`- **Call:** \`${e.call}\``);
|
|
86
|
+
L.push(`- **Zero/omitted means:** ${e.whatZeroMeans}`);
|
|
87
|
+
L.push(`- **Fix:** ${e.fix}`);
|
|
88
|
+
L.push(`- **Detection:** ${e.ruleId ? `\`${e.ruleId}\` (bundled)` : "advisory \u2014 no bundled rule yet"}`);
|
|
89
|
+
if (e.docsUrl) L.push(`- **Docs:** ${e.docsUrl}`);
|
|
90
|
+
L.push("");
|
|
91
|
+
}
|
|
92
|
+
return L.join("\n");
|
|
93
|
+
}
|
|
94
|
+
function renderFeeConfigsText(patterns = FEE_CONFIGS) {
|
|
95
|
+
const L = [];
|
|
96
|
+
L.push("\u2500\u2500 Fee Configs \u2014 silent zero-revenue class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
97
|
+
L.push(" fields that default to zero and silently collect nothing");
|
|
98
|
+
L.push("");
|
|
99
|
+
for (const e of patterns) {
|
|
100
|
+
const status = e.status === "enforced" ? "[enforced]" : "[advisory]";
|
|
101
|
+
L.push(` ${status} ${CATEGORY_LABEL[e.category]}: ${e.sdk} \xB7 ${e.field}`);
|
|
102
|
+
L.push(` ${e.whatZeroMeans}`);
|
|
103
|
+
if (e.ruleId) L.push(` rule: ${e.ruleId}`);
|
|
104
|
+
L.push("");
|
|
105
|
+
}
|
|
106
|
+
L.push(` ${enforcedCount(patterns)}/${patterns.length} enforced by a bundled rule`);
|
|
107
|
+
return L.join("\n");
|
|
108
|
+
}
|
|
109
|
+
function renderFeeConfigDetailText(e) {
|
|
110
|
+
const L = [];
|
|
111
|
+
L.push(`\u2500\u2500 ${e.sdk} \u2014 ${e.field} \u2500\u2500`);
|
|
112
|
+
L.push(` category: ${CATEGORY_LABEL[e.category]}`);
|
|
113
|
+
L.push(` call: ${e.call}`);
|
|
114
|
+
L.push(` zero means: ${e.whatZeroMeans}`);
|
|
115
|
+
L.push(` fix: ${e.fix}`);
|
|
116
|
+
L.push(` detection: ${e.ruleId ? `${e.ruleId} (bundled rule)` : "advisory \u2014 no bundled rule yet"}`);
|
|
117
|
+
if (e.docsUrl) L.push(` docs: ${e.docsUrl}`);
|
|
118
|
+
return L.join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
FEE_CONFIGS,
|
|
123
|
+
getFeeConfig,
|
|
124
|
+
feeConfigsByCategory,
|
|
125
|
+
enforcedCount,
|
|
126
|
+
renderFeeConfigsMd,
|
|
127
|
+
renderFeeConfigsText,
|
|
128
|
+
renderFeeConfigDetailText
|
|
129
|
+
};
|
|
@@ -430,6 +430,19 @@ var objectArgPropertyLiteralEquals = (c, p) => {
|
|
|
430
430
|
|
|
431
431
|
// src/checkers/objectArgPropertyForbiddenLiteral.ts
|
|
432
432
|
import { SyntaxKind as SyntaxKind7 } from "ts-morph";
|
|
433
|
+
function isBnWrappedZero(init) {
|
|
434
|
+
const isNewOrCall = init.getKind() === SyntaxKind7.NewExpression || init.getKind() === SyntaxKind7.CallExpression;
|
|
435
|
+
if (!isNewOrCall) return false;
|
|
436
|
+
const calleeText = init.getExpression?.()?.getText?.() ?? "";
|
|
437
|
+
if (!/(^|\.)bn$/i.test(calleeText)) return false;
|
|
438
|
+
const args = init.getArguments?.() ?? [];
|
|
439
|
+
if (args.length !== 1) return false;
|
|
440
|
+
const a = args[0];
|
|
441
|
+
const k = a.getKind();
|
|
442
|
+
if (k === SyntaxKind7.NumericLiteral) return Number(a.getLiteralValue()) === 0;
|
|
443
|
+
if (k === SyntaxKind7.StringLiteral) return a.getLiteralValue() === "0";
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
433
446
|
var objectArgPropertyForbiddenLiteral = (c, p) => {
|
|
434
447
|
const calls = c.fn.getDescendantsOfKind(SyntaxKind7.CallExpression).filter((ce2) => {
|
|
435
448
|
const expr = ce2.getExpression();
|
|
@@ -469,7 +482,8 @@ var objectArgPropertyForbiddenLiteral = (c, p) => {
|
|
|
469
482
|
const kind = init.getKind();
|
|
470
483
|
const text = init.getText();
|
|
471
484
|
const forbidden = JSON.stringify(p.forbiddenValue);
|
|
472
|
-
const
|
|
485
|
+
const isLiteralForbidden = (kind === SyntaxKind7.NumericLiteral || kind === SyntaxKind7.StringLiteral) && (text === forbidden || text === String(p.forbiddenValue));
|
|
486
|
+
const isForbidden = isLiteralForbidden || p.forbiddenValue === 0 && isBnWrappedZero(init);
|
|
473
487
|
if (isForbidden) {
|
|
474
488
|
return {
|
|
475
489
|
result: "fail",
|
|
@@ -1050,6 +1064,75 @@ function anchorCpiUnverifiedProgram(c, p) {
|
|
|
1050
1064
|
};
|
|
1051
1065
|
}
|
|
1052
1066
|
|
|
1067
|
+
// src/checkers/feeConfigsZeroOrMissing.ts
|
|
1068
|
+
import { SyntaxKind as SyntaxKind12 } from "ts-morph";
|
|
1069
|
+
function callName6(call) {
|
|
1070
|
+
const exp = call.getExpression();
|
|
1071
|
+
if (exp.getKind() === SyntaxKind12.Identifier) return exp.getText();
|
|
1072
|
+
if (exp.getKind() === SyntaxKind12.PropertyAccessExpression) {
|
|
1073
|
+
return exp.asKind(SyntaxKind12.PropertyAccessExpression).getName();
|
|
1074
|
+
}
|
|
1075
|
+
return "";
|
|
1076
|
+
}
|
|
1077
|
+
function unwrap(expr) {
|
|
1078
|
+
let e = expr;
|
|
1079
|
+
while (e) {
|
|
1080
|
+
const k = e.getKind();
|
|
1081
|
+
if (k === SyntaxKind12.AsExpression || k === SyntaxKind12.ParenthesizedExpression || k === SyntaxKind12.TypeAssertionExpression || k === SyntaxKind12.NonNullExpression) {
|
|
1082
|
+
e = e.getExpression();
|
|
1083
|
+
} else break;
|
|
1084
|
+
}
|
|
1085
|
+
return e;
|
|
1086
|
+
}
|
|
1087
|
+
function isLiteralZero(expr) {
|
|
1088
|
+
const u = unwrap(expr);
|
|
1089
|
+
if (!u) return false;
|
|
1090
|
+
const num = u.asKind(SyntaxKind12.NumericLiteral);
|
|
1091
|
+
if (num) return Number(num.getLiteralValue()) === 0;
|
|
1092
|
+
const big = u.asKind(SyntaxKind12.BigIntLiteral);
|
|
1093
|
+
if (big) return /^0n?$/.test(big.getText().replace(/_/g, ""));
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
var feeConfigsZeroOrMissing = (c, p) => {
|
|
1097
|
+
const names = Array.isArray(p.calls) ? p.calls : [p.calls];
|
|
1098
|
+
const field = p.field;
|
|
1099
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind12.CallExpression).filter((x) => names.includes(callName6(x)));
|
|
1100
|
+
if (calls.length === 0) {
|
|
1101
|
+
return {
|
|
1102
|
+
result: "cant_tell",
|
|
1103
|
+
detail: p.absentDetail ?? `No call to ${names.join("/")} in '${c.fnName}'; fee-configs check does not apply.`
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
for (const call of calls) {
|
|
1107
|
+
const obj = call.getArguments().map((a) => unwrap(a)?.asKind(SyntaxKind12.ObjectLiteralExpression)).find((o) => !!o);
|
|
1108
|
+
if (!obj) continue;
|
|
1109
|
+
const member = obj.getProperty(field);
|
|
1110
|
+
if (!member) {
|
|
1111
|
+
return {
|
|
1112
|
+
result: "fail",
|
|
1113
|
+
detail: p.omittedDetail ?? `'${field}' is omitted from the ${callName6(call)} config \u2014 it defaults to zero. This is a permanent, silent zero-revenue misconfiguration: the call succeeds and no value is ever collected. Set '${field}' explicitly.`
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
const pa = member.asKind(SyntaxKind12.PropertyAssignment);
|
|
1117
|
+
const shorthand = member.asKind(SyntaxKind12.ShorthandPropertyAssignment);
|
|
1118
|
+
const init = pa?.getInitializer() ?? shorthand?.getNameNode();
|
|
1119
|
+
if (isLiteralZero(init)) {
|
|
1120
|
+
return {
|
|
1121
|
+
result: "fail",
|
|
1122
|
+
detail: p.zeroDetail ?? `'${field}' is set to a literal 0 in the ${callName6(call)} config \u2014 zero revenue will ever be collected. If intentional, this is a footgun; otherwise set a non-zero value.`
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
result: "pass",
|
|
1127
|
+
detail: p.passDetail ?? `'${field}' is set to a non-zero value in the ${callName6(call)} config.`
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
result: "cant_tell",
|
|
1132
|
+
detail: p.absentDetail ?? `Call to ${names.join("/")} found but its config argument isn't an object literal; can't validate '${field}'.`
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1053
1136
|
// src/checkers/index.ts
|
|
1054
1137
|
var registry = {
|
|
1055
1138
|
"positional-arg-identity": positionalArgIdentity,
|
|
@@ -1068,7 +1151,8 @@ var registry = {
|
|
|
1068
1151
|
"anchor-account-missing-constraint": anchorAccountMissingConstraint,
|
|
1069
1152
|
"anchor-forbidden-account-type": anchorForbiddenAccountType,
|
|
1070
1153
|
"anchor-body-call-pattern": anchorBodyCallPattern,
|
|
1071
|
-
"anchor-cpi-unverified-program": anchorCpiUnverifiedProgram
|
|
1154
|
+
"anchor-cpi-unverified-program": anchorCpiUnverifiedProgram,
|
|
1155
|
+
"fee-configs-zero-or-missing": feeConfigsZeroOrMissing
|
|
1072
1156
|
};
|
|
1073
1157
|
function runChecker(kind, c, params) {
|
|
1074
1158
|
const fn = registry[kind];
|
|
@@ -1284,7 +1368,7 @@ function findRustCandidates(targetDir, rule) {
|
|
|
1284
1368
|
}
|
|
1285
1369
|
|
|
1286
1370
|
// src/fixers/positionalArgIdentity.ts
|
|
1287
|
-
import { SyntaxKind as
|
|
1371
|
+
import { SyntaxKind as SyntaxKind13 } from "ts-morph";
|
|
1288
1372
|
|
|
1289
1373
|
// src/fixers/diffUtil.ts
|
|
1290
1374
|
function buildDiff(node, replacement) {
|
|
@@ -1309,9 +1393,9 @@ function buildDiff(node, replacement) {
|
|
|
1309
1393
|
// src/fixers/positionalArgIdentity.ts
|
|
1310
1394
|
var fixPositionalArgIdentity = (c, p, outcome) => {
|
|
1311
1395
|
if (outcome.result !== "fail") return void 0;
|
|
1312
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1396
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind13.CallExpression).filter((call) => {
|
|
1313
1397
|
const exp = call.getExpression();
|
|
1314
|
-
return exp.getKind() ===
|
|
1398
|
+
return exp.getKind() === SyntaxKind13.PropertyAccessExpression && exp.asKind(SyntaxKind13.PropertyAccessExpression).getName() === p.call;
|
|
1315
1399
|
});
|
|
1316
1400
|
if (calls.length === 0) {
|
|
1317
1401
|
const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
|
|
@@ -1326,7 +1410,7 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
1326
1410
|
}
|
|
1327
1411
|
const arg = calls[0].getArguments()[p.argIndex];
|
|
1328
1412
|
const wantParam = c.params[p.paramIndex];
|
|
1329
|
-
if (arg && wantParam && arg.getKind() ===
|
|
1413
|
+
if (arg && wantParam && arg.getKind() === SyntaxKind13.CallExpression) {
|
|
1330
1414
|
return {
|
|
1331
1415
|
summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
|
|
1332
1416
|
diff: buildDiff(arg, wantParam)
|
|
@@ -1336,12 +1420,12 @@ Do not call JSON.parse() on the body before this verification step.`
|
|
|
1336
1420
|
};
|
|
1337
1421
|
|
|
1338
1422
|
// src/fixers/requiredCallWithOptions.ts
|
|
1339
|
-
import { SyntaxKind as
|
|
1340
|
-
function
|
|
1423
|
+
import { SyntaxKind as SyntaxKind14 } from "ts-morph";
|
|
1424
|
+
function callName7(call) {
|
|
1341
1425
|
const exp = call.getExpression();
|
|
1342
|
-
if (exp.getKind() ===
|
|
1343
|
-
if (exp.getKind() ===
|
|
1344
|
-
return exp.asKind(
|
|
1426
|
+
if (exp.getKind() === SyntaxKind14.Identifier) return exp.getText();
|
|
1427
|
+
if (exp.getKind() === SyntaxKind14.PropertyAccessExpression) {
|
|
1428
|
+
return exp.asKind(SyntaxKind14.PropertyAccessExpression).getName();
|
|
1345
1429
|
}
|
|
1346
1430
|
return "";
|
|
1347
1431
|
}
|
|
@@ -1359,15 +1443,15 @@ function placeholderFor(propName) {
|
|
|
1359
1443
|
}
|
|
1360
1444
|
var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
1361
1445
|
if (outcome.result !== "fail") return void 0;
|
|
1362
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1363
|
-
const verify = calls.filter((x) => p.verifyCalls.includes(
|
|
1446
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind14.CallExpression);
|
|
1447
|
+
const verify = calls.filter((x) => p.verifyCalls.includes(callName7(x)));
|
|
1364
1448
|
if (verify.length > 0) {
|
|
1365
1449
|
const call = verify[0];
|
|
1366
1450
|
const args = call.getArguments();
|
|
1367
1451
|
const lastArg = args[args.length - 1];
|
|
1368
|
-
const obj = lastArg?.asKind(
|
|
1452
|
+
const obj = lastArg?.asKind(SyntaxKind14.ObjectLiteralExpression);
|
|
1369
1453
|
const presentNames = obj ? obj.getProperties().map((pr) => {
|
|
1370
|
-
const pa = pr.asKind(
|
|
1454
|
+
const pa = pr.asKind(SyntaxKind14.PropertyAssignment) ?? pr.asKind(SyntaxKind14.ShorthandPropertyAssignment);
|
|
1371
1455
|
return pa?.getName() ?? "";
|
|
1372
1456
|
}) : [];
|
|
1373
1457
|
const missingGroups = p.requiredProps.filter(
|
|
@@ -1375,7 +1459,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
1375
1459
|
);
|
|
1376
1460
|
if (missingGroups.length === 0) return void 0;
|
|
1377
1461
|
const newProps = missingGroups.map((g) => placeholderFor(g[0])).join(", ");
|
|
1378
|
-
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${
|
|
1462
|
+
const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${callName7(call)} call`;
|
|
1379
1463
|
if (obj) {
|
|
1380
1464
|
const inner = obj.getText().slice(1, -1).trim();
|
|
1381
1465
|
const newText = inner.length > 0 ? `{ ${inner}, ${newProps} }` : `{ ${newProps} }`;
|
|
@@ -1387,7 +1471,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
|
|
|
1387
1471
|
}
|
|
1388
1472
|
return {
|
|
1389
1473
|
summary,
|
|
1390
|
-
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${
|
|
1474
|
+
suggestion: `Add an options object ({ ${newProps} }) as an argument to ${callName7(call)}.`
|
|
1391
1475
|
};
|
|
1392
1476
|
}
|
|
1393
1477
|
return {
|
|
@@ -1401,22 +1485,22 @@ JWKS must come from Privy's published JWKS endpoint for your app.`
|
|
|
1401
1485
|
};
|
|
1402
1486
|
|
|
1403
1487
|
// src/fixers/literalMultiplierWrongConstant.ts
|
|
1404
|
-
import { SyntaxKind as
|
|
1405
|
-
function
|
|
1488
|
+
import { SyntaxKind as SyntaxKind15 } from "ts-morph";
|
|
1489
|
+
function callName8(call) {
|
|
1406
1490
|
const exp = call.getExpression();
|
|
1407
|
-
if (exp.getKind() ===
|
|
1408
|
-
if (exp.getKind() ===
|
|
1409
|
-
return exp.asKind(
|
|
1491
|
+
if (exp.getKind() === SyntaxKind15.Identifier) return exp.getText();
|
|
1492
|
+
if (exp.getKind() === SyntaxKind15.PropertyAccessExpression) {
|
|
1493
|
+
return exp.asKind(SyntaxKind15.PropertyAccessExpression).getName();
|
|
1410
1494
|
}
|
|
1411
1495
|
return "";
|
|
1412
1496
|
}
|
|
1413
1497
|
function findIdentifier(node, name) {
|
|
1414
|
-
if (node.getKind() ===
|
|
1415
|
-
return node.getDescendantsOfKind(
|
|
1498
|
+
if (node.getKind() === SyntaxKind15.Identifier && node.getText() === name) return node;
|
|
1499
|
+
return node.getDescendantsOfKind(SyntaxKind15.Identifier).find((id) => id.getText() === name);
|
|
1416
1500
|
}
|
|
1417
1501
|
var fixLiteralMultiplierWrongConstant = (c, p, outcome) => {
|
|
1418
1502
|
if (outcome.result !== "fail") return void 0;
|
|
1419
|
-
const calls = c.fn.getDescendantsOfKind(
|
|
1503
|
+
const calls = c.fn.getDescendantsOfKind(SyntaxKind15.CallExpression).filter((call) => callName8(call) === p.call);
|
|
1420
1504
|
if (calls.length === 0) return void 0;
|
|
1421
1505
|
const arg = calls[0].getArguments()[p.argIndex];
|
|
1422
1506
|
if (!arg) return void 0;
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
initPack,
|
|
8
8
|
isTelemetryEnabled,
|
|
9
9
|
lamportsToSol,
|
|
10
|
+
listBundledPacks,
|
|
10
11
|
parseDiff,
|
|
11
12
|
recordGraduationEvents,
|
|
12
13
|
renderCostReportMd,
|
|
@@ -14,11 +15,12 @@ import {
|
|
|
14
15
|
renderExploitsMd,
|
|
15
16
|
renderExploitsText,
|
|
16
17
|
rentExemptMinimum,
|
|
18
|
+
resolveBundledPackToken,
|
|
17
19
|
startWatch,
|
|
18
20
|
submitTelemetry,
|
|
19
21
|
telemetryFilePath,
|
|
20
22
|
validatePack
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-A3U6JDMN.js";
|
|
22
24
|
import {
|
|
23
25
|
renderTrustGraphMd
|
|
24
26
|
} from "./chunk-QMJEZ6NO.js";
|
|
@@ -32,7 +34,7 @@ import {
|
|
|
32
34
|
audit,
|
|
33
35
|
getChangedRanges,
|
|
34
36
|
resolveRules
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-Q5DMQN67.js";
|
|
36
38
|
import "./chunk-2XJORJPQ.js";
|
|
37
39
|
import "./chunk-O5Z4ZJHC.js";
|
|
38
40
|
import "./chunk-XSVQSK53.js";
|
|
@@ -42,7 +44,7 @@ import {
|
|
|
42
44
|
import "./chunk-3RG5ZIWI.js";
|
|
43
45
|
|
|
44
46
|
// src/cli.ts
|
|
45
|
-
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
47
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
46
48
|
import { join as join3 } from "path";
|
|
47
49
|
|
|
48
50
|
// src/memory.ts
|
|
@@ -469,7 +471,16 @@ function parsePackDirs(argv) {
|
|
|
469
471
|
if (idx < 0) return [];
|
|
470
472
|
const value = argv[idx + 1];
|
|
471
473
|
if (!value || value.startsWith("--")) return [];
|
|
472
|
-
|
|
474
|
+
const tokens = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
475
|
+
return tokens.map((t) => {
|
|
476
|
+
if (existsSync3(join3(t, "brainblast-pack.yaml"))) return t;
|
|
477
|
+
const resolved = resolveBundledPackToken(t);
|
|
478
|
+
if (resolved) return resolved;
|
|
479
|
+
console.error(
|
|
480
|
+
`brainblast: --packs '${t}' is not a known bundled pack or a pack directory. Run 'brainblast packs' to list bundled packs.`
|
|
481
|
+
);
|
|
482
|
+
process.exit(2);
|
|
483
|
+
});
|
|
473
484
|
}
|
|
474
485
|
if (args[0] === "diff") {
|
|
475
486
|
await runDiff(args.slice(1));
|
|
@@ -480,7 +491,7 @@ if (args[0] === "drift") {
|
|
|
480
491
|
process.exit(0);
|
|
481
492
|
}
|
|
482
493
|
if (args[0] === "mcp") {
|
|
483
|
-
const { startMcpServer } = await import("./mcp-
|
|
494
|
+
const { startMcpServer } = await import("./mcp-O45PSOVU.js");
|
|
484
495
|
await startMcpServer();
|
|
485
496
|
process.exit(0);
|
|
486
497
|
}
|
|
@@ -492,6 +503,10 @@ if (args[0] === "pack") {
|
|
|
492
503
|
runPack(args.slice(1));
|
|
493
504
|
process.exit(0);
|
|
494
505
|
}
|
|
506
|
+
if (args[0] === "packs") {
|
|
507
|
+
runPacks(args.slice(1));
|
|
508
|
+
process.exit(0);
|
|
509
|
+
}
|
|
495
510
|
if (args[0] === "telemetry") {
|
|
496
511
|
await runTelemetry(args.slice(1));
|
|
497
512
|
process.exit(0);
|
|
@@ -567,6 +582,10 @@ if (args[0] === "oracle") {
|
|
|
567
582
|
await runOracle(args.slice(1));
|
|
568
583
|
process.exit(0);
|
|
569
584
|
}
|
|
585
|
+
if (args[0] === "fee-configs") {
|
|
586
|
+
await runFeeConfigs(args.slice(1));
|
|
587
|
+
process.exit(0);
|
|
588
|
+
}
|
|
570
589
|
if (args[0] === "fix") {
|
|
571
590
|
await runFix(args.slice(1));
|
|
572
591
|
process.exit(0);
|
|
@@ -704,6 +723,45 @@ function runDeployPlan(argv) {
|
|
|
704
723
|
writeFileSync2(mdPath, renderDeployPlanMd(plan));
|
|
705
724
|
console.log(` deploy plan: ${mdPath}`);
|
|
706
725
|
}
|
|
726
|
+
async function runFeeConfigs(argv) {
|
|
727
|
+
const {
|
|
728
|
+
FEE_CONFIGS,
|
|
729
|
+
getFeeConfig,
|
|
730
|
+
renderFeeConfigsText,
|
|
731
|
+
renderFeeConfigsMd,
|
|
732
|
+
renderFeeConfigDetailText
|
|
733
|
+
} = await import("./feeConfigs-S4E5GQ7Q.js");
|
|
734
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
735
|
+
console.log("usage: brainblast fee-configs [id] [--json]");
|
|
736
|
+
console.log(" Fee Config Validator: the silent zero-revenue class (fees, royalties,");
|
|
737
|
+
console.log(" rewards) \u2014 fields that default to zero and quietly collect nothing. Pass an");
|
|
738
|
+
console.log(" id to see one in detail. Known ids:");
|
|
739
|
+
console.log(` ${FEE_CONFIGS.map((e) => e.id).join(", ")}`);
|
|
740
|
+
process.exit(0);
|
|
741
|
+
}
|
|
742
|
+
const json = argv.includes("--json");
|
|
743
|
+
const id = argv.find((a) => !a.startsWith("--"));
|
|
744
|
+
if (id) {
|
|
745
|
+
const e = getFeeConfig(id);
|
|
746
|
+
if (!e) {
|
|
747
|
+
console.error(`error: no fee-config '${id}'. Known: ${FEE_CONFIGS.map((x) => x.id).join(", ")}`);
|
|
748
|
+
process.exit(2);
|
|
749
|
+
}
|
|
750
|
+
if (json) console.log(JSON.stringify(e, null, 2));
|
|
751
|
+
else console.log(renderFeeConfigDetailText(e));
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (json) {
|
|
755
|
+
console.log(JSON.stringify(FEE_CONFIGS, null, 2));
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
console.log(renderFeeConfigsText());
|
|
759
|
+
const outDir2 = join3(process.cwd(), ".agent-research");
|
|
760
|
+
mkdirSync2(outDir2, { recursive: true });
|
|
761
|
+
writeFileSync2(join3(outDir2, "fee-configs.md"), renderFeeConfigsMd());
|
|
762
|
+
console.log(`
|
|
763
|
+
catalog: ${join3(outDir2, "fee-configs.md")}`);
|
|
764
|
+
}
|
|
707
765
|
async function runOracle(argv) {
|
|
708
766
|
if (argv.includes("--help") || argv.includes("-h") || argv.filter((a) => !a.startsWith("--")).length === 0) {
|
|
709
767
|
console.log("usage: brainblast oracle <account> [--rpc URL] [--max-staleness-slots N | --max-staleness-seconds N] [--json]");
|
|
@@ -779,6 +837,27 @@ function runExploits(argv) {
|
|
|
779
837
|
console.log(`
|
|
780
838
|
database: ${mdPath}`);
|
|
781
839
|
}
|
|
840
|
+
function runPacks(argv) {
|
|
841
|
+
const packs = listBundledPacks();
|
|
842
|
+
if (argv.includes("--json")) {
|
|
843
|
+
console.log(JSON.stringify(packs.map((p) => ({ ...p.manifest, dir: p.dir })), null, 2));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (packs.length === 0) {
|
|
847
|
+
console.log("No bundled protocol packs found.");
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
console.log("Protocol Pack Library \u2014 opt into the exact stack you build on:\n");
|
|
851
|
+
console.log(" brainblast --packs <name>[,<name>...] .\n");
|
|
852
|
+
for (const p of packs) {
|
|
853
|
+
const lead = p.id.split("-")[0];
|
|
854
|
+
const shortName = resolveBundledPackToken(lead) === p.dir ? lead : p.id;
|
|
855
|
+
console.log(` ${shortName.padEnd(12)} ${p.manifest.name}`);
|
|
856
|
+
if (p.manifest.description) console.log(` ${" ".repeat(12)} ${p.manifest.description}`);
|
|
857
|
+
console.log("");
|
|
858
|
+
}
|
|
859
|
+
console.log(`${packs.length} pack(s). Each ships RED\u2192GREEN fixtures; run 'brainblast pack validate <dir>' to verify.`);
|
|
860
|
+
}
|
|
782
861
|
function runPack(argv) {
|
|
783
862
|
const sub = argv[0];
|
|
784
863
|
if (sub === "init") {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FEE_CONFIGS,
|
|
3
|
+
enforcedCount,
|
|
4
|
+
feeConfigsByCategory,
|
|
5
|
+
getFeeConfig,
|
|
6
|
+
renderFeeConfigDetailText,
|
|
7
|
+
renderFeeConfigsMd,
|
|
8
|
+
renderFeeConfigsText
|
|
9
|
+
} from "./chunk-CSYGLMZR.js";
|
|
10
|
+
import "./chunk-3RG5ZIWI.js";
|
|
11
|
+
export {
|
|
12
|
+
FEE_CONFIGS,
|
|
13
|
+
enforcedCount,
|
|
14
|
+
feeConfigsByCategory,
|
|
15
|
+
getFeeConfig,
|
|
16
|
+
renderFeeConfigDetailText,
|
|
17
|
+
renderFeeConfigsMd,
|
|
18
|
+
renderFeeConfigsText
|
|
19
|
+
};
|