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
package/dist/index.d.ts
CHANGED
|
@@ -918,6 +918,44 @@ declare function checkOracleFreshness(account: string, opts?: OracleOpts): Promi
|
|
|
918
918
|
declare function renderOracleText(f: OracleFreshness): string;
|
|
919
919
|
declare function renderOracleMd(f: OracleFreshness): string;
|
|
920
920
|
|
|
921
|
+
interface BundledPack {
|
|
922
|
+
id: string;
|
|
923
|
+
dir: string;
|
|
924
|
+
manifest: PackManifest;
|
|
925
|
+
}
|
|
926
|
+
declare function listBundledPacks(): BundledPack[];
|
|
927
|
+
declare function resolveBundledPackToken(token: string): string | null;
|
|
928
|
+
|
|
929
|
+
type FeeConfigCategory = "royalty" | "fee" | "reward";
|
|
930
|
+
type FeeConfigStatus = "enforced" | "advisory";
|
|
931
|
+
interface FeeConfig {
|
|
932
|
+
/** Stable slug. */
|
|
933
|
+
id: string;
|
|
934
|
+
category: FeeConfigCategory;
|
|
935
|
+
/** SDK / protocol the field belongs to. */
|
|
936
|
+
sdk: string;
|
|
937
|
+
/** The setup/config call that takes the field. */
|
|
938
|
+
call: string;
|
|
939
|
+
/** The revenue-bearing field that silently defaults to zero. */
|
|
940
|
+
field: string;
|
|
941
|
+
/** What a zero/omitted value costs you, in one sentence. */
|
|
942
|
+
whatZeroMeans: string;
|
|
943
|
+
/** The safe pattern. */
|
|
944
|
+
fix: string;
|
|
945
|
+
/** Bundled rule that detects this, or null for an advisory entry. */
|
|
946
|
+
ruleId: string | null;
|
|
947
|
+
status: FeeConfigStatus;
|
|
948
|
+
/** Optional reference / docs URL. */
|
|
949
|
+
docsUrl?: string;
|
|
950
|
+
}
|
|
951
|
+
declare const FEE_CONFIGS: FeeConfig[];
|
|
952
|
+
declare function getFeeConfig(id: string): FeeConfig | undefined;
|
|
953
|
+
declare function feeConfigsByCategory(cat: FeeConfigCategory): FeeConfig[];
|
|
954
|
+
declare function enforcedCount(patterns?: FeeConfig[]): number;
|
|
955
|
+
declare function renderFeeConfigsMd(patterns?: FeeConfig[]): string;
|
|
956
|
+
declare function renderFeeConfigsText(patterns?: FeeConfig[]): string;
|
|
957
|
+
declare function renderFeeConfigDetailText(e: FeeConfig): string;
|
|
958
|
+
|
|
921
959
|
interface ExploitPattern {
|
|
922
960
|
/** Stable slug, e.g. "wormhole". */
|
|
923
961
|
id: string;
|
|
@@ -947,4 +985,4 @@ declare function renderExploitsMd(patterns?: ExploitPattern[]): string;
|
|
|
947
985
|
declare function renderExploitsText(patterns?: ExploitPattern[]): string;
|
|
948
986
|
declare function renderExploitDetailText(e: ExploitPattern): string;
|
|
949
987
|
|
|
950
|
-
export { type AccountFlow, type AnchorIdl, type AuditRef, type AuthorityClassification, type BatchResult, type BatchRow, type BatchScanOpts, type BuildOpts, CANONICAL_BY_MINT, CANONICAL_MINTS, type Candidate, type CanonicalMint, type ChainEvent, type ChainWatchOpts, type ChainWatchState, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_STALENESS_SLOTS, DEFAULT_TTL_HOURS, type DecodedInstruction, type DecodedTx, type DiffResult, type DriftAdvisory, type DriftBaseline, type DriftPackage, type DriftResult, EXPLOIT_PATTERNS, type ExploitPattern, type FirewallFinding, type FirewallOpts, type FirewallProgram, type FirewallReport, type FirewallSeverity, type FirewallVerdict, type Grade, type GraduationEvent, type IdentityStatus, type IdlAccount, type IdlConstraintParams, type IdlInstruction, KNOWN_AUTHORITY_OWNERS, KNOWN_PROGRAMS, type MintInfo, type OnChainProgram, type OracleFreshness, type OracleOpts, type OracleVerdict, type OsvAdvisory, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PreflightCheck, type PreflightOpts, type PreflightReport, type PreflightStatus, type PreflightVerdict, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type RicoOutcome, type RicoResult, type RicoTokenSecurity, type Rule, type RustAccountField, type RustCandidate, type RustChecker, SYSTEM_PROGRAM, type ScoreFactor, type Severity, type TelemetrySubmitResult, type TokenIdentity, type TrustGraph, type TrustScore, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type VerifyOpts, type WatchEvent, type WatchOptions, analyzeCosts, analyzeInstructions, analyzeToken, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, batchScan, buildConstraintParams, buildTrustGraph, rules as bundledRules, cacheSize, canonicalMintForSymbol, checkDrift, checkOracleFreshness, checkerKinds, classifyUpgradeAuthority, decodeTransaction, defaultCachePath, deployerFlagsFrom, diffVersions, enrichAuthorityClassification, fileChanged, findCandidates, findConfigCandidates, formatUsd, generateRulesFromIdl, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getExploitPattern, getRepoHash, getUserHash, getWorkingTreeChanges, gradeAtLeast, gradeForScore, idlProgramName, initPack, initialChainWatchState, inspectTransaction, isCanonicalMint, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseCpiPrograms, parseDiff, parseIdl, parseMintAccount, parseMintList, pollChainOnce, pumpPreflight, putCacheEntry, queryOsv, rangeChanged, recordGraduationEvents, renderBatchText, renderCostReportMd, renderDiffMd, renderDiffText, renderDriftText, renderExploitDetailText, renderExploitsMd, renderExploitsText, renderFirewallText, renderOracleMd, renderOracleText, renderPreflightText, renderRicoText, renderRulesYaml, renderScoreText, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, scoreFromProgram, scoreProgram, seedPackages, startChainWatch, startWatch, submitTelemetry, telemetryFilePath, testKinds, toSnakeCase, totalLossUsd, validatePack, validatePackManifest, verifyTokenIdentity };
|
|
988
|
+
export { type AccountFlow, type AnchorIdl, type AuditRef, type AuthorityClassification, type BatchResult, type BatchRow, type BatchScanOpts, type BuildOpts, type BundledPack, CANONICAL_BY_MINT, CANONICAL_MINTS, type Candidate, type CanonicalMint, type ChainEvent, type ChainWatchOpts, type ChainWatchState, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_STALENESS_SLOTS, DEFAULT_TTL_HOURS, type DecodedInstruction, type DecodedTx, type DiffResult, type DriftAdvisory, type DriftBaseline, type DriftPackage, type DriftResult, EXPLOIT_PATTERNS, type ExploitPattern, FEE_CONFIGS, type FeeConfig, type FeeConfigCategory, type FeeConfigStatus, type FirewallFinding, type FirewallOpts, type FirewallProgram, type FirewallReport, type FirewallSeverity, type FirewallVerdict, type Grade, type GraduationEvent, type IdentityStatus, type IdlAccount, type IdlConstraintParams, type IdlInstruction, KNOWN_AUTHORITY_OWNERS, KNOWN_PROGRAMS, type MintInfo, type OnChainProgram, type OracleFreshness, type OracleOpts, type OracleVerdict, type OsvAdvisory, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PreflightCheck, type PreflightOpts, type PreflightReport, type PreflightStatus, type PreflightVerdict, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type RicoOutcome, type RicoResult, type RicoTokenSecurity, type Rule, type RustAccountField, type RustCandidate, type RustChecker, SYSTEM_PROGRAM, type ScoreFactor, type Severity, type TelemetrySubmitResult, type TokenIdentity, type TrustGraph, type TrustScore, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type VerifyOpts, type WatchEvent, type WatchOptions, analyzeCosts, analyzeInstructions, analyzeToken, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, batchScan, buildConstraintParams, buildTrustGraph, rules as bundledRules, cacheSize, canonicalMintForSymbol, checkDrift, checkOracleFreshness, checkerKinds, classifyUpgradeAuthority, decodeTransaction, defaultCachePath, deployerFlagsFrom, diffVersions, enforcedCount, enrichAuthorityClassification, feeConfigsByCategory, fileChanged, findCandidates, findConfigCandidates, formatUsd, generateRulesFromIdl, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getExploitPattern, getFeeConfig, getRepoHash, getUserHash, getWorkingTreeChanges, gradeAtLeast, gradeForScore, idlProgramName, initPack, initialChainWatchState, inspectTransaction, isCanonicalMint, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, listBundledPacks, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseCpiPrograms, parseDiff, parseIdl, parseMintAccount, parseMintList, pollChainOnce, pumpPreflight, putCacheEntry, queryOsv, rangeChanged, recordGraduationEvents, renderBatchText, renderCostReportMd, renderDiffMd, renderDiffText, renderDriftText, renderExploitDetailText, renderExploitsMd, renderExploitsText, renderFeeConfigDetailText, renderFeeConfigsMd, renderFeeConfigsText, renderFirewallText, renderOracleMd, renderOracleText, renderPreflightText, renderRicoText, renderRulesYaml, renderScoreText, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveBundledPackToken, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, scoreFromProgram, scoreProgram, seedPackages, startChainWatch, startWatch, submitTelemetry, telemetryFilePath, testKinds, toSnakeCase, totalLossUsd, validatePack, validatePackManifest, verifyTokenIdentity };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
batchScan,
|
|
3
|
+
parseMintList,
|
|
4
|
+
renderBatchText
|
|
5
|
+
} from "./chunk-QC27GNQ7.js";
|
|
6
|
+
import {
|
|
7
|
+
FEE_CONFIGS,
|
|
8
|
+
enforcedCount,
|
|
9
|
+
feeConfigsByCategory,
|
|
10
|
+
getFeeConfig,
|
|
11
|
+
renderFeeConfigDetailText,
|
|
12
|
+
renderFeeConfigsMd,
|
|
13
|
+
renderFeeConfigsText
|
|
14
|
+
} from "./chunk-CSYGLMZR.js";
|
|
1
15
|
import {
|
|
2
16
|
DEFAULT_STALENESS_SLOTS,
|
|
3
17
|
checkOracleFreshness,
|
|
@@ -29,11 +43,6 @@ import {
|
|
|
29
43
|
pumpPreflight,
|
|
30
44
|
renderPreflightText
|
|
31
45
|
} from "./chunk-WX3IR7LK.js";
|
|
32
|
-
import {
|
|
33
|
-
batchScan,
|
|
34
|
-
parseMintList,
|
|
35
|
-
renderBatchText
|
|
36
|
-
} from "./chunk-QC27GNQ7.js";
|
|
37
46
|
import {
|
|
38
47
|
analyzeToken,
|
|
39
48
|
deployerFlagsFrom,
|
|
@@ -54,6 +63,7 @@ import {
|
|
|
54
63
|
initPack,
|
|
55
64
|
isTelemetryEnabled,
|
|
56
65
|
lamportsToSol,
|
|
66
|
+
listBundledPacks,
|
|
57
67
|
parseDiff,
|
|
58
68
|
recordGraduationEvents,
|
|
59
69
|
renderCostReportMd,
|
|
@@ -61,13 +71,14 @@ import {
|
|
|
61
71
|
renderExploitsMd,
|
|
62
72
|
renderExploitsText,
|
|
63
73
|
rentExemptMinimum,
|
|
74
|
+
resolveBundledPackToken,
|
|
64
75
|
runIncrementalScan,
|
|
65
76
|
startWatch,
|
|
66
77
|
submitTelemetry,
|
|
67
78
|
telemetryFilePath,
|
|
68
79
|
totalLossUsd,
|
|
69
80
|
validatePack
|
|
70
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-A3U6JDMN.js";
|
|
71
82
|
import {
|
|
72
83
|
renderTrustGraphMd
|
|
73
84
|
} from "./chunk-QMJEZ6NO.js";
|
|
@@ -108,7 +119,7 @@ import {
|
|
|
108
119
|
runChecker,
|
|
109
120
|
testKinds,
|
|
110
121
|
validatePackManifest
|
|
111
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-Q5DMQN67.js";
|
|
112
123
|
import {
|
|
113
124
|
CANONICAL_BY_MINT,
|
|
114
125
|
CANONICAL_MINTS,
|
|
@@ -163,6 +174,7 @@ export {
|
|
|
163
174
|
DEFAULT_STALENESS_SLOTS,
|
|
164
175
|
DEFAULT_TTL_HOURS,
|
|
165
176
|
EXPLOIT_PATTERNS,
|
|
177
|
+
FEE_CONFIGS,
|
|
166
178
|
KNOWN_AUTHORITY_OWNERS,
|
|
167
179
|
KNOWN_PROGRAMS,
|
|
168
180
|
PACK_MANIFEST_FILE,
|
|
@@ -189,7 +201,9 @@ export {
|
|
|
189
201
|
defaultCachePath,
|
|
190
202
|
deployerFlagsFrom,
|
|
191
203
|
diffVersions,
|
|
204
|
+
enforcedCount,
|
|
192
205
|
enrichAuthorityClassification,
|
|
206
|
+
feeConfigsByCategory,
|
|
193
207
|
fileChanged,
|
|
194
208
|
findCandidates,
|
|
195
209
|
findConfigCandidates,
|
|
@@ -200,6 +214,7 @@ export {
|
|
|
200
214
|
getCacheEntryMeta,
|
|
201
215
|
getChangedRanges,
|
|
202
216
|
getExploitPattern,
|
|
217
|
+
getFeeConfig,
|
|
203
218
|
getRepoHash,
|
|
204
219
|
getUserHash,
|
|
205
220
|
getWorkingTreeChanges,
|
|
@@ -214,6 +229,7 @@ export {
|
|
|
214
229
|
isTelemetryEnabled,
|
|
215
230
|
isValidSolanaAddress,
|
|
216
231
|
lamportsToSol,
|
|
232
|
+
listBundledPacks,
|
|
217
233
|
loadDirectory,
|
|
218
234
|
loadPack,
|
|
219
235
|
loadPacksFromDir,
|
|
@@ -238,6 +254,9 @@ export {
|
|
|
238
254
|
renderExploitDetailText,
|
|
239
255
|
renderExploitsMd,
|
|
240
256
|
renderExploitsText,
|
|
257
|
+
renderFeeConfigDetailText,
|
|
258
|
+
renderFeeConfigsMd,
|
|
259
|
+
renderFeeConfigsText,
|
|
241
260
|
renderFirewallText,
|
|
242
261
|
renderOracleMd,
|
|
243
262
|
renderOracleText,
|
|
@@ -248,6 +267,7 @@ export {
|
|
|
248
267
|
renderTest,
|
|
249
268
|
renderTrustGraphMd,
|
|
250
269
|
rentExemptMinimum,
|
|
270
|
+
resolveBundledPackToken,
|
|
251
271
|
resolveRules,
|
|
252
272
|
riskScore,
|
|
253
273
|
runChecker,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Protocol Pack Library
|
|
2
|
+
|
|
3
|
+
Every Solana app is built on some combination of Jupiter, Raydium, Pyth, Meteora, Jito, … — each with its own silent footguns. A **pack per protocol** means you opt into research-and-enforcement for the exact stack you build on, before a line is written:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
brainblast --packs jupiter,pyth .
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Packs are **opt-in** (not loaded by default), **pure data** (rules bind only to brainblast's vetted checker templates — no executable code ships in a pack), and **proven** (every rule ships `vulnerable/` + `fixed/` fixtures that must go RED → GREEN). List them anytime:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
brainblast packs # the library, by protocol name
|
|
13
|
+
brainblast pack validate <dir> # re-prove a pack RED → GREEN
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Bundled packs
|
|
17
|
+
|
|
18
|
+
| Protocol | Pack | Trap |
|
|
19
|
+
|----------|------|------|
|
|
20
|
+
| **Jupiter** | `jupiter-quote-zero-slippage` | `quoteGet({ slippageBps: 0 })` — MEV/sandwich exposure |
|
|
21
|
+
| **Raydium** | `raydium-compute-zero-slippage` | `computeAmountOut({ slippage: 0 })` — no min-out floor |
|
|
22
|
+
| **Pyth** | `pyth-price-unchecked-staleness` | `getPriceUnchecked()` — trades on a stale oracle |
|
|
23
|
+
| **Meteora** | `meteora-dlmm-zero-min-out` | `swap({ minOutAmount: new BN(0) })` — no slippage floor |
|
|
24
|
+
| **Jito** | `jito-bundle-zero-tip` | `sendBundle({ tipLamports: new BN(0) })` — bundle never lands |
|
|
25
|
+
| **Metaplex** | `metaplex-nft-royalty-zero` | `create({ sellerFeeBasisPoints: 0 })` — zero royalties |
|
|
26
|
+
| **Solana** | `solana-sendtx-unconfirmed` | `sendTransaction()` without confirmation — silent drop |
|
|
27
|
+
| **SPL** | `spl-transfer-not-checked-in-payout` | `createTransferInstruction` vs `…Checked` |
|
|
28
|
+
|
|
29
|
+
`--packs <name>` resolves a protocol name (e.g. `pyth`) to its pack, or takes an explicit pack directory.
|
|
30
|
+
|
|
31
|
+
## The compounding moat
|
|
32
|
+
|
|
33
|
+
Each pack someone contributes makes brainblast more valuable for the next dev building on that protocol. To add one: `brainblast pack init packs/<id> --id <id> …`, write a rule that binds to a vetted checker, add `fixtures/<id>/{vulnerable,fixed}/`, and `brainblast pack validate packs/<id>` until it proves RED → GREEN. See any pack here as a template.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# jito-bundle-zero-tip
|
|
2
|
+
|
|
3
|
+
**Severity:** HIGH
|
|
4
|
+
|
|
5
|
+
## What's the trap?
|
|
6
|
+
|
|
7
|
+
Jito bundles are ordered by their **tip**. The block engine prioritizes bundles that pay more; a bundle that tips **0** is deprioritized and, under any competition, simply never lands.
|
|
8
|
+
|
|
9
|
+
The trap is that sending a zero-tip bundle still *succeeds* at the API level — you get a bundle id back — so the code proceeds as if the transactions submitted. They didn't. For an arbitrage or liquidation bot, "the bundle silently never landed" is the difference between a profit and a missed opportunity (or a half-executed position).
|
|
10
|
+
|
|
11
|
+
## Why it's silent
|
|
12
|
+
|
|
13
|
+
`sendBundle(...)` returns a bundle id regardless of the tip. Nothing throws. The bundle just isn't included, and you only notice when on-chain state never reflects your transactions.
|
|
14
|
+
|
|
15
|
+
## The fix
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// BEFORE (vulnerable — no tip, bundle won't land)
|
|
19
|
+
return sendBundle({ transactions, tipLamports: new BN(0) });
|
|
20
|
+
|
|
21
|
+
// AFTER (fixed — nonzero tip the block engine can prioritize)
|
|
22
|
+
return sendBundle({ transactions, tipLamports: new BN(100_000) });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
In production, **scale the tip with competition** rather than hardcoding it.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
This rule (`object-arg-property-forbidden-literal`, `BN(0)`-aware) targets the common wrapper signature `sendBundle({ ..., tipLamports })` and fires only in files that import a Jito SDK. The positional `Bundle.addTipTx(payer, lamports, tipAccount, blockhash)` form isn't matched by a single object-field rule — verify that path manually, or add a project-local rule for your exact tip-builder signature.
|
|
30
|
+
|
|
31
|
+
## References
|
|
32
|
+
|
|
33
|
+
- [Jito — low-latency transaction send](https://docs.jito.wtf/lowlatencytxnsend/)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { searcherClient } from "jito-ts/dist/sdk/block-engine/searcher.js";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
|
|
4
|
+
async function sendBundle(opts: { transactions: any[]; tipLamports: BN }) {
|
|
5
|
+
const client = searcherClient("https://mainnet.block-engine.jito.wtf");
|
|
6
|
+
return (client as any).sendBundle(opts.transactions, opts.tipLamports);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// FIXED — a nonzero tip (100,000 lamports) so the block engine can prioritize
|
|
10
|
+
// the bundle. In production, scale the tip with observed competition.
|
|
11
|
+
export async function submitArb(transactions: any[]) {
|
|
12
|
+
return sendBundle({ transactions, tipLamports: new BN(100_000) });
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { searcherClient } from "jito-ts/dist/sdk/block-engine/searcher.js";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
|
|
4
|
+
// A thin project wrapper around the Jito searcher client, as most teams write.
|
|
5
|
+
async function sendBundle(opts: { transactions: any[]; tipLamports: BN }) {
|
|
6
|
+
const client = searcherClient("https://mainnet.block-engine.jito.wtf");
|
|
7
|
+
// ... build bundle with the tip, then client.sendBundle(...)
|
|
8
|
+
return (client as any).sendBundle(opts.transactions, opts.tipLamports);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// VULNERABLE — tipLamports: new BN(0). The bundle has no tip, so the Jito block
|
|
12
|
+
// engine deprioritizes it and it never lands under competition. sendBundle
|
|
13
|
+
// still returns a bundle id, so the caller assumes the transactions submitted.
|
|
14
|
+
export async function submitArb(transactions: any[]) {
|
|
15
|
+
return sendBundle({ transactions, tipLamports: new BN(0) });
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Pure-data rule. Binds to the vetted `object-arg-property-forbidden-literal`
|
|
2
|
+
# checker (BN(0)-aware as of brainblast v0.7.6).
|
|
3
|
+
#
|
|
4
|
+
# Jito bundles are ordered by their tip. A bundle that tips 0 is deprioritized
|
|
5
|
+
# by the block engine and, under any competition, simply never lands — yet the
|
|
6
|
+
# send call returns a bundle id and the code proceeds as if it submitted.
|
|
7
|
+
# This rule targets the common wrapper signature `sendBundle({ ..., tipLamports })`
|
|
8
|
+
# (and `new BN(0)`). The positional `Bundle.addTipTx(payer, lamports, ...)` form
|
|
9
|
+
# is a complementary manual check — see the pack README.
|
|
10
|
+
#
|
|
11
|
+
# Scope note: this fires only in files that import a Jito SDK (detect.modules),
|
|
12
|
+
# so a zero-amount transfer elsewhere won't trip it.
|
|
13
|
+
id: jito-bundle-zero-tip
|
|
14
|
+
severity: high
|
|
15
|
+
title: Jito bundle sent with a tip of 0 — bundle is deprioritized and will not land
|
|
16
|
+
component:
|
|
17
|
+
name: Jito (block engine / bundles)
|
|
18
|
+
type: Blockchain
|
|
19
|
+
version: unversioned
|
|
20
|
+
sourceUrl: https://docs.jito.wtf/lowlatencytxnsend/
|
|
21
|
+
detect:
|
|
22
|
+
modules:
|
|
23
|
+
- "jito-ts"
|
|
24
|
+
- "jito-js-rpc"
|
|
25
|
+
- "@jito-foundation/sdk"
|
|
26
|
+
nameRegex: bundle|jito|send|submit|tip|mev
|
|
27
|
+
triggerCalls:
|
|
28
|
+
- sendBundle
|
|
29
|
+
- sendJitoBundle
|
|
30
|
+
check:
|
|
31
|
+
kind: object-arg-property-forbidden-literal
|
|
32
|
+
params:
|
|
33
|
+
call: sendBundle
|
|
34
|
+
argIndex: 0
|
|
35
|
+
propName: tipLamports
|
|
36
|
+
forbiddenValue: 0
|
|
37
|
+
passDetail: >-
|
|
38
|
+
sendBundle is called with a nonzero tipLamports — the bundle has a tip the
|
|
39
|
+
block engine can prioritize.
|
|
40
|
+
failDetail: >-
|
|
41
|
+
sendBundle is called with tipLamports of 0 (or new BN(0)). A Jito bundle
|
|
42
|
+
with no tip is deprioritized by the block engine and, under any
|
|
43
|
+
competition, never lands — but the call still returns a bundle id, so the
|
|
44
|
+
code proceeds as if the transactions submitted. Set a nonzero tip (and
|
|
45
|
+
ideally scale it with priority/competition).
|
|
46
|
+
absentCallDetail: >-
|
|
47
|
+
Handler is in Jito scope but does not call sendBundle; the zero-tip rule
|
|
48
|
+
does not apply here.
|
|
49
|
+
absentArgDetail: >-
|
|
50
|
+
sendBundle's options object does not set tipLamports as an inline literal;
|
|
51
|
+
the tip cannot be confirmed statically. Verify it resolves to a nonzero
|
|
52
|
+
value.
|
|
53
|
+
test:
|
|
54
|
+
kind: none
|
package/dist/packs/jupiter-quote-zero-slippage/fixtures/jupiter-quote-zero-slippage/fixed/arb.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createJupiterApiClient } from "@jup-ag/api";
|
|
2
|
+
|
|
3
|
+
const jupiterQuoteApi = createJupiterApiClient();
|
|
4
|
+
|
|
5
|
+
const wSolMint = "So11111111111111111111111111111111111111112";
|
|
6
|
+
const usdcMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
7
|
+
|
|
8
|
+
export async function getArbQuote(amountInJSBI: number) {
|
|
9
|
+
return jupiterQuoteApi.quoteGet({
|
|
10
|
+
inputMint: wSolMint,
|
|
11
|
+
outputMint: usdcMint,
|
|
12
|
+
amount: amountInJSBI,
|
|
13
|
+
onlyDirectRoutes: false,
|
|
14
|
+
slippageBps: 50,
|
|
15
|
+
maxAccounts: 20,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createJupiterApiClient } from "@jup-ag/api";
|
|
2
|
+
|
|
3
|
+
const jupiterQuoteApi = createJupiterApiClient();
|
|
4
|
+
|
|
5
|
+
const wSolMint = "So11111111111111111111111111111111111111112";
|
|
6
|
+
const usdcMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
7
|
+
|
|
8
|
+
export async function getArbQuote(amountInJSBI: number) {
|
|
9
|
+
return jupiterQuoteApi.quoteGet({
|
|
10
|
+
inputMint: wSolMint,
|
|
11
|
+
outputMint: usdcMint,
|
|
12
|
+
amount: amountInJSBI,
|
|
13
|
+
onlyDirectRoutes: false,
|
|
14
|
+
slippageBps: 0,
|
|
15
|
+
maxAccounts: 20,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Pure-data rule (facts). Binds to the vetted `object-arg-property-forbidden-literal`
|
|
2
|
+
# checker template.
|
|
3
|
+
#
|
|
4
|
+
# Real-world instances of this exact pattern:
|
|
5
|
+
# - WSOL12/Solana-Arbitrage-Bot (510 stars, 208 forks), src/index.ts —
|
|
6
|
+
# `quoteUrl` fetch params include `slippageBps: 0`.
|
|
7
|
+
# - Forked verbatim into ChainBuff/sol-arb-bot, src/index.ts (same lines).
|
|
8
|
+
# - ARBProtocol/solana-jupiter-bot, src/bot/setup.js — same field on the
|
|
9
|
+
# Jupiter quote call.
|
|
10
|
+
id: jupiter-quote-zero-slippage
|
|
11
|
+
severity: high
|
|
12
|
+
title: Jupiter quote/swap call hardcodes slippageBps to 0
|
|
13
|
+
component:
|
|
14
|
+
name: Jupiter Aggregator API
|
|
15
|
+
type: Blockchain
|
|
16
|
+
version: unversioned
|
|
17
|
+
sourceUrl: https://station.jup.ag/docs/apis/swap-api
|
|
18
|
+
detect:
|
|
19
|
+
modules: ["@jup-ag/api"]
|
|
20
|
+
# Keep broad enough to catch arb/swap bots that wrap the Jupiter quote
|
|
21
|
+
# call in their own helper (getQuote, fetchQuote, getArbQuote, swap, ...).
|
|
22
|
+
nameRegex: "quote|swap|arb"
|
|
23
|
+
triggerCalls: [quoteGet]
|
|
24
|
+
check:
|
|
25
|
+
kind: object-arg-property-forbidden-literal
|
|
26
|
+
params:
|
|
27
|
+
call: quoteGet
|
|
28
|
+
argIndex: 0
|
|
29
|
+
propName: slippageBps
|
|
30
|
+
forbiddenValue: 0
|
|
31
|
+
passDetail: >-
|
|
32
|
+
quoteGet's slippageBps is a nonzero literal — the swap has explicit
|
|
33
|
+
slippage protection.
|
|
34
|
+
failDetail: >-
|
|
35
|
+
quoteGet is called with slippageBps: 0, which disables slippage
|
|
36
|
+
protection entirely. Any price movement between the quote and the
|
|
37
|
+
on-chain swap (including a sandwich attack) executes the trade at
|
|
38
|
+
whatever price results, with no minimum-output guarantee. Set
|
|
39
|
+
slippageBps to a nonzero value (e.g. 50 = 0.5%) or use
|
|
40
|
+
`dynamicSlippage` so the swap reverts if the price moves too far.
|
|
41
|
+
absentCallDetail: >-
|
|
42
|
+
Handler is in the Jupiter-quote scope but does not call quoteGet; the
|
|
43
|
+
zero-slippage rule does not apply here.
|
|
44
|
+
absentArgDetail: >-
|
|
45
|
+
quoteGet's options object does not set slippageBps as an inline
|
|
46
|
+
literal — cannot determine statically whether slippage protection is
|
|
47
|
+
disabled. If slippageBps is omitted entirely, the Jupiter API defaults
|
|
48
|
+
to 50 bps (0.5%), which is safe; if it's a non-literal expression,
|
|
49
|
+
verify the resolved value is nonzero.
|
|
50
|
+
test:
|
|
51
|
+
kind: none
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# metaplex-nft-royalty-zero
|
|
2
|
+
|
|
3
|
+
**Severity:** HIGH
|
|
4
|
+
|
|
5
|
+
## What's the trap?
|
|
6
|
+
|
|
7
|
+
When minting an NFT with `@metaplex-foundation/js`, the `sellerFeeBasisPoints` field in `create()` sets the on-chain royalty percentage for the NFT's entire lifetime. A value of `0` means:
|
|
8
|
+
|
|
9
|
+
- Creators earn **zero** royalties on every secondary sale
|
|
10
|
+
- This is **immutable** — Metaplex token-metadata cannot be changed after mint without burning the NFT and reminting it
|
|
11
|
+
|
|
12
|
+
AI code generators frequently use `0` as a "fill in later" placeholder, and launch teams sometimes leave it in to appear collection-friendly. Either way, the economic harm is permanent.
|
|
13
|
+
|
|
14
|
+
## Why it's silent
|
|
15
|
+
|
|
16
|
+
The `create()` call succeeds with `sellerFeeBasisPoints: 0` — there is no warning, no validation error, no on-chain check. The NFT mints normally and is immediately tradeable. The royalty loss only becomes apparent when secondary sales generate no creator income.
|
|
17
|
+
|
|
18
|
+
## The fix
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// BEFORE (vulnerable — zero royalties, permanent)
|
|
22
|
+
return metaplex.nfts().create({
|
|
23
|
+
uri,
|
|
24
|
+
name: "My NFT",
|
|
25
|
+
sellerFeeBasisPoints: 0, // ← trap
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// AFTER (fixed — 5% royalties)
|
|
29
|
+
return metaplex.nfts().create({
|
|
30
|
+
uri,
|
|
31
|
+
name: "My NFT",
|
|
32
|
+
sellerFeeBasisPoints: 500, // 500 basis points = 5%
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## References
|
|
37
|
+
|
|
38
|
+
- [Metaplex Token Metadata — Mint docs](https://developers.metaplex.com/token-metadata/mint)
|
|
39
|
+
- `CreateNftInput.sellerFeeBasisPoints` — required field, immutable after mint
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Metaplex } from "@metaplex-foundation/js";
|
|
2
|
+
|
|
3
|
+
export async function mintNft(metaplex: Metaplex, uri: string) {
|
|
4
|
+
// FIXED: 5% royalty on secondary sales
|
|
5
|
+
return metaplex.nfts().create({
|
|
6
|
+
uri,
|
|
7
|
+
name: "My NFT",
|
|
8
|
+
sellerFeeBasisPoints: 500,
|
|
9
|
+
symbol: "MNFT",
|
|
10
|
+
});
|
|
11
|
+
}
|
package/dist/packs/metaplex-nft-royalty-zero/fixtures/metaplex-nft-royalty-zero/vulnerable/mint.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Metaplex } from "@metaplex-foundation/js";
|
|
2
|
+
|
|
3
|
+
export async function mintNft(metaplex: Metaplex, uri: string) {
|
|
4
|
+
// VULNERABLE: royalties are permanently set to zero — cannot be changed after mint
|
|
5
|
+
return metaplex.nfts().create({
|
|
6
|
+
uri,
|
|
7
|
+
name: "My NFT",
|
|
8
|
+
sellerFeeBasisPoints: 0,
|
|
9
|
+
symbol: "MNFT",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Auto-synthesized from a research Finding (proof-as-classifier loop).
|
|
2
|
+
# Pure data — every kind below resolves to a HUMAN-VETTED template in core.
|
|
3
|
+
id: metaplex-nft-royalty-zero
|
|
4
|
+
severity: high
|
|
5
|
+
title: "NFT minted with sellerFeeBasisPoints: 0 — creators receive zero
|
|
6
|
+
royalties on secondary sales"
|
|
7
|
+
component:
|
|
8
|
+
name: "@metaplex-foundation/js"
|
|
9
|
+
type: Blockchain
|
|
10
|
+
version: ">=0.18.0"
|
|
11
|
+
sourceUrl: https://developers.metaplex.com/token-metadata/mint
|
|
12
|
+
detect:
|
|
13
|
+
modules:
|
|
14
|
+
- "@metaplex-foundation/js"
|
|
15
|
+
nameRegex: mint|create|nft|deploy
|
|
16
|
+
triggerCalls:
|
|
17
|
+
- create
|
|
18
|
+
check:
|
|
19
|
+
kind: object-arg-property-forbidden-literal
|
|
20
|
+
params:
|
|
21
|
+
call: create
|
|
22
|
+
argIndex: 0
|
|
23
|
+
propName: sellerFeeBasisPoints
|
|
24
|
+
forbiddenValue: 0
|
|
25
|
+
passDetail: create() sets sellerFeeBasisPoints to a nonzero literal — creators
|
|
26
|
+
will receive royalties on secondary sales.
|
|
27
|
+
failDetail: "create() is called with sellerFeeBasisPoints: 0, which bakes zero
|
|
28
|
+
royalties into the NFT's on-chain metadata. Secondary marketplaces that
|
|
29
|
+
honour on-chain royalties will pay creators nothing. Metaplex
|
|
30
|
+
token-metadata is immutable after mint — this cannot be corrected without
|
|
31
|
+
burning and reminting. Set sellerFeeBasisPoints to the intended basis
|
|
32
|
+
points (e.g. 500 = 5%)."
|
|
33
|
+
absentCallDetail: Handler is in the NFT-mint scope but does not call create();
|
|
34
|
+
this rule does not apply here.
|
|
35
|
+
absentArgDetail: create() does not include a sellerFeeBasisPoints literal in its
|
|
36
|
+
options object; the royalty rate cannot be confirmed statically. Verify
|
|
37
|
+
the resolved value is the intended nonzero royalty.
|
|
38
|
+
test:
|
|
39
|
+
kind: none
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# meteora-dlmm-zero-min-out
|
|
2
|
+
|
|
3
|
+
**Severity:** HIGH
|
|
4
|
+
|
|
5
|
+
## What's the trap?
|
|
6
|
+
|
|
7
|
+
Meteora DLMM's `swap()` takes a `minOutAmount` — the minimum number of output tokens the swap must return, or it reverts. Passing `new BN(0)` (or `0`) removes that floor entirely.
|
|
8
|
+
|
|
9
|
+
With no floor, any price movement between when you quoted and when the swap lands on-chain — including a deliberate **sandwich attack** — fills the swap at whatever price results. There is no protection.
|
|
10
|
+
|
|
11
|
+
AI-written swap bots reach for `minOutAmount: new BN(0)` to "just make the swap go through," not realizing they've disabled all slippage protection.
|
|
12
|
+
|
|
13
|
+
## Why it's silent
|
|
14
|
+
|
|
15
|
+
`swap()` with `minOutAmount: new BN(0)` builds and submits successfully. The trade executes. The loss only shows up as consistently worse-than-quoted fills — value quietly extracted by MEV.
|
|
16
|
+
|
|
17
|
+
## The fix
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// BEFORE (vulnerable — no floor)
|
|
21
|
+
return dlmmPool.swap({ /* ... */, minOutAmount: new BN(0) });
|
|
22
|
+
|
|
23
|
+
// AFTER (fixed — floor from the quote + slippage tolerance)
|
|
24
|
+
const quote = dlmmPool.swapQuote(inAmount, swapForY, new BN(50), binArrays); // 0.5%
|
|
25
|
+
return dlmmPool.swap({ /* ... */, minOutAmount: quote.minOutAmount });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> This rule is `BN(0)`-aware (brainblast v0.7.6): it flags `minOutAmount: 0` **and** the idiomatic `minOutAmount: new BN(0)`.
|
|
29
|
+
|
|
30
|
+
## References
|
|
31
|
+
|
|
32
|
+
- [Meteora DLMM overview](https://docs.meteora.ag/product-overview/meteora-liquidity-pools/dlmm-overview)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
id: meteora-dlmm-zero-min-out
|
|
2
|
+
name: Meteora DLMM zero minimum-out
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
author: DSB-117
|
|
5
|
+
description: "Flags Meteora DLMM swap() calls with minOutAmount of 0 (including new BN(0)) — no minimum-output floor, so a sandwich attack executes the swap at any price."
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import DLMM from "@meteora-ag/dlmm";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
|
|
4
|
+
// FIXED — minOutAmount is derived from the on-chain quote and a 0.5% slippage
|
|
5
|
+
// tolerance, so the swap reverts if it would return less than expected.
|
|
6
|
+
export async function swapExactIn(dlmmPool: DLMM, user: any, inAmount: BN) {
|
|
7
|
+
const swapForY = true;
|
|
8
|
+
const binArrays = await dlmmPool.getBinArrayForSwap(swapForY);
|
|
9
|
+
const quote = dlmmPool.swapQuote(inAmount, swapForY, new BN(50), binArrays); // 50 bps = 0.5%
|
|
10
|
+
return dlmmPool.swap({
|
|
11
|
+
inToken: (dlmmPool as any).tokenX.publicKey,
|
|
12
|
+
outToken: (dlmmPool as any).tokenY.publicKey,
|
|
13
|
+
inAmount,
|
|
14
|
+
minOutAmount: quote.minOutAmount,
|
|
15
|
+
lbPair: (dlmmPool as any).pubkey,
|
|
16
|
+
user,
|
|
17
|
+
binArraysPubkey: binArrays.map((b: any) => b.publicKey),
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/packs/meteora-dlmm-zero-min-out/fixtures/meteora-dlmm-zero-min-out/vulnerable/swap.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import DLMM from "@meteora-ag/dlmm";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
|
|
4
|
+
// VULNERABLE — minOutAmount: new BN(0) removes the slippage floor. The swap
|
|
5
|
+
// will fill at any price; a sandwich bot can move the pool and extract value
|
|
6
|
+
// with no revert protection.
|
|
7
|
+
export async function swapExactIn(dlmmPool: DLMM, user: any, inAmount: BN) {
|
|
8
|
+
const swapForY = true;
|
|
9
|
+
const binArrays = await dlmmPool.getBinArrayForSwap(swapForY);
|
|
10
|
+
return dlmmPool.swap({
|
|
11
|
+
inToken: (dlmmPool as any).tokenX.publicKey,
|
|
12
|
+
outToken: (dlmmPool as any).tokenY.publicKey,
|
|
13
|
+
inAmount,
|
|
14
|
+
minOutAmount: new BN(0),
|
|
15
|
+
lbPair: (dlmmPool as any).pubkey,
|
|
16
|
+
user,
|
|
17
|
+
binArraysPubkey: binArrays.map((b: any) => b.publicKey),
|
|
18
|
+
});
|
|
19
|
+
}
|