@itachisol/plugin-x402-swarms 0.1.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/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/actions/delegateToSwarm.d.ts +8 -0
- package/dist/actions/delegateToSwarm.d.ts.map +1 -0
- package/dist/actions/delegateToSwarm.js +296 -0
- package/dist/actions/delegateToSwarm.js.map +1 -0
- package/dist/actions/delegateToSwarmWithPayment.d.ts +8 -0
- package/dist/actions/delegateToSwarmWithPayment.d.ts.map +1 -0
- package/dist/actions/delegateToSwarmWithPayment.js +323 -0
- package/dist/actions/delegateToSwarmWithPayment.js.map +1 -0
- package/dist/actions/discoverServices.d.ts +3 -0
- package/dist/actions/discoverServices.d.ts.map +1 -0
- package/dist/actions/discoverServices.js +63 -0
- package/dist/actions/discoverServices.js.map +1 -0
- package/dist/actions/index.d.ts +6 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +6 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/payForService.d.ts +3 -0
- package/dist/actions/payForService.d.ts.map +1 -0
- package/dist/actions/payForService.js +115 -0
- package/dist/actions/payForService.js.map +1 -0
- package/dist/actions/runSwarmAgent.d.ts +7 -0
- package/dist/actions/runSwarmAgent.d.ts.map +1 -0
- package/dist/actions/runSwarmAgent.js +131 -0
- package/dist/actions/runSwarmAgent.js.map +1 -0
- package/dist/client/index.d.ts +512 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +469 -0
- package/dist/client/index.js.map +1 -0
- package/dist/evaluators/paymentEvaluator.d.ts +7 -0
- package/dist/evaluators/paymentEvaluator.d.ts.map +1 -0
- package/dist/evaluators/paymentEvaluator.js +104 -0
- package/dist/evaluators/paymentEvaluator.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/marketing/index.d.ts +7 -0
- package/dist/marketing/index.d.ts.map +1 -0
- package/dist/marketing/index.js +7 -0
- package/dist/marketing/index.js.map +1 -0
- package/dist/marketing/milestoneAgent.d.ts +54 -0
- package/dist/marketing/milestoneAgent.d.ts.map +1 -0
- package/dist/marketing/milestoneAgent.js +121 -0
- package/dist/marketing/milestoneAgent.js.map +1 -0
- package/dist/marketing/tweetTemplates.d.ts +32 -0
- package/dist/marketing/tweetTemplates.d.ts.map +1 -0
- package/dist/marketing/tweetTemplates.js +60 -0
- package/dist/marketing/tweetTemplates.js.map +1 -0
- package/dist/marketing/xMonitor.d.ts +51 -0
- package/dist/marketing/xMonitor.d.ts.map +1 -0
- package/dist/marketing/xMonitor.js +141 -0
- package/dist/marketing/xMonitor.js.map +1 -0
- package/dist/providers/x402Provider.d.ts +7 -0
- package/dist/providers/x402Provider.d.ts.map +1 -0
- package/dist/providers/x402Provider.js +72 -0
- package/dist/providers/x402Provider.js.map +1 -0
- package/dist/providers/x402ServerProvider.d.ts +7 -0
- package/dist/providers/x402ServerProvider.d.ts.map +1 -0
- package/dist/providers/x402ServerProvider.js +48 -0
- package/dist/providers/x402ServerProvider.js.map +1 -0
- package/dist/routes/advancedRoutes.d.ts +5 -0
- package/dist/routes/advancedRoutes.d.ts.map +1 -0
- package/dist/routes/advancedRoutes.js +869 -0
- package/dist/routes/advancedRoutes.js.map +1 -0
- package/dist/routes/batchRoutes.d.ts +13 -0
- package/dist/routes/batchRoutes.d.ts.map +1 -0
- package/dist/routes/batchRoutes.js +496 -0
- package/dist/routes/batchRoutes.js.map +1 -0
- package/dist/routes/codeAuditRoutes.d.ts +6 -0
- package/dist/routes/codeAuditRoutes.d.ts.map +1 -0
- package/dist/routes/codeAuditRoutes.js +415 -0
- package/dist/routes/codeAuditRoutes.js.map +1 -0
- package/dist/routes/contentRoutes.d.ts +5 -0
- package/dist/routes/contentRoutes.d.ts.map +1 -0
- package/dist/routes/contentRoutes.js +370 -0
- package/dist/routes/contentRoutes.js.map +1 -0
- package/dist/routes/cryptoAnalysisRoutes.d.ts +5 -0
- package/dist/routes/cryptoAnalysisRoutes.d.ts.map +1 -0
- package/dist/routes/cryptoAnalysisRoutes.js +641 -0
- package/dist/routes/cryptoAnalysisRoutes.js.map +1 -0
- package/dist/routes/cryptoRoutes.d.ts +5 -0
- package/dist/routes/cryptoRoutes.d.ts.map +1 -0
- package/dist/routes/cryptoRoutes.js +1225 -0
- package/dist/routes/cryptoRoutes.js.map +1 -0
- package/dist/routes/heliusDataRoutes.d.ts +14 -0
- package/dist/routes/heliusDataRoutes.d.ts.map +1 -0
- package/dist/routes/heliusDataRoutes.js +388 -0
- package/dist/routes/heliusDataRoutes.js.map +1 -0
- package/dist/routes/taskRoutes.d.ts +5 -0
- package/dist/routes/taskRoutes.d.ts.map +1 -0
- package/dist/routes/taskRoutes.js +574 -0
- package/dist/routes/taskRoutes.js.map +1 -0
- package/dist/routes/tradingRoutes.d.ts +5 -0
- package/dist/routes/tradingRoutes.d.ts.map +1 -0
- package/dist/routes/tradingRoutes.js +500 -0
- package/dist/routes/tradingRoutes.js.map +1 -0
- package/dist/routes/walletAnalyzerRoutes.d.ts +12 -0
- package/dist/routes/walletAnalyzerRoutes.d.ts.map +1 -0
- package/dist/routes/walletAnalyzerRoutes.js +316 -0
- package/dist/routes/walletAnalyzerRoutes.js.map +1 -0
- package/dist/routes/x402Routes.d.ts +9 -0
- package/dist/routes/x402Routes.d.ts.map +1 -0
- package/dist/routes/x402Routes.js +474 -0
- package/dist/routes/x402Routes.js.map +1 -0
- package/dist/schemas/budgetState.d.ts +250 -0
- package/dist/schemas/budgetState.d.ts.map +1 -0
- package/dist/schemas/budgetState.js +20 -0
- package/dist/schemas/budgetState.js.map +1 -0
- package/dist/schemas/endpointScores.d.ts +182 -0
- package/dist/schemas/endpointScores.d.ts.map +1 -0
- package/dist/schemas/endpointScores.js +17 -0
- package/dist/schemas/endpointScores.js.map +1 -0
- package/dist/schemas/index.d.ts +4 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +4 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/paymentHistory.d.ts +284 -0
- package/dist/schemas/paymentHistory.d.ts.map +1 -0
- package/dist/schemas/paymentHistory.js +24 -0
- package/dist/schemas/paymentHistory.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/x402Gate.d.ts +56 -0
- package/dist/server/x402Gate.d.ts.map +1 -0
- package/dist/server/x402Gate.js +240 -0
- package/dist/server/x402Gate.js.map +1 -0
- package/dist/server/x402ServerService.d.ts +30 -0
- package/dist/server/x402ServerService.d.ts.map +1 -0
- package/dist/server/x402ServerService.js +79 -0
- package/dist/server/x402ServerService.js.map +1 -0
- package/dist/services/paymentMemoryService.d.ts +73 -0
- package/dist/services/paymentMemoryService.d.ts.map +1 -0
- package/dist/services/paymentMemoryService.js +247 -0
- package/dist/services/paymentMemoryService.js.map +1 -0
- package/dist/services/swarmsService.d.ts +66 -0
- package/dist/services/swarmsService.d.ts.map +1 -0
- package/dist/services/swarmsService.js +102 -0
- package/dist/services/swarmsService.js.map +1 -0
- package/dist/services/x402WalletService.d.ts +57 -0
- package/dist/services/x402WalletService.d.ts.map +1 -0
- package/dist/services/x402WalletService.js +258 -0
- package/dist/services/x402WalletService.js.map +1 -0
- package/dist/templates/index.d.ts +24 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +51 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/swarmTemplates.d.ts +22 -0
- package/dist/templates/swarmTemplates.d.ts.map +1 -0
- package/dist/templates/swarmTemplates.js +225 -0
- package/dist/templates/swarmTemplates.js.map +1 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cache.d.ts +17 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +32 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/llm.d.ts +16 -0
- package/dist/utils/llm.d.ts.map +1 -0
- package/dist/utils/llm.js +32 -0
- package/dist/utils/llm.js.map +1 -0
- package/dist/utils/reportStore.d.ts +45 -0
- package/dist/utils/reportStore.d.ts.map +1 -0
- package/dist/utils/reportStore.js +164 -0
- package/dist/utils/reportStore.js.map +1 -0
- package/dist/utils/taskQueue.d.ts +54 -0
- package/dist/utils/taskQueue.d.ts.map +1 -0
- package/dist/utils/taskQueue.js +124 -0
- package/dist/utils/taskQueue.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
import { x402Gate } from "../server/x402Gate.js";
|
|
2
|
+
import { SOLANA_ADDR_RE, heliusRpcUrl, rpcCall } from "./heliusDataRoutes.js";
|
|
3
|
+
import { saveReport } from "../utils/reportStore.js";
|
|
4
|
+
// ── Helper: build public URLs for a report ───────────────────────────────
|
|
5
|
+
function reportUrls(id) {
|
|
6
|
+
const base = process.env.RAILWAY_PUBLIC_DOMAIN
|
|
7
|
+
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
|
|
8
|
+
: process.env.SWARMX_BASE_URL ?? "https://api.swarmx.io";
|
|
9
|
+
return {
|
|
10
|
+
reportUrl: `${base}/report/${id}`,
|
|
11
|
+
badgeUrl: `${base}/badge/${id}`,
|
|
12
|
+
badgeMarkdown: `[](${base}/report/${id})`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// ── Input validation helpers ───────────────────────────────────────────
|
|
16
|
+
function requireString(body, field, maxLen = 100_000) {
|
|
17
|
+
const val = body[field];
|
|
18
|
+
if (!val || typeof val !== "string" || val.trim().length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
return val.slice(0, maxLen);
|
|
21
|
+
}
|
|
22
|
+
// ── Helper: get SwarmsService or null ──────────────────────────────────
|
|
23
|
+
function getSwarmsService(runtime) {
|
|
24
|
+
const svc = runtime.getService("SWARMS");
|
|
25
|
+
return svc?.isAvailable() ? svc : null;
|
|
26
|
+
}
|
|
27
|
+
// ── JSON parse helper (try JSON first, fallback to raw text) ───────────
|
|
28
|
+
function tryParseJson(raw) {
|
|
29
|
+
try {
|
|
30
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
31
|
+
if (match) {
|
|
32
|
+
return JSON.parse(match[0]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// not valid JSON
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// ── Helper: extract raw text from swarm response ─────────────────────
|
|
41
|
+
// Swarms API `output` can be:
|
|
42
|
+
// - a plain string
|
|
43
|
+
// - an array of { role: string; content: string } objects
|
|
44
|
+
// - a nested object with an `output` key
|
|
45
|
+
// This normalizes all formats to a single concatenated string.
|
|
46
|
+
function extractSwarmOutput(result) {
|
|
47
|
+
const output = result.output;
|
|
48
|
+
// Case 1: output is a string
|
|
49
|
+
if (typeof output === "string")
|
|
50
|
+
return output;
|
|
51
|
+
// Case 2: output is an array of agent messages
|
|
52
|
+
if (Array.isArray(output)) {
|
|
53
|
+
return output
|
|
54
|
+
.map((item) => {
|
|
55
|
+
if (typeof item === "string")
|
|
56
|
+
return item;
|
|
57
|
+
if (item && typeof item === "object") {
|
|
58
|
+
const obj = item;
|
|
59
|
+
const role = obj.role ?? obj.agent_name ?? "agent";
|
|
60
|
+
const content = obj.content ?? obj.text ?? obj.output ?? "";
|
|
61
|
+
return `[${role}]\n${content}`;
|
|
62
|
+
}
|
|
63
|
+
return String(item);
|
|
64
|
+
})
|
|
65
|
+
.join("\n\n");
|
|
66
|
+
}
|
|
67
|
+
// Case 3: output is a nested object (e.g. { output: "..." })
|
|
68
|
+
if (output && typeof output === "object") {
|
|
69
|
+
const nested = output;
|
|
70
|
+
if (typeof nested.output === "string")
|
|
71
|
+
return nested.output;
|
|
72
|
+
if (typeof nested.content === "string")
|
|
73
|
+
return nested.content;
|
|
74
|
+
// Last resort: stringify the object
|
|
75
|
+
return JSON.stringify(output);
|
|
76
|
+
}
|
|
77
|
+
// Case 4: no output field — try the whole result
|
|
78
|
+
if (typeof result === "string")
|
|
79
|
+
return result;
|
|
80
|
+
return JSON.stringify(result);
|
|
81
|
+
}
|
|
82
|
+
// ── Helper: estimate risk score from severity keywords in text ────────
|
|
83
|
+
// Scans for CRITICAL, HIGH, MEDIUM, LOW, INFO keywords and produces 0-100.
|
|
84
|
+
function estimateRiskScore(text) {
|
|
85
|
+
const upper = text.toUpperCase();
|
|
86
|
+
const critical = (upper.match(/\bCRITICAL\b/g) ?? []).length;
|
|
87
|
+
const high = (upper.match(/\bHIGH\b/g) ?? []).length;
|
|
88
|
+
const medium = (upper.match(/\bMEDIUM\b/g) ?? []).length;
|
|
89
|
+
const low = (upper.match(/\bLOW\b/g) ?? []).length;
|
|
90
|
+
// Weighted scoring: critical=25, high=15, medium=8, low=3
|
|
91
|
+
const raw = critical * 25 + high * 15 + medium * 8 + low * 3;
|
|
92
|
+
// Clamp to 0-100
|
|
93
|
+
return Math.min(100, Math.max(0, raw));
|
|
94
|
+
}
|
|
95
|
+
function parseContractAuditText(text) {
|
|
96
|
+
// Try to find structured JSON first (the reporter should produce valid JSON)
|
|
97
|
+
const parsed = tryParseJson(text);
|
|
98
|
+
if (parsed && typeof parsed.riskScore === "number") {
|
|
99
|
+
const riskScore = parsed.riskScore;
|
|
100
|
+
// Derive verdict from score if not provided
|
|
101
|
+
let verdict = parsed.verdict ?? "CAUTION";
|
|
102
|
+
if (!parsed.verdict) {
|
|
103
|
+
if (riskScore >= 85)
|
|
104
|
+
verdict = "SAFE";
|
|
105
|
+
else if (riskScore >= 50)
|
|
106
|
+
verdict = "CAUTION";
|
|
107
|
+
else
|
|
108
|
+
verdict = "DANGER";
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
riskScore,
|
|
112
|
+
verdict,
|
|
113
|
+
findings: parsed.findings ?? { security: [], economic: [], gas: [] },
|
|
114
|
+
strengths: Array.isArray(parsed.strengths) ? parsed.strengths : [],
|
|
115
|
+
weaknesses: Array.isArray(parsed.weaknesses) ? parsed.weaknesses : [],
|
|
116
|
+
redFlags: Array.isArray(parsed.red_flags) ? parsed.red_flags :
|
|
117
|
+
Array.isArray(parsed.redFlags) ? parsed.redFlags : [],
|
|
118
|
+
copyLikelihoodScore: typeof parsed.copy_likelihood_score === "number"
|
|
119
|
+
? parsed.copy_likelihood_score
|
|
120
|
+
: typeof parsed.copyLikelihoodScore === "number"
|
|
121
|
+
? parsed.copyLikelihoodScore
|
|
122
|
+
: 0,
|
|
123
|
+
complexityScore: typeof parsed.complexity_score === "number"
|
|
124
|
+
? parsed.complexity_score
|
|
125
|
+
: typeof parsed.complexityScore === "number"
|
|
126
|
+
? parsed.complexityScore
|
|
127
|
+
: 50,
|
|
128
|
+
summary: parsed.summary ?? text.slice(0, 500),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Extract findings from each agent section
|
|
132
|
+
const security = [];
|
|
133
|
+
const economic = [];
|
|
134
|
+
const gas = [];
|
|
135
|
+
// Try to extract individual JSON blocks from each agent's output
|
|
136
|
+
const jsonBlocks = text.match(/\{[\s\S]*?\n\}/g) ?? [];
|
|
137
|
+
for (const block of jsonBlocks) {
|
|
138
|
+
const obj = tryParseJson(block);
|
|
139
|
+
if (!obj)
|
|
140
|
+
continue;
|
|
141
|
+
const findings = Array.isArray(obj.findings) ? obj.findings : [];
|
|
142
|
+
// Categorize by surrounding context or finding content
|
|
143
|
+
const blockContext = text.slice(Math.max(0, text.indexOf(block) - 200), text.indexOf(block));
|
|
144
|
+
if (/security|auditor|reentrancy|vulnerability|access.control/i.test(blockContext)) {
|
|
145
|
+
security.push(...findings);
|
|
146
|
+
}
|
|
147
|
+
else if (/economic|attack|mev|sandwich|flashloan|arbitrage/i.test(blockContext)) {
|
|
148
|
+
economic.push(...findings);
|
|
149
|
+
}
|
|
150
|
+
else if (/gas|optim|sload|storage.packing|calldata/i.test(blockContext)) {
|
|
151
|
+
gas.push(...findings);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// If we can't tell, add to security by default
|
|
155
|
+
security.push(...findings);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// If no JSON blocks found, extract bullet-point findings
|
|
159
|
+
if (security.length === 0 && economic.length === 0 && gas.length === 0) {
|
|
160
|
+
const findingRegex = /(?:^|\n)\s*[-*•]\s*((?:CRITICAL|HIGH|MEDIUM|LOW|INFO)\s*[:\-–]\s*.+)/gi;
|
|
161
|
+
let match;
|
|
162
|
+
while ((match = findingRegex.exec(text)) !== null) {
|
|
163
|
+
const line = match[1].trim();
|
|
164
|
+
const severity = line.match(/^(CRITICAL|HIGH|MEDIUM|LOW|INFO)/i)?.[1] ?? "INFO";
|
|
165
|
+
const title = line.replace(/^(?:CRITICAL|HIGH|MEDIUM|LOW|INFO)\s*[:\-–]\s*/i, "").trim();
|
|
166
|
+
security.push({ severity: severity.toUpperCase(), title, description: title });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const riskScore = estimateRiskScore(text);
|
|
170
|
+
// Derive verdict from risk score
|
|
171
|
+
let verdict;
|
|
172
|
+
if (riskScore <= 15)
|
|
173
|
+
verdict = "SAFE";
|
|
174
|
+
else if (riskScore <= 50)
|
|
175
|
+
verdict = "CAUTION";
|
|
176
|
+
else
|
|
177
|
+
verdict = "DANGER";
|
|
178
|
+
// Build summary from first meaningful paragraph
|
|
179
|
+
const summaryMatch = text.match(/(?:summary|conclusion|overall|executive)[:\s]*([^\n]{20,500})/i);
|
|
180
|
+
const summary = summaryMatch?.[1]?.trim() ?? text.slice(0, 500);
|
|
181
|
+
return {
|
|
182
|
+
riskScore,
|
|
183
|
+
verdict,
|
|
184
|
+
findings: { security, economic, gas },
|
|
185
|
+
strengths: [],
|
|
186
|
+
weaknesses: [],
|
|
187
|
+
redFlags: [],
|
|
188
|
+
copyLikelihoodScore: 0,
|
|
189
|
+
complexityScore: 50,
|
|
190
|
+
summary,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function parseTokenRiskText(text) {
|
|
194
|
+
const parsed = tryParseJson(text);
|
|
195
|
+
if (parsed &&
|
|
196
|
+
typeof parsed.riskScore === "number" &&
|
|
197
|
+
typeof parsed.verdict === "string") {
|
|
198
|
+
return {
|
|
199
|
+
riskScore: parsed.riskScore,
|
|
200
|
+
verdict: parsed.verdict,
|
|
201
|
+
findings: parsed.findings ?? { contract: [], tokenomics: [] },
|
|
202
|
+
copyLikelihoodScore: typeof parsed.copy_likelihood_score === "number"
|
|
203
|
+
? parsed.copy_likelihood_score
|
|
204
|
+
: typeof parsed.copyLikelihoodScore === "number"
|
|
205
|
+
? parsed.copyLikelihoodScore
|
|
206
|
+
: 0,
|
|
207
|
+
timelineAnomalies: Array.isArray(parsed.timeline_anomalies)
|
|
208
|
+
? parsed.timeline_anomalies
|
|
209
|
+
: Array.isArray(parsed.timelineAnomalies)
|
|
210
|
+
? parsed.timelineAnomalies
|
|
211
|
+
: [],
|
|
212
|
+
summary: parsed.summary ?? text.slice(0, 500),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Extract verdict from text
|
|
216
|
+
let verdict = "CAUTION"; // default
|
|
217
|
+
if (/\bDANGER\b/i.test(text))
|
|
218
|
+
verdict = "DANGER";
|
|
219
|
+
else if (/\bSAFE\b/i.test(text) && !/\bNOT\s+SAFE\b/i.test(text))
|
|
220
|
+
verdict = "SAFE";
|
|
221
|
+
else if (/\bCAUTION\b/i.test(text))
|
|
222
|
+
verdict = "CAUTION";
|
|
223
|
+
const contract = [];
|
|
224
|
+
const tokenomics = [];
|
|
225
|
+
// Extract JSON findings blocks
|
|
226
|
+
const jsonBlocks = text.match(/\{[\s\S]*?\n\}/g) ?? [];
|
|
227
|
+
for (const block of jsonBlocks) {
|
|
228
|
+
const obj = tryParseJson(block);
|
|
229
|
+
if (!obj)
|
|
230
|
+
continue;
|
|
231
|
+
const findings = Array.isArray(obj.findings) ? obj.findings : [];
|
|
232
|
+
const ctx = text.slice(Math.max(0, text.indexOf(block) - 200), text.indexOf(block));
|
|
233
|
+
if (/tokenomics|supply|holder|distribution|liquidity/i.test(ctx)) {
|
|
234
|
+
tokenomics.push(...findings);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
contract.push(...findings);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const riskScore = estimateRiskScore(text);
|
|
241
|
+
// Reconcile verdict with score if not explicitly found
|
|
242
|
+
if (verdict === "CAUTION") {
|
|
243
|
+
if (riskScore <= 25)
|
|
244
|
+
verdict = "SAFE";
|
|
245
|
+
else if (riskScore >= 61)
|
|
246
|
+
verdict = "DANGER";
|
|
247
|
+
}
|
|
248
|
+
const summaryMatch = text.match(/(?:summary|conclusion|verdict|assessment|overall)[:\s]*([^\n]{20,500})/i);
|
|
249
|
+
const summary = summaryMatch?.[1]?.trim() ?? text.slice(0, 500);
|
|
250
|
+
return {
|
|
251
|
+
riskScore,
|
|
252
|
+
verdict,
|
|
253
|
+
findings: { contract, tokenomics },
|
|
254
|
+
copyLikelihoodScore: 0,
|
|
255
|
+
timelineAnomalies: [],
|
|
256
|
+
summary,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// ── Helper: extract DAO analysis data from unstructured text ──────────
|
|
260
|
+
function parseDaoAnalyzeText(text) {
|
|
261
|
+
const parsed = tryParseJson(text);
|
|
262
|
+
if (parsed &&
|
|
263
|
+
typeof parsed.recommendation === "string" &&
|
|
264
|
+
typeof parsed.confidence === "number") {
|
|
265
|
+
return {
|
|
266
|
+
recommendation: parsed.recommendation,
|
|
267
|
+
confidence: parsed.confidence,
|
|
268
|
+
analysis: parsed.analysis ?? { economic: "", technical: "", risk: "" },
|
|
269
|
+
summary: parsed.summary ?? text.slice(0, 500),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
// Extract recommendation from text
|
|
273
|
+
let recommendation = "ABSTAIN"; // default
|
|
274
|
+
if (/\bRECOMMENDATION\s*[:\-–]\s*FOR\b/i.test(text) || /\bVOTE\s*[:\-–]?\s*FOR\b/i.test(text)) {
|
|
275
|
+
recommendation = "FOR";
|
|
276
|
+
}
|
|
277
|
+
else if (/\bRECOMMENDATION\s*[:\-–]\s*AGAINST\b/i.test(text) ||
|
|
278
|
+
/\bVOTE\s*[:\-–]?\s*AGAINST\b/i.test(text)) {
|
|
279
|
+
recommendation = "AGAINST";
|
|
280
|
+
}
|
|
281
|
+
else if (/\bAGAINST\b/i.test(text) && !/\bFOR\b/i.test(text)) {
|
|
282
|
+
recommendation = "AGAINST";
|
|
283
|
+
}
|
|
284
|
+
else if (/\bFOR\b/i.test(text) && !/\bAGAINST\b/i.test(text)) {
|
|
285
|
+
recommendation = "FOR";
|
|
286
|
+
}
|
|
287
|
+
// Extract confidence from text (look for "confidence: XX" or "XX% confident")
|
|
288
|
+
let confidence = 50; // default
|
|
289
|
+
const confMatch = text.match(/confidence\s*[:\-–]\s*(\d{1,3})/i) ??
|
|
290
|
+
text.match(/(\d{1,3})\s*%?\s*confiden/i);
|
|
291
|
+
if (confMatch?.[1]) {
|
|
292
|
+
const val = parseInt(confMatch[1], 10);
|
|
293
|
+
if (val >= 0 && val <= 100)
|
|
294
|
+
confidence = val;
|
|
295
|
+
}
|
|
296
|
+
// Extract per-agent analysis sections
|
|
297
|
+
const extractSection = (label) => {
|
|
298
|
+
const match = text.match(new RegExp(`${label.source}[:\\s]*([\\s\\S]{20,800}?)(?=\\n\\[|\\n---|\$)`, "i"));
|
|
299
|
+
return match?.[1]?.trim().slice(0, 500) ?? "";
|
|
300
|
+
};
|
|
301
|
+
const economic = extractSection(/economic|financial|treasury/);
|
|
302
|
+
const technical = extractSection(/technical|feasibility|implementation/);
|
|
303
|
+
const risk = extractSection(/risk|threat|danger/);
|
|
304
|
+
const summaryMatch = text.match(/(?:summary|conclusion|recommendation|executive)[:\s]*([^\n]{20,500})/i);
|
|
305
|
+
const summary = summaryMatch?.[1]?.trim() ?? text.slice(0, 500);
|
|
306
|
+
return { recommendation, confidence, analysis: { economic, technical, risk }, summary };
|
|
307
|
+
}
|
|
308
|
+
// ── Free tier output truncation ──────────────────────────────────────────
|
|
309
|
+
// Free calls get scores and finding COUNTS but not full finding details.
|
|
310
|
+
const FREE_TIER_PLACEHOLDER = "[Connect wallet to see full details]";
|
|
311
|
+
function truncateContractAuditForFreeTier(result, gate) {
|
|
312
|
+
if (gate.amountUsd > 0)
|
|
313
|
+
return result; // paid — return full
|
|
314
|
+
return {
|
|
315
|
+
riskScore: result.riskScore,
|
|
316
|
+
verdict: result.verdict,
|
|
317
|
+
findings: {
|
|
318
|
+
security: result.findings.security.length,
|
|
319
|
+
economic: result.findings.economic.length,
|
|
320
|
+
gas: result.findings.gas.length,
|
|
321
|
+
},
|
|
322
|
+
copyLikelihoodScore: result.copyLikelihoodScore,
|
|
323
|
+
complexityScore: result.complexityScore,
|
|
324
|
+
summary: FREE_TIER_PLACEHOLDER,
|
|
325
|
+
_preview: true,
|
|
326
|
+
_message: `Verdict: ${result.verdict}. Found ${result.findings.security.length} security, ${result.findings.economic.length} economic, and ${result.findings.gas.length} gas findings. Pay $0.10 to see full details.`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function truncateTokenRiskForFreeTier(result, gate) {
|
|
330
|
+
if (gate.amountUsd > 0)
|
|
331
|
+
return result; // paid — return full
|
|
332
|
+
return {
|
|
333
|
+
riskScore: result.riskScore,
|
|
334
|
+
verdict: result.verdict,
|
|
335
|
+
findings: {
|
|
336
|
+
contract: result.findings.contract.length,
|
|
337
|
+
tokenomics: result.findings.tokenomics.length,
|
|
338
|
+
},
|
|
339
|
+
copyLikelihoodScore: result.copyLikelihoodScore,
|
|
340
|
+
summary: FREE_TIER_PLACEHOLDER,
|
|
341
|
+
_preview: true,
|
|
342
|
+
_message: `Verdict: ${result.verdict} (${result.riskScore}/100). Found ${result.findings.contract.length} contract and ${result.findings.tokenomics.length} tokenomics findings. Pay $0.05 to see full details.`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function truncateDaoAnalyzeForFreeTier(result, gate) {
|
|
346
|
+
if (gate.amountUsd > 0)
|
|
347
|
+
return result; // paid — return full
|
|
348
|
+
return {
|
|
349
|
+
recommendation: result.recommendation,
|
|
350
|
+
confidence: result.confidence,
|
|
351
|
+
analysis: {
|
|
352
|
+
economic: FREE_TIER_PLACEHOLDER,
|
|
353
|
+
technical: FREE_TIER_PLACEHOLDER,
|
|
354
|
+
risk: FREE_TIER_PLACEHOLDER,
|
|
355
|
+
},
|
|
356
|
+
summary: FREE_TIER_PLACEHOLDER,
|
|
357
|
+
_preview: true,
|
|
358
|
+
_message: `Recommendation: ${result.recommendation} (${result.confidence}% confidence). Pay $0.10 to see full analysis.`,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// ── Catalog entries for the 3 crypto-native endpoints ──────────────────
|
|
362
|
+
export const CRYPTO_CATALOG = [
|
|
363
|
+
{
|
|
364
|
+
name: "Smart Contract Audit",
|
|
365
|
+
description: "Multi-agent smart contract pre-audit — security vulns, economic attacks, copy/clone detection, complexity assessment, gas optimization, and structured risk report with verdict (ConcurrentWorkflow, 4 agents)",
|
|
366
|
+
path: "/x402/contract-audit",
|
|
367
|
+
method: "POST",
|
|
368
|
+
priceUsd: "0.10",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "Quick Contract Audit",
|
|
372
|
+
description: "Single-agent quick security scan — fast, cheap, covers major security vulnerabilities only (1 agent, SecurityAuditor)",
|
|
373
|
+
path: "/x402/contract-audit/quick",
|
|
374
|
+
method: "POST",
|
|
375
|
+
priceUsd: "0.03",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: "Deep Contract Audit",
|
|
379
|
+
description: "Comprehensive 6-agent deep audit — security, economic, gas, copy/clone detection, plus additional verification pass with GasOptimizer and CopyDetector cross-checks (ConcurrentWorkflow, 6 agents)",
|
|
380
|
+
path: "/x402/contract-audit/deep",
|
|
381
|
+
method: "POST",
|
|
382
|
+
priceUsd: "0.25",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: "Token Risk Assessment",
|
|
386
|
+
description: "Multi-agent token risk scoring — rug pull detection, timeline anomalies, copy/clone detection, tokenomics analysis, and SAFE/CAUTION/DANGER verdict (SequentialWorkflow, 3 agents)",
|
|
387
|
+
path: "/x402/token-risk",
|
|
388
|
+
method: "POST",
|
|
389
|
+
priceUsd: "0.05",
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "DAO Proposal Analysis",
|
|
393
|
+
description: "Multi-agent DAO proposal analysis — economic impact, technical feasibility, risk assessment, and FOR/AGAINST/ABSTAIN recommendation (MixtureOfAgents, 4 agents)",
|
|
394
|
+
path: "/x402/dao-analyze",
|
|
395
|
+
method: "POST",
|
|
396
|
+
priceUsd: "0.10",
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
// ── Routes ─────────────────────────────────────────────────────────────
|
|
400
|
+
export const cryptoRoutes = [
|
|
401
|
+
// ── POST /x402/contract-audit — $0.50 ──────────────────────────────
|
|
402
|
+
{
|
|
403
|
+
type: "POST",
|
|
404
|
+
path: "/x402/contract-audit",
|
|
405
|
+
handler: async (req, res, runtime) => {
|
|
406
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
407
|
+
amountUsd: "0.10",
|
|
408
|
+
description: "Multi-agent smart contract pre-audit (4 agents, ConcurrentWorkflow)",
|
|
409
|
+
});
|
|
410
|
+
if (!gate.paid)
|
|
411
|
+
return;
|
|
412
|
+
const body = req.body ?? {};
|
|
413
|
+
const code = requireString(body, "code");
|
|
414
|
+
if (!code) {
|
|
415
|
+
res.status(400).json({ error: "Missing required field: code (non-empty string)" });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const language = typeof body.language === "string" &&
|
|
419
|
+
["solidity", "rust", "anchor"].includes(body.language)
|
|
420
|
+
? body.language
|
|
421
|
+
: "solidity";
|
|
422
|
+
const swarmsService = getSwarmsService(runtime);
|
|
423
|
+
if (!swarmsService) {
|
|
424
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const result = await swarmsService.runSwarm({
|
|
429
|
+
name: `contract-audit-${Date.now()}`,
|
|
430
|
+
description: `Smart contract audit (${language})`,
|
|
431
|
+
agents: [
|
|
432
|
+
{
|
|
433
|
+
agent_name: "SecurityAuditor",
|
|
434
|
+
system_prompt: `You are an expert smart contract security auditor specializing in ${language}. Analyze the provided code for:\n\n` +
|
|
435
|
+
"SECURITY ISSUES:\n" +
|
|
436
|
+
"- Reentrancy vulnerabilities (external calls before state updates)\n" +
|
|
437
|
+
"- Integer overflow/underflow\n" +
|
|
438
|
+
"- Access control flaws (missing owner checks, unprotected admin functions)\n" +
|
|
439
|
+
"- Front-running / MEV vectors\n" +
|
|
440
|
+
"- Oracle manipulation risks\n" +
|
|
441
|
+
"- Unchecked external calls\n" +
|
|
442
|
+
"- Delegate call risks\n" +
|
|
443
|
+
"- Flash loan attack vectors\n" +
|
|
444
|
+
"- Wallet drain patterns / hidden fee extraction\n" +
|
|
445
|
+
"- Admin backdoors / privileged functions without timelocks\n\n" +
|
|
446
|
+
"IMPORTANT CONTEXT:\n" +
|
|
447
|
+
"- Having contract addresses, token references, or crypto-related functionality is EXPECTED and NORMAL — not a red flag.\n" +
|
|
448
|
+
"- Only report REAL issues you found in the code. Do NOT invent problems or pad your findings.\n" +
|
|
449
|
+
"- Write findings for non-developers — explain like talking to a smart 15-year-old.\n\n" +
|
|
450
|
+
"For each finding, provide:\n" +
|
|
451
|
+
'- severity: "critical" | "high" | "medium" | "low" | "info"\n' +
|
|
452
|
+
"- title: short description\n" +
|
|
453
|
+
"- description: what the issue is and why it matters\n" +
|
|
454
|
+
"- location: where in the code (line/function reference)\n" +
|
|
455
|
+
"- recommendation: how to fix it\n\n" +
|
|
456
|
+
'Output a JSON object: { "findings": [{ "severity": "...", "title": "...", "description": "...", "location": "...", "recommendation": "..." }], ' +
|
|
457
|
+
'"legitimacy_score": <0-100> }',
|
|
458
|
+
model_name: "gpt-4o",
|
|
459
|
+
role: "worker",
|
|
460
|
+
max_loops: 1,
|
|
461
|
+
max_tokens: 4096,
|
|
462
|
+
temperature: 0.2,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
agent_name: "EconomicAttacker",
|
|
466
|
+
system_prompt: `You are an economic security analyst evaluating ${language} smart contracts.\n\n` +
|
|
467
|
+
"ECONOMIC ATTACK VECTORS:\n" +
|
|
468
|
+
"- MEV / sandwich attack surface\n" +
|
|
469
|
+
"- Flash loan exploit paths\n" +
|
|
470
|
+
"- Oracle manipulation (price feed dependency)\n" +
|
|
471
|
+
"- Front-running opportunities\n" +
|
|
472
|
+
"- Liquidity manipulation vectors\n" +
|
|
473
|
+
"- Token economics manipulation (mint/burn authority abuse)\n\n" +
|
|
474
|
+
"COPY/CLONE DETECTION:\n" +
|
|
475
|
+
"- References to different project names in comments, strings, or configs\n" +
|
|
476
|
+
"- Package.json/Cargo.toml with mismatched project names\n" +
|
|
477
|
+
"- TODO comments or placeholders from other projects\n" +
|
|
478
|
+
"- Generic template code with no customization\n" +
|
|
479
|
+
"- README that doesn't match the actual code\n" +
|
|
480
|
+
"- Very shallow commit history (1-2 bulk commits)\n\n" +
|
|
481
|
+
"COMPLEXITY ASSESSMENT:\n" +
|
|
482
|
+
"- HIGH EFFORT: Multiple interconnected files, custom business logic, error handling, tests\n" +
|
|
483
|
+
"- LOW EFFORT: Single file, just boilerplate, no custom logic, no error handling\n\n" +
|
|
484
|
+
"Output a JSON object: {\n" +
|
|
485
|
+
' "economic_findings": [{ "severity": "...", "title": "...", "attackScenario": "...", "potentialImpact": "..." }],\n' +
|
|
486
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
487
|
+
' "complexity_score": <0-100>\n' +
|
|
488
|
+
"}",
|
|
489
|
+
model_name: "gpt-4o",
|
|
490
|
+
role: "worker",
|
|
491
|
+
max_loops: 1,
|
|
492
|
+
max_tokens: 4096,
|
|
493
|
+
temperature: 0.3,
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
agent_name: "GasOptimizer",
|
|
497
|
+
system_prompt: `You are a ${language} gas optimization specialist. ` +
|
|
498
|
+
"Analyze the code for gas inefficiencies: " +
|
|
499
|
+
"unnecessary storage reads (SLOADs), suboptimal storage packing, " +
|
|
500
|
+
"loop inefficiencies, redundant computations, calldata vs memory misuse, " +
|
|
501
|
+
"missing short-circuit logic, expensive operations in loops, " +
|
|
502
|
+
"and opportunities for using unchecked blocks or assembly. " +
|
|
503
|
+
"Estimate gas savings for each suggestion. " +
|
|
504
|
+
"Output a JSON object: { \"findings\": [{ \"title\": \"...\", \"description\": \"...\", \"estimatedSavings\": \"...\" }] }",
|
|
505
|
+
model_name: "gpt-4o-mini",
|
|
506
|
+
role: "worker",
|
|
507
|
+
max_loops: 1,
|
|
508
|
+
max_tokens: 4096,
|
|
509
|
+
temperature: 0.3,
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
agent_name: "AuditReporter",
|
|
513
|
+
system_prompt: "You are the final audit report synthesizer. Combine findings from the SecurityAuditor, EconomicAttacker, and GasOptimizer into a structured report.\n\n" +
|
|
514
|
+
"OUTPUT FORMAT (JSON):\n" +
|
|
515
|
+
"{\n" +
|
|
516
|
+
' "riskScore": <0-100 integer>,\n' +
|
|
517
|
+
' "verdict": "SAFE" | "CAUTION" | "DANGER",\n' +
|
|
518
|
+
' "findings": {\n' +
|
|
519
|
+
' "security": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}],\n' +
|
|
520
|
+
' "economic": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}],\n' +
|
|
521
|
+
' "gas": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}]\n' +
|
|
522
|
+
" },\n" +
|
|
523
|
+
' "strengths": ["..."],\n' +
|
|
524
|
+
' "weaknesses": ["..."],\n' +
|
|
525
|
+
' "red_flags": ["..."],\n' +
|
|
526
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
527
|
+
' "complexity_score": <0-100>,\n' +
|
|
528
|
+
' "summary": "Non-technical summary for a smart 15-year-old"\n' +
|
|
529
|
+
"}\n\n" +
|
|
530
|
+
"SCORING RUBRIC (riskScore is LEGITIMACY — higher = safer):\n" +
|
|
531
|
+
"85-100: Well-written, secure, legitimate project\n" +
|
|
532
|
+
"70-84: Good project with minor issues\n" +
|
|
533
|
+
"50-69: Concerning issues that need attention\n" +
|
|
534
|
+
"30-49: Significant problems or red flags\n" +
|
|
535
|
+
"0-29: Likely scam or severely compromised\n\n" +
|
|
536
|
+
"Only report REAL issues. Don't invent problems to make the report look thorough.\n" +
|
|
537
|
+
"Output ONLY the JSON object — no markdown fences, no extra text.",
|
|
538
|
+
model_name: "gpt-4o",
|
|
539
|
+
role: "worker",
|
|
540
|
+
max_loops: 1,
|
|
541
|
+
max_tokens: 8192,
|
|
542
|
+
temperature: 0.2,
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
swarm_type: "ConcurrentWorkflow",
|
|
546
|
+
task: `Audit the following ${language} smart contract code. ` +
|
|
547
|
+
"Each agent should analyze from their specialization and the AuditReporter should synthesize all findings.\n\n" +
|
|
548
|
+
"```\n" +
|
|
549
|
+
code +
|
|
550
|
+
"\n```",
|
|
551
|
+
max_loops: 1,
|
|
552
|
+
});
|
|
553
|
+
const rawOutput = extractSwarmOutput(result);
|
|
554
|
+
const audit = parseContractAuditText(rawOutput);
|
|
555
|
+
const truncated = truncateContractAuditForFreeTier(audit, gate);
|
|
556
|
+
// Save report for shareable link + badge
|
|
557
|
+
const reportId = saveReport({
|
|
558
|
+
type: "contract-audit",
|
|
559
|
+
createdAt: new Date().toISOString(),
|
|
560
|
+
input: { code: code.slice(0, 2000), language },
|
|
561
|
+
result: audit,
|
|
562
|
+
riskScore: audit.riskScore,
|
|
563
|
+
paid: gate.amountUsd > 0,
|
|
564
|
+
});
|
|
565
|
+
const urls = reportUrls(reportId);
|
|
566
|
+
res.json({
|
|
567
|
+
...truncated,
|
|
568
|
+
...urls,
|
|
569
|
+
rawOutput: gate.amountUsd > 0 ? rawOutput : undefined,
|
|
570
|
+
template: "ContractAudit",
|
|
571
|
+
freeRemaining: gate.freeRemaining,
|
|
572
|
+
payment: {
|
|
573
|
+
amount: "0.10",
|
|
574
|
+
transaction: gate.transaction,
|
|
575
|
+
network: gate.network,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
catch (err) {
|
|
580
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/contract-audit] Swarm execution failed");
|
|
581
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
// ── POST /x402/contract-audit/quick — $0.03 (single SecurityAuditor) ──
|
|
586
|
+
{
|
|
587
|
+
type: "POST",
|
|
588
|
+
path: "/x402/contract-audit/quick",
|
|
589
|
+
handler: async (req, res, runtime) => {
|
|
590
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
591
|
+
amountUsd: "0.03",
|
|
592
|
+
description: "Quick smart contract security scan (1 agent, SecurityAuditor)",
|
|
593
|
+
});
|
|
594
|
+
if (!gate.paid)
|
|
595
|
+
return;
|
|
596
|
+
const body = req.body ?? {};
|
|
597
|
+
const code = requireString(body, "code");
|
|
598
|
+
if (!code) {
|
|
599
|
+
res.status(400).json({ error: "Missing required field: code (non-empty string)" });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const language = typeof body.language === "string" &&
|
|
603
|
+
["solidity", "rust", "anchor"].includes(body.language)
|
|
604
|
+
? body.language
|
|
605
|
+
: "solidity";
|
|
606
|
+
const swarmsService = getSwarmsService(runtime);
|
|
607
|
+
if (!swarmsService) {
|
|
608
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
const result = await swarmsService.runAgent({
|
|
613
|
+
agent_name: "SecurityAuditor",
|
|
614
|
+
system_prompt: `You are an expert smart contract security auditor specializing in ${language}. Analyze the provided code for:\n\n` +
|
|
615
|
+
"SECURITY ISSUES:\n" +
|
|
616
|
+
"- Reentrancy vulnerabilities (external calls before state updates)\n" +
|
|
617
|
+
"- Integer overflow/underflow\n" +
|
|
618
|
+
"- Access control flaws (missing owner checks, unprotected admin functions)\n" +
|
|
619
|
+
"- Front-running / MEV vectors\n" +
|
|
620
|
+
"- Oracle manipulation risks\n" +
|
|
621
|
+
"- Unchecked external calls\n" +
|
|
622
|
+
"- Delegate call risks\n" +
|
|
623
|
+
"- Flash loan attack vectors\n" +
|
|
624
|
+
"- Wallet drain patterns / hidden fee extraction\n" +
|
|
625
|
+
"- Admin backdoors / privileged functions without timelocks\n\n" +
|
|
626
|
+
"IMPORTANT: Only report REAL issues you found in the code. Do NOT invent problems.\n\n" +
|
|
627
|
+
"Output ONLY a JSON object with this structure:\n" +
|
|
628
|
+
"{\n" +
|
|
629
|
+
' "riskScore": <0-100 integer>,\n' +
|
|
630
|
+
' "verdict": "SAFE" | "CAUTION" | "DANGER",\n' +
|
|
631
|
+
' "findings": {\n' +
|
|
632
|
+
' "security": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}],\n' +
|
|
633
|
+
' "economic": [],\n' +
|
|
634
|
+
' "gas": []\n' +
|
|
635
|
+
" },\n" +
|
|
636
|
+
' "strengths": ["..."],\n' +
|
|
637
|
+
' "weaknesses": ["..."],\n' +
|
|
638
|
+
' "red_flags": ["..."],\n' +
|
|
639
|
+
' "copy_likelihood_score": 0,\n' +
|
|
640
|
+
' "complexity_score": <0-100>,\n' +
|
|
641
|
+
' "summary": "Non-technical summary for a smart 15-year-old"\n' +
|
|
642
|
+
"}\n\n" +
|
|
643
|
+
"Output ONLY the JSON object — no markdown fences, no extra text.",
|
|
644
|
+
model_name: "gpt-4o",
|
|
645
|
+
role: "worker",
|
|
646
|
+
max_loops: 1,
|
|
647
|
+
max_tokens: 4096,
|
|
648
|
+
temperature: 0.2,
|
|
649
|
+
}, `Audit the following ${language} smart contract code for security issues.\n\n` +
|
|
650
|
+
"```\n" +
|
|
651
|
+
code +
|
|
652
|
+
"\n```");
|
|
653
|
+
const rawOutput = String(result.outputs ?? result);
|
|
654
|
+
const audit = parseContractAuditText(rawOutput);
|
|
655
|
+
const truncated = truncateContractAuditForFreeTier(audit, gate);
|
|
656
|
+
// Save report for shareable link + badge
|
|
657
|
+
const reportId = saveReport({
|
|
658
|
+
type: "contract-audit",
|
|
659
|
+
createdAt: new Date().toISOString(),
|
|
660
|
+
input: { code: code.slice(0, 2000), language },
|
|
661
|
+
result: audit,
|
|
662
|
+
riskScore: audit.riskScore,
|
|
663
|
+
paid: gate.amountUsd > 0,
|
|
664
|
+
});
|
|
665
|
+
const urls = reportUrls(reportId);
|
|
666
|
+
res.json({
|
|
667
|
+
...truncated,
|
|
668
|
+
...urls,
|
|
669
|
+
rawOutput: gate.amountUsd > 0 ? rawOutput : undefined,
|
|
670
|
+
template: "ContractAuditQuick",
|
|
671
|
+
tier: "quick",
|
|
672
|
+
freeRemaining: gate.freeRemaining,
|
|
673
|
+
payment: {
|
|
674
|
+
amount: "0.03",
|
|
675
|
+
transaction: gate.transaction,
|
|
676
|
+
network: gate.network,
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
catch (err) {
|
|
681
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/contract-audit/quick] Agent execution failed");
|
|
682
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
// ── POST /x402/contract-audit/deep — $0.25 (6 agents) ────────────
|
|
687
|
+
{
|
|
688
|
+
type: "POST",
|
|
689
|
+
path: "/x402/contract-audit/deep",
|
|
690
|
+
handler: async (req, res, runtime) => {
|
|
691
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
692
|
+
amountUsd: "0.25",
|
|
693
|
+
description: "Deep smart contract audit with verification pass (6 agents, ConcurrentWorkflow)",
|
|
694
|
+
});
|
|
695
|
+
if (!gate.paid)
|
|
696
|
+
return;
|
|
697
|
+
const body = req.body ?? {};
|
|
698
|
+
const code = requireString(body, "code");
|
|
699
|
+
if (!code) {
|
|
700
|
+
res.status(400).json({ error: "Missing required field: code (non-empty string)" });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const language = typeof body.language === "string" &&
|
|
704
|
+
["solidity", "rust", "anchor"].includes(body.language)
|
|
705
|
+
? body.language
|
|
706
|
+
: "solidity";
|
|
707
|
+
const swarmsService = getSwarmsService(runtime);
|
|
708
|
+
if (!swarmsService) {
|
|
709
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const result = await swarmsService.runSwarm({
|
|
714
|
+
name: `contract-audit-deep-${Date.now()}`,
|
|
715
|
+
description: `Deep smart contract audit (${language})`,
|
|
716
|
+
agents: [
|
|
717
|
+
{
|
|
718
|
+
agent_name: "SecurityAuditor",
|
|
719
|
+
system_prompt: `You are an expert smart contract security auditor specializing in ${language}. Analyze the provided code for:\n\n` +
|
|
720
|
+
"SECURITY ISSUES:\n" +
|
|
721
|
+
"- Reentrancy vulnerabilities (external calls before state updates)\n" +
|
|
722
|
+
"- Integer overflow/underflow\n" +
|
|
723
|
+
"- Access control flaws (missing owner checks, unprotected admin functions)\n" +
|
|
724
|
+
"- Front-running / MEV vectors\n" +
|
|
725
|
+
"- Oracle manipulation risks\n" +
|
|
726
|
+
"- Unchecked external calls\n" +
|
|
727
|
+
"- Delegate call risks\n" +
|
|
728
|
+
"- Flash loan attack vectors\n" +
|
|
729
|
+
"- Wallet drain patterns / hidden fee extraction\n" +
|
|
730
|
+
"- Admin backdoors / privileged functions without timelocks\n\n" +
|
|
731
|
+
"IMPORTANT CONTEXT:\n" +
|
|
732
|
+
"- Having contract addresses, token references, or crypto-related functionality is EXPECTED and NORMAL — not a red flag.\n" +
|
|
733
|
+
"- Only report REAL issues you found in the code. Do NOT invent problems or pad your findings.\n" +
|
|
734
|
+
"- Write findings for non-developers — explain like talking to a smart 15-year-old.\n\n" +
|
|
735
|
+
"For each finding, provide:\n" +
|
|
736
|
+
'- severity: "critical" | "high" | "medium" | "low" | "info"\n' +
|
|
737
|
+
"- title: short description\n" +
|
|
738
|
+
"- description: what the issue is and why it matters\n" +
|
|
739
|
+
"- location: where in the code (line/function reference)\n" +
|
|
740
|
+
"- recommendation: how to fix it\n\n" +
|
|
741
|
+
'Output a JSON object: { "findings": [{ "severity": "...", "title": "...", "description": "...", "location": "...", "recommendation": "..." }], ' +
|
|
742
|
+
'"legitimacy_score": <0-100> }',
|
|
743
|
+
model_name: "gpt-4o",
|
|
744
|
+
role: "worker",
|
|
745
|
+
max_loops: 1,
|
|
746
|
+
max_tokens: 4096,
|
|
747
|
+
temperature: 0.2,
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
agent_name: "EconomicAttacker",
|
|
751
|
+
system_prompt: `You are an economic security analyst evaluating ${language} smart contracts.\n\n` +
|
|
752
|
+
"ECONOMIC ATTACK VECTORS:\n" +
|
|
753
|
+
"- MEV / sandwich attack surface\n" +
|
|
754
|
+
"- Flash loan exploit paths\n" +
|
|
755
|
+
"- Oracle manipulation (price feed dependency)\n" +
|
|
756
|
+
"- Front-running opportunities\n" +
|
|
757
|
+
"- Liquidity manipulation vectors\n" +
|
|
758
|
+
"- Token economics manipulation (mint/burn authority abuse)\n\n" +
|
|
759
|
+
"COPY/CLONE DETECTION:\n" +
|
|
760
|
+
"- References to different project names in comments, strings, or configs\n" +
|
|
761
|
+
"- Package.json/Cargo.toml with mismatched project names\n" +
|
|
762
|
+
"- TODO comments or placeholders from other projects\n" +
|
|
763
|
+
"- Generic template code with no customization\n" +
|
|
764
|
+
"- README that doesn't match the actual code\n" +
|
|
765
|
+
"- Very shallow commit history (1-2 bulk commits)\n\n" +
|
|
766
|
+
"COMPLEXITY ASSESSMENT:\n" +
|
|
767
|
+
"- HIGH EFFORT: Multiple interconnected files, custom business logic, error handling, tests\n" +
|
|
768
|
+
"- LOW EFFORT: Single file, just boilerplate, no custom logic, no error handling\n\n" +
|
|
769
|
+
"Output a JSON object: {\n" +
|
|
770
|
+
' "economic_findings": [{ "severity": "...", "title": "...", "attackScenario": "...", "potentialImpact": "..." }],\n' +
|
|
771
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
772
|
+
' "complexity_score": <0-100>\n' +
|
|
773
|
+
"}",
|
|
774
|
+
model_name: "gpt-4o",
|
|
775
|
+
role: "worker",
|
|
776
|
+
max_loops: 1,
|
|
777
|
+
max_tokens: 4096,
|
|
778
|
+
temperature: 0.3,
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
agent_name: "GasOptimizer",
|
|
782
|
+
system_prompt: `You are a ${language} gas optimization specialist. ` +
|
|
783
|
+
"Analyze the code for gas inefficiencies: " +
|
|
784
|
+
"unnecessary storage reads (SLOADs), suboptimal storage packing, " +
|
|
785
|
+
"loop inefficiencies, redundant computations, calldata vs memory misuse, " +
|
|
786
|
+
"missing short-circuit logic, expensive operations in loops, " +
|
|
787
|
+
"and opportunities for using unchecked blocks or assembly. " +
|
|
788
|
+
"Estimate gas savings for each suggestion. " +
|
|
789
|
+
"Output a JSON object: { \"findings\": [{ \"title\": \"...\", \"description\": \"...\", \"estimatedSavings\": \"...\" }] }",
|
|
790
|
+
model_name: "gpt-4o-mini",
|
|
791
|
+
role: "worker",
|
|
792
|
+
max_loops: 1,
|
|
793
|
+
max_tokens: 4096,
|
|
794
|
+
temperature: 0.3,
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
agent_name: "CopyDetector",
|
|
798
|
+
system_prompt: `You are a ${language} code originality analyst. ` +
|
|
799
|
+
"Determine if the smart contract is original or a copy/fork/clone.\n\n" +
|
|
800
|
+
"DETECTION CRITERIA:\n" +
|
|
801
|
+
"- Compare code structure to well-known templates (OpenZeppelin, SafeMoon, standard ERC20/721)\n" +
|
|
802
|
+
"- Look for residual project names, comments, or URLs from other projects\n" +
|
|
803
|
+
"- Check for unchanged default values or placeholder strings\n" +
|
|
804
|
+
"- Assess customization depth vs boilerplate ratio\n" +
|
|
805
|
+
"- Identify if the code is a minimal fork with only name/symbol changes\n" +
|
|
806
|
+
"- Look for copy-paste artifacts (mismatched naming, dead code from original)\n\n" +
|
|
807
|
+
"Output a JSON object: {\n" +
|
|
808
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
809
|
+
' "source_matches": ["<known project/template this resembles>"],\n' +
|
|
810
|
+
' "originality_assessment": "<detailed explanation>",\n' +
|
|
811
|
+
' "customization_depth": "none|minimal|moderate|extensive"\n' +
|
|
812
|
+
"}",
|
|
813
|
+
model_name: "gpt-4o",
|
|
814
|
+
role: "worker",
|
|
815
|
+
max_loops: 1,
|
|
816
|
+
max_tokens: 4096,
|
|
817
|
+
temperature: 0.2,
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
agent_name: "VerificationAuditor",
|
|
821
|
+
system_prompt: "You are a verification auditor performing a second-pass review. " +
|
|
822
|
+
"You receive findings from SecurityAuditor, EconomicAttacker, GasOptimizer, and CopyDetector. " +
|
|
823
|
+
"Your job is to:\n" +
|
|
824
|
+
"1. Confirm or reject each finding — remove false positives\n" +
|
|
825
|
+
"2. Identify any issues missed by the other agents\n" +
|
|
826
|
+
"3. Cross-check severity ratings for consistency\n" +
|
|
827
|
+
"4. Flag if copy detection and security findings are contradictory\n\n" +
|
|
828
|
+
"Output a JSON object: {\n" +
|
|
829
|
+
' "confirmed_findings": [{ "agent": "...", "title": "...", "status": "confirmed|rejected|upgraded|downgraded" }],\n' +
|
|
830
|
+
' "missed_issues": [{ "severity": "...", "title": "...", "description": "..." }],\n' +
|
|
831
|
+
' "verification_notes": "..."\n' +
|
|
832
|
+
"}",
|
|
833
|
+
model_name: "gpt-4o",
|
|
834
|
+
role: "worker",
|
|
835
|
+
max_loops: 1,
|
|
836
|
+
max_tokens: 4096,
|
|
837
|
+
temperature: 0.2,
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
agent_name: "AuditReporter",
|
|
841
|
+
system_prompt: "You are the final audit report synthesizer for a DEEP audit. " +
|
|
842
|
+
"Combine findings from SecurityAuditor, EconomicAttacker, GasOptimizer, CopyDetector, and VerificationAuditor into a comprehensive structured report.\n\n" +
|
|
843
|
+
"The VerificationAuditor has already confirmed/rejected findings — prioritize confirmed findings and include any missed issues they identified.\n\n" +
|
|
844
|
+
"OUTPUT FORMAT (JSON):\n" +
|
|
845
|
+
"{\n" +
|
|
846
|
+
' "riskScore": <0-100 integer>,\n' +
|
|
847
|
+
' "verdict": "SAFE" | "CAUTION" | "DANGER",\n' +
|
|
848
|
+
' "findings": {\n' +
|
|
849
|
+
' "security": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}],\n' +
|
|
850
|
+
' "economic": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}],\n' +
|
|
851
|
+
' "gas": [{"severity": "...", "title": "...", "description": "...", "recommendation": "..."}]\n' +
|
|
852
|
+
" },\n" +
|
|
853
|
+
' "strengths": ["..."],\n' +
|
|
854
|
+
' "weaknesses": ["..."],\n' +
|
|
855
|
+
' "red_flags": ["..."],\n' +
|
|
856
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
857
|
+
' "complexity_score": <0-100>,\n' +
|
|
858
|
+
' "summary": "Non-technical summary for a smart 15-year-old"\n' +
|
|
859
|
+
"}\n\n" +
|
|
860
|
+
"SCORING RUBRIC (riskScore is LEGITIMACY — higher = safer):\n" +
|
|
861
|
+
"85-100: Well-written, secure, legitimate project\n" +
|
|
862
|
+
"70-84: Good project with minor issues\n" +
|
|
863
|
+
"50-69: Concerning issues that need attention\n" +
|
|
864
|
+
"30-49: Significant problems or red flags\n" +
|
|
865
|
+
"0-29: Likely scam or severely compromised\n\n" +
|
|
866
|
+
"Only report CONFIRMED issues. Don't invent problems.\n" +
|
|
867
|
+
"Output ONLY the JSON object — no markdown fences, no extra text.",
|
|
868
|
+
model_name: "gpt-4o",
|
|
869
|
+
role: "worker",
|
|
870
|
+
max_loops: 1,
|
|
871
|
+
max_tokens: 8192,
|
|
872
|
+
temperature: 0.2,
|
|
873
|
+
},
|
|
874
|
+
],
|
|
875
|
+
swarm_type: "ConcurrentWorkflow",
|
|
876
|
+
task: `Deep audit the following ${language} smart contract code. ` +
|
|
877
|
+
"Each agent should analyze from their specialization. The VerificationAuditor should cross-check all findings. " +
|
|
878
|
+
"The AuditReporter should synthesize all confirmed findings into a comprehensive report.\n\n" +
|
|
879
|
+
"```\n" +
|
|
880
|
+
code +
|
|
881
|
+
"\n```",
|
|
882
|
+
max_loops: 1,
|
|
883
|
+
});
|
|
884
|
+
const rawOutput = extractSwarmOutput(result);
|
|
885
|
+
const audit = parseContractAuditText(rawOutput);
|
|
886
|
+
const truncated = truncateContractAuditForFreeTier(audit, gate);
|
|
887
|
+
// Save report for shareable link + badge
|
|
888
|
+
const reportId = saveReport({
|
|
889
|
+
type: "contract-audit",
|
|
890
|
+
createdAt: new Date().toISOString(),
|
|
891
|
+
input: { code: code.slice(0, 2000), language },
|
|
892
|
+
result: audit,
|
|
893
|
+
riskScore: audit.riskScore,
|
|
894
|
+
paid: gate.amountUsd > 0,
|
|
895
|
+
});
|
|
896
|
+
const urls = reportUrls(reportId);
|
|
897
|
+
res.json({
|
|
898
|
+
...truncated,
|
|
899
|
+
...urls,
|
|
900
|
+
rawOutput: gate.amountUsd > 0 ? rawOutput : undefined,
|
|
901
|
+
template: "ContractAuditDeep",
|
|
902
|
+
tier: "deep",
|
|
903
|
+
freeRemaining: gate.freeRemaining,
|
|
904
|
+
payment: {
|
|
905
|
+
amount: "0.25",
|
|
906
|
+
transaction: gate.transaction,
|
|
907
|
+
network: gate.network,
|
|
908
|
+
},
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
catch (err) {
|
|
912
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/contract-audit/deep] Swarm execution failed");
|
|
913
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
// ── POST /x402/token-risk — $0.05 ──────────────────────────────────
|
|
918
|
+
{
|
|
919
|
+
type: "POST",
|
|
920
|
+
path: "/x402/token-risk",
|
|
921
|
+
handler: async (req, res, runtime) => {
|
|
922
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
923
|
+
amountUsd: "0.05",
|
|
924
|
+
description: "Multi-agent token risk assessment (3 agents, SequentialWorkflow)",
|
|
925
|
+
});
|
|
926
|
+
if (!gate.paid)
|
|
927
|
+
return;
|
|
928
|
+
const body = req.body ?? {};
|
|
929
|
+
const mint = requireString(body, "mint", 200);
|
|
930
|
+
if (!mint) {
|
|
931
|
+
res.status(400).json({ error: "Missing required field: mint (token address string)" });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const chain = typeof body.chain === "string" && ["solana", "evm"].includes(body.chain)
|
|
935
|
+
? body.chain
|
|
936
|
+
: "solana";
|
|
937
|
+
const swarmsService = getSwarmsService(runtime);
|
|
938
|
+
if (!swarmsService) {
|
|
939
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
// ── Optional on-chain data via Helius (Solana only) ────────────
|
|
943
|
+
let onChainContext = "";
|
|
944
|
+
if (chain === "solana" && SOLANA_ADDR_RE.test(mint)) {
|
|
945
|
+
const heliusKey = String(runtime.getSetting("HELIUS_API_KEY") ?? "");
|
|
946
|
+
if (heliusKey) {
|
|
947
|
+
try {
|
|
948
|
+
const rpcUrl = heliusRpcUrl(heliusKey);
|
|
949
|
+
const holders = await rpcCall(rpcUrl, "getTokenLargestAccounts", [mint]);
|
|
950
|
+
if (holders?.value && Array.isArray(holders.value)) {
|
|
951
|
+
const totalSupply = holders.value.reduce((sum, h) => sum + parseFloat(h.amount ?? "0"), 0);
|
|
952
|
+
const topHolders = holders.value.slice(0, 10).map((h, i) => ({
|
|
953
|
+
rank: i + 1,
|
|
954
|
+
address: h.address,
|
|
955
|
+
amount: h.amount,
|
|
956
|
+
percentage: totalSupply > 0
|
|
957
|
+
? ((parseFloat(h.amount ?? "0") / totalSupply) * 100).toFixed(2) + "%"
|
|
958
|
+
: "N/A",
|
|
959
|
+
}));
|
|
960
|
+
onChainContext =
|
|
961
|
+
"\n\n--- ON-CHAIN DATA (Solana) ---\n" +
|
|
962
|
+
`Token mint: ${mint}\n` +
|
|
963
|
+
`Top 10 holders (of ${holders.value.length} total accounts):\n` +
|
|
964
|
+
JSON.stringify(topHolders, null, 2) +
|
|
965
|
+
"\n--- END ON-CHAIN DATA ---\n";
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
catch (err) {
|
|
969
|
+
// Non-fatal: proceed with LLM knowledge only
|
|
970
|
+
runtime.logger.warn({ error: err instanceof Error ? err.message : String(err) }, "[x402/token-risk] Helius lookup failed, proceeding with LLM knowledge only");
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
const result = await swarmsService.runSwarm({
|
|
976
|
+
name: `token-risk-${Date.now()}`,
|
|
977
|
+
description: `Token risk assessment: ${mint}`,
|
|
978
|
+
agents: [
|
|
979
|
+
{
|
|
980
|
+
agent_name: "ContractScanner",
|
|
981
|
+
system_prompt: "You are a smart contract scanner specializing in detecting rug pull patterns and timeline anomalies.\n\n" +
|
|
982
|
+
"CONTRACT ANALYSIS:\n" +
|
|
983
|
+
"- Mint authority (can new tokens be minted?)\n" +
|
|
984
|
+
"- Freeze authority (can transfers be frozen?)\n" +
|
|
985
|
+
"- Honeypot patterns (can tokens be sold?)\n" +
|
|
986
|
+
"- Blacklist/whitelist functions\n" +
|
|
987
|
+
"- Hidden fees or fee manipulation\n" +
|
|
988
|
+
"- Proxy/upgradeable patterns (admin can change logic)\n" +
|
|
989
|
+
"- Self-destruct mechanisms\n" +
|
|
990
|
+
"- Ownership renouncement status\n" +
|
|
991
|
+
"- Wallet drain patterns / hidden fee extraction\n" +
|
|
992
|
+
"- Admin backdoors / privileged functions without timelocks\n\n" +
|
|
993
|
+
"TIMELINE ANOMALY DETECTION:\n" +
|
|
994
|
+
"- Token deployed very recently with instant large liquidity (suspicious)\n" +
|
|
995
|
+
"- Contract verified long after deployment (hiding initial malicious code)\n" +
|
|
996
|
+
"- Large number of tokens sent to multiple wallets immediately after creation\n" +
|
|
997
|
+
"- Contract interactions that suggest coordinated wash trading\n" +
|
|
998
|
+
"- Ownership transferred multiple times in rapid succession\n\n" +
|
|
999
|
+
"IMPORTANT: Having contract addresses and token references is NORMAL. Only report REAL issues.\n\n" +
|
|
1000
|
+
'Output a JSON object: { "findings": [{ "risk": "critical|high|medium|low|info", "title": "...", "description": "..." }], ' +
|
|
1001
|
+
'"mintAuthority": "active|renounced|unknown", "freezeAuthority": "active|renounced|unknown", ' +
|
|
1002
|
+
'"timeline_anomalies": ["..."] }',
|
|
1003
|
+
model_name: "gpt-4o",
|
|
1004
|
+
role: "worker",
|
|
1005
|
+
max_loops: 1,
|
|
1006
|
+
max_tokens: 4096,
|
|
1007
|
+
temperature: 0.2,
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
agent_name: "TokenomicsAnalyzer",
|
|
1011
|
+
system_prompt: "You are a tokenomics analyst with copy/clone detection capabilities.\n\n" +
|
|
1012
|
+
"TOKENOMICS ANALYSIS:\n" +
|
|
1013
|
+
"- Supply distribution and top holder concentration (whales)\n" +
|
|
1014
|
+
"- Vesting schedules and unlock timelines\n" +
|
|
1015
|
+
"- Liquidity lock status and duration\n" +
|
|
1016
|
+
"- LP token distribution\n" +
|
|
1017
|
+
"- Insider allocation percentage\n" +
|
|
1018
|
+
"- Circulating vs total supply ratio\n" +
|
|
1019
|
+
"- Inflation/deflation mechanics\n" +
|
|
1020
|
+
"If on-chain data is provided, use it for concrete analysis.\n\n" +
|
|
1021
|
+
"COPY/CLONE DETECTION:\n" +
|
|
1022
|
+
"- Is this token a fork/clone of an existing well-known token?\n" +
|
|
1023
|
+
"- Are the tokenomics parameters identical to a template?\n" +
|
|
1024
|
+
"- Signs of lazy copying: default values, unchanged descriptions, mismatched naming\n" +
|
|
1025
|
+
"- Compare token economics to known patterns (e.g., standard SafeMoon fork, standard reflection token)\n\n" +
|
|
1026
|
+
'Output a JSON object: { "findings": [{ "risk": "critical|high|medium|low|info", "title": "...", "description": "..." }], ' +
|
|
1027
|
+
'"topHolderConcentration": "<percentage or unknown>", "liquidityLocked": "yes|no|unknown", ' +
|
|
1028
|
+
'"copy_likelihood_score": <0-100> }',
|
|
1029
|
+
model_name: "gpt-4o",
|
|
1030
|
+
role: "worker",
|
|
1031
|
+
max_loops: 1,
|
|
1032
|
+
max_tokens: 4096,
|
|
1033
|
+
temperature: 0.3,
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
agent_name: "RiskVerdict",
|
|
1037
|
+
system_prompt: "You are a crypto risk assessment judge. You receive findings from ContractScanner and TokenomicsAnalyzer.\n" +
|
|
1038
|
+
"Produce a final risk assessment including copy detection and timeline anomalies.\n\n" +
|
|
1039
|
+
"Output ONLY a JSON object with this exact structure:\n" +
|
|
1040
|
+
"{\n" +
|
|
1041
|
+
' "riskScore": <number 0-100>,\n' +
|
|
1042
|
+
' "verdict": "SAFE" | "CAUTION" | "DANGER",\n' +
|
|
1043
|
+
' "findings": {\n' +
|
|
1044
|
+
' "contract": [{ "risk": "...", "title": "...", "description": "..." }],\n' +
|
|
1045
|
+
' "tokenomics": [{ "risk": "...", "title": "...", "description": "..." }]\n' +
|
|
1046
|
+
" },\n" +
|
|
1047
|
+
' "copy_likelihood_score": <0-100>,\n' +
|
|
1048
|
+
' "timeline_anomalies": ["..."],\n' +
|
|
1049
|
+
' "summary": "<non-technical summary for a smart 15-year-old>"\n' +
|
|
1050
|
+
"}\n\n" +
|
|
1051
|
+
"Risk score guide: 0-25 = SAFE, 26-60 = CAUTION, 61-100 = DANGER.\n" +
|
|
1052
|
+
"Only report REAL issues. Don't invent problems.\n" +
|
|
1053
|
+
"Output ONLY the JSON object — no markdown fences, no extra text.",
|
|
1054
|
+
model_name: "gpt-4o",
|
|
1055
|
+
role: "worker",
|
|
1056
|
+
max_loops: 1,
|
|
1057
|
+
max_tokens: 4096,
|
|
1058
|
+
temperature: 0.2,
|
|
1059
|
+
},
|
|
1060
|
+
],
|
|
1061
|
+
swarm_type: "SequentialWorkflow",
|
|
1062
|
+
task: `Assess the risk of the following token on ${chain}.\n` +
|
|
1063
|
+
`Token address/mint: ${mint}\n` +
|
|
1064
|
+
"Analyze for rug pull patterns, tokenomics red flags, and produce a final risk verdict." +
|
|
1065
|
+
onChainContext,
|
|
1066
|
+
max_loops: 1,
|
|
1067
|
+
rules: "ContractScanner analyzes the contract first, TokenomicsAnalyzer evaluates supply/distribution, then RiskVerdict synthesizes both into a final score and verdict.",
|
|
1068
|
+
});
|
|
1069
|
+
const rawOutput = extractSwarmOutput(result);
|
|
1070
|
+
const risk = parseTokenRiskText(rawOutput);
|
|
1071
|
+
const truncated = truncateTokenRiskForFreeTier(risk, gate);
|
|
1072
|
+
// Save report for shareable link + badge
|
|
1073
|
+
const reportId = saveReport({
|
|
1074
|
+
type: "token-risk",
|
|
1075
|
+
createdAt: new Date().toISOString(),
|
|
1076
|
+
input: { mint, chain },
|
|
1077
|
+
result: risk,
|
|
1078
|
+
riskScore: risk.riskScore,
|
|
1079
|
+
paid: gate.amountUsd > 0,
|
|
1080
|
+
});
|
|
1081
|
+
const urls = reportUrls(reportId);
|
|
1082
|
+
res.json({
|
|
1083
|
+
...truncated,
|
|
1084
|
+
...urls,
|
|
1085
|
+
rawOutput: gate.amountUsd > 0 ? rawOutput : undefined,
|
|
1086
|
+
onChainDataUsed: onChainContext.length > 0,
|
|
1087
|
+
template: "TokenRisk",
|
|
1088
|
+
freeRemaining: gate.freeRemaining,
|
|
1089
|
+
payment: {
|
|
1090
|
+
amount: "0.05",
|
|
1091
|
+
transaction: gate.transaction,
|
|
1092
|
+
network: gate.network,
|
|
1093
|
+
},
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
catch (err) {
|
|
1097
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/token-risk] Swarm execution failed");
|
|
1098
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
// ── POST /x402/dao-analyze — $0.10 ─────────────────────────────────
|
|
1103
|
+
{
|
|
1104
|
+
type: "POST",
|
|
1105
|
+
path: "/x402/dao-analyze",
|
|
1106
|
+
handler: async (req, res, runtime) => {
|
|
1107
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
1108
|
+
amountUsd: "0.10",
|
|
1109
|
+
description: "Multi-agent DAO proposal analysis (4 agents, MixtureOfAgents)",
|
|
1110
|
+
});
|
|
1111
|
+
if (!gate.paid)
|
|
1112
|
+
return;
|
|
1113
|
+
const body = req.body ?? {};
|
|
1114
|
+
const proposal = requireString(body, "proposal");
|
|
1115
|
+
if (!proposal) {
|
|
1116
|
+
res.status(400).json({ error: "Missing required field: proposal (non-empty string)" });
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const daoName = typeof body.daoName === "string" ? body.daoName : "Unknown DAO";
|
|
1120
|
+
const swarmsService = getSwarmsService(runtime);
|
|
1121
|
+
if (!swarmsService) {
|
|
1122
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const result = await swarmsService.runSwarm({
|
|
1127
|
+
name: `dao-analyze-${Date.now()}`,
|
|
1128
|
+
description: `DAO proposal analysis: ${daoName}`,
|
|
1129
|
+
agents: [
|
|
1130
|
+
{
|
|
1131
|
+
agent_name: "EconomicAnalyst",
|
|
1132
|
+
system_prompt: "You are a DeFi economic analyst specializing in DAO treasury and protocol economics. " +
|
|
1133
|
+
"Analyze the financial impact of the proposed change: " +
|
|
1134
|
+
"treasury impact, revenue/cost projections, token price implications, " +
|
|
1135
|
+
"liquidity effects, incentive alignment, opportunity cost, and comparison to alternatives. " +
|
|
1136
|
+
"Quantify where possible. " +
|
|
1137
|
+
"Output a JSON object: { \"analysis\": \"<detailed economic analysis>\", " +
|
|
1138
|
+
"\"impact\": \"positive|negative|neutral\", \"confidence\": <0-100> }",
|
|
1139
|
+
model_name: "gpt-4o",
|
|
1140
|
+
role: "worker",
|
|
1141
|
+
max_loops: 1,
|
|
1142
|
+
max_tokens: 4096,
|
|
1143
|
+
temperature: 0.4,
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
agent_name: "TechnicalReviewer",
|
|
1147
|
+
system_prompt: "You are a blockchain technical architect reviewing DAO proposals. " +
|
|
1148
|
+
"Evaluate implementation feasibility: " +
|
|
1149
|
+
"smart contract changes required, upgrade complexity, integration risks, " +
|
|
1150
|
+
"dependencies on external protocols, timeline realism, " +
|
|
1151
|
+
"testing requirements, and potential for unintended side effects. " +
|
|
1152
|
+
"Output a JSON object: { \"analysis\": \"<detailed technical review>\", " +
|
|
1153
|
+
"\"feasibility\": \"straightforward|moderate|complex|infeasible\", \"confidence\": <0-100> }",
|
|
1154
|
+
model_name: "gpt-4o",
|
|
1155
|
+
role: "worker",
|
|
1156
|
+
max_loops: 1,
|
|
1157
|
+
max_tokens: 4096,
|
|
1158
|
+
temperature: 0.3,
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
agent_name: "RiskAssessor",
|
|
1162
|
+
system_prompt: "You are a crypto risk management specialist analyzing DAO proposals. " +
|
|
1163
|
+
"Identify what could go wrong: " +
|
|
1164
|
+
"smart contract risk, governance attack vectors, regulatory exposure, " +
|
|
1165
|
+
"centralization risks, key person dependencies, market timing risks, " +
|
|
1166
|
+
"community backlash potential, and worst-case scenarios. " +
|
|
1167
|
+
"Rate overall risk as Low/Medium/High/Critical. " +
|
|
1168
|
+
"Output a JSON object: { \"analysis\": \"<detailed risk assessment>\", " +
|
|
1169
|
+
"\"riskLevel\": \"low|medium|high|critical\", \"topRisks\": [\"...\"] }",
|
|
1170
|
+
model_name: "gpt-4o",
|
|
1171
|
+
role: "worker",
|
|
1172
|
+
max_loops: 1,
|
|
1173
|
+
max_tokens: 4096,
|
|
1174
|
+
temperature: 0.3,
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
agent_name: "VoteSummarizer",
|
|
1178
|
+
system_prompt: "You are a DAO governance advisor. You receive analyses from EconomicAnalyst, TechnicalReviewer, and RiskAssessor. " +
|
|
1179
|
+
"Synthesize their findings into a clear voting recommendation. " +
|
|
1180
|
+
"Output ONLY a JSON object with this exact structure: " +
|
|
1181
|
+
"{ \"recommendation\": \"FOR\" | \"AGAINST\" | \"ABSTAIN\", " +
|
|
1182
|
+
"\"confidence\": <number 0-100>, " +
|
|
1183
|
+
"\"analysis\": { " +
|
|
1184
|
+
"\"economic\": \"<summary of economic impact>\", " +
|
|
1185
|
+
"\"technical\": \"<summary of technical feasibility>\", " +
|
|
1186
|
+
"\"risk\": \"<summary of key risks>\" " +
|
|
1187
|
+
"}, " +
|
|
1188
|
+
"\"summary\": \"<2-3 sentence executive summary with clear recommendation and reasoning>\" }",
|
|
1189
|
+
model_name: "gpt-4o",
|
|
1190
|
+
role: "worker",
|
|
1191
|
+
max_loops: 1,
|
|
1192
|
+
max_tokens: 8192,
|
|
1193
|
+
temperature: 0.3,
|
|
1194
|
+
},
|
|
1195
|
+
],
|
|
1196
|
+
swarm_type: "MixtureOfAgents",
|
|
1197
|
+
task: `Analyze the following DAO proposal for ${daoName}.\n\n` +
|
|
1198
|
+
`Proposal:\n${proposal}\n\n` +
|
|
1199
|
+
"Each agent should analyze from their specialization, then the VoteSummarizer should synthesize all findings into a voting recommendation.",
|
|
1200
|
+
max_loops: 1,
|
|
1201
|
+
});
|
|
1202
|
+
const rawOutput = extractSwarmOutput(result);
|
|
1203
|
+
const dao = parseDaoAnalyzeText(rawOutput);
|
|
1204
|
+
const truncated = truncateDaoAnalyzeForFreeTier(dao, gate);
|
|
1205
|
+
res.json({
|
|
1206
|
+
...truncated,
|
|
1207
|
+
rawOutput: gate.amountUsd > 0 ? rawOutput : undefined,
|
|
1208
|
+
daoName,
|
|
1209
|
+
template: "DAOAnalysis",
|
|
1210
|
+
freeRemaining: gate.freeRemaining,
|
|
1211
|
+
payment: {
|
|
1212
|
+
amount: "0.10",
|
|
1213
|
+
transaction: gate.transaction,
|
|
1214
|
+
network: gate.network,
|
|
1215
|
+
},
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/dao-analyze] Swarm execution failed");
|
|
1220
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
];
|
|
1225
|
+
//# sourceMappingURL=cryptoRoutes.js.map
|