@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,869 @@
|
|
|
1
|
+
import { x402Gate } from "../server/x402Gate.js";
|
|
2
|
+
import { callOpenAI } from "../utils/llm.js";
|
|
3
|
+
import { TTLCache } from "../utils/cache.js";
|
|
4
|
+
import { saveReport } from "../utils/reportStore.js";
|
|
5
|
+
// ── Disclaimers ─────────────────────────────────────────────────────────
|
|
6
|
+
const YIELD_DISCLAIMER = "This is not financial advice. DeFi carries inherent risks including smart contract vulnerabilities, impermanent loss, and protocol failures. Always do your own research.";
|
|
7
|
+
const COMPLIANCE_DISCLAIMER = "This analysis is for informational purposes only and does not constitute legal advice. Consult a qualified legal professional for compliance decisions.";
|
|
8
|
+
const DD_DISCLAIMER = "Not financial advice. Do your own research.";
|
|
9
|
+
// ── Input validation helpers ────────────────────────────────────────────
|
|
10
|
+
function requireString(body, field, maxLen = 100_000) {
|
|
11
|
+
const val = body[field];
|
|
12
|
+
if (!val || typeof val !== "string" || val.trim().length === 0)
|
|
13
|
+
return null;
|
|
14
|
+
return val.slice(0, maxLen);
|
|
15
|
+
}
|
|
16
|
+
// ── Helper: get SwarmsService or null ───────────────────────────────────
|
|
17
|
+
function getSwarmsService(runtime) {
|
|
18
|
+
const svc = runtime.getService("SWARMS");
|
|
19
|
+
return svc?.isAvailable() ? svc : null;
|
|
20
|
+
}
|
|
21
|
+
// ── JSON parse helper ───────────────────────────────────────────────────
|
|
22
|
+
function tryParseJson(raw) {
|
|
23
|
+
try {
|
|
24
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
25
|
+
if (match) {
|
|
26
|
+
return JSON.parse(match[0]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// not valid JSON
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// ── Extract raw text from swarm response ────────────────────────────────
|
|
35
|
+
function extractSwarmOutput(result) {
|
|
36
|
+
const output = result.output;
|
|
37
|
+
if (typeof output === "string")
|
|
38
|
+
return output;
|
|
39
|
+
if (Array.isArray(output)) {
|
|
40
|
+
return output
|
|
41
|
+
.map((item) => {
|
|
42
|
+
if (typeof item === "string")
|
|
43
|
+
return item;
|
|
44
|
+
if (item && typeof item === "object") {
|
|
45
|
+
const obj = item;
|
|
46
|
+
const role = obj.role ?? obj.agent_name ?? "agent";
|
|
47
|
+
const content = obj.content ?? obj.text ?? obj.output ?? "";
|
|
48
|
+
return `[${role}]\n${content}`;
|
|
49
|
+
}
|
|
50
|
+
return String(item);
|
|
51
|
+
})
|
|
52
|
+
.join("\n\n");
|
|
53
|
+
}
|
|
54
|
+
if (output && typeof output === "object") {
|
|
55
|
+
const nested = output;
|
|
56
|
+
if (typeof nested.output === "string")
|
|
57
|
+
return nested.output;
|
|
58
|
+
if (typeof nested.content === "string")
|
|
59
|
+
return nested.content;
|
|
60
|
+
return JSON.stringify(output);
|
|
61
|
+
}
|
|
62
|
+
if (typeof result === "string")
|
|
63
|
+
return result;
|
|
64
|
+
return JSON.stringify(result);
|
|
65
|
+
}
|
|
66
|
+
// ── Report URL helpers ──────────────────────────────────────────────────
|
|
67
|
+
function reportUrls(id) {
|
|
68
|
+
const base = process.env.RAILWAY_PUBLIC_DOMAIN
|
|
69
|
+
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
|
|
70
|
+
: process.env.SWARMX_BASE_URL ?? "https://api.swarmx.io";
|
|
71
|
+
return {
|
|
72
|
+
reportUrl: `${base}/report/${id}`,
|
|
73
|
+
badgeUrl: `${base}/badge/${id}`,
|
|
74
|
+
badgeMarkdown: `[](${base}/report/${id})`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// ── Free tier placeholder ───────────────────────────────────────────────
|
|
78
|
+
const FREE_TIER_PLACEHOLDER = "[Connect wallet to see full details]";
|
|
79
|
+
// ── Caches ──────────────────────────────────────────────────────────────
|
|
80
|
+
const yieldCache = new TTLCache(5 * 60 * 1000); // 5 min
|
|
81
|
+
const researchCache = new TTLCache(60 * 60 * 1000); // 1 hour
|
|
82
|
+
const ddCache = new TTLCache(30 * 60 * 1000); // 30 min
|
|
83
|
+
async function fetchYields(chains) {
|
|
84
|
+
const cacheKey = `yields:${chains.sort().join(",")}`;
|
|
85
|
+
const cached = yieldCache.get(cacheKey);
|
|
86
|
+
if (cached)
|
|
87
|
+
return cached;
|
|
88
|
+
const response = await fetch("https://yields.llama.fi/pools");
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
// Try stale cache (any chain combo)
|
|
91
|
+
const stale = yieldCache.get("yields:stale");
|
|
92
|
+
if (stale)
|
|
93
|
+
return stale;
|
|
94
|
+
throw new Error("Yield data temporarily unavailable");
|
|
95
|
+
}
|
|
96
|
+
const data = (await response.json());
|
|
97
|
+
const chainSet = new Set(chains.map((c) => c.toLowerCase()));
|
|
98
|
+
const filtered = (data.data ?? [])
|
|
99
|
+
.filter((p) => p.tvlUsd > 1_000_000 &&
|
|
100
|
+
chainSet.has(p.chain?.toLowerCase() ?? ""))
|
|
101
|
+
.sort((a, b) => (b.apy ?? 0) - (a.apy ?? 0))
|
|
102
|
+
.slice(0, 50);
|
|
103
|
+
yieldCache.set(cacheKey, filtered);
|
|
104
|
+
yieldCache.set("yields:stale", filtered); // stale fallback
|
|
105
|
+
return filtered;
|
|
106
|
+
}
|
|
107
|
+
// ── Topic hash for research cache ───────────────────────────────────────
|
|
108
|
+
function topicHash(topic, depth) {
|
|
109
|
+
let hash = 0;
|
|
110
|
+
const key = `${topic.toLowerCase().trim()}:${depth}`;
|
|
111
|
+
for (let i = 0; i < key.length; i++) {
|
|
112
|
+
hash = ((hash << 5) - hash + key.charCodeAt(i)) | 0;
|
|
113
|
+
}
|
|
114
|
+
return `research:${hash}`;
|
|
115
|
+
}
|
|
116
|
+
// ── Catalog entries ─────────────────────────────────────────────────────
|
|
117
|
+
export const ADVANCED_CATALOG = [
|
|
118
|
+
{
|
|
119
|
+
name: "DeFi Yield Optimizer",
|
|
120
|
+
description: "3-agent yield optimization — scans DeFiLlama yields, evaluates protocol risk, recommends allocation strategy (MixtureOfAgents, 3 agents)",
|
|
121
|
+
path: "/x402/yield-optimizer",
|
|
122
|
+
method: "POST",
|
|
123
|
+
priceUsd: "0.10",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "Fact-Checked Research Report",
|
|
127
|
+
description: "4-agent research pipeline with fact-checking — researcher, fact-checker (VERIFIED/UNVERIFIED/DISPUTED/OUTDATED/FABRICATED), analyst, writer (SequentialWorkflow, 4 agents)",
|
|
128
|
+
path: "/x402/research-report",
|
|
129
|
+
method: "POST",
|
|
130
|
+
priceUsd: "0.50",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "Compliance Check",
|
|
134
|
+
description: "3-agent compliance analysis — auto-detects or targets GDPR/SOC2/HIPAA/MiCA/AML/PCI-DSS/CCPA, gap analysis, remediation roadmap (SequentialWorkflow, 3 agents)",
|
|
135
|
+
path: "/x402/compliance-check",
|
|
136
|
+
method: "POST",
|
|
137
|
+
priceUsd: "0.50",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "Investment Due Diligence",
|
|
141
|
+
description: "5-agent concurrent analysis + synthesis — team, tokenomics, tech, community, market scoring with cross-check penalties and STRONG_BUY/BUY/HOLD/AVOID/STRONG_AVOID recommendation (ConcurrentWorkflow + synthesis, 5+1 agents)",
|
|
142
|
+
path: "/x402/investment-dd",
|
|
143
|
+
method: "POST",
|
|
144
|
+
priceUsd: "5.00",
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
// ── Routes ──────────────────────────────────────────────────────────────
|
|
148
|
+
export const advancedRoutes = [
|
|
149
|
+
// ── POST /x402/yield-optimizer — $0.10 ────────────────────────────────
|
|
150
|
+
{
|
|
151
|
+
type: "POST",
|
|
152
|
+
path: "/x402/yield-optimizer",
|
|
153
|
+
handler: async (req, res, runtime) => {
|
|
154
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
155
|
+
amountUsd: "0.10",
|
|
156
|
+
description: "DeFi yield optimizer (3 agents, MixtureOfAgents)",
|
|
157
|
+
});
|
|
158
|
+
if (!gate.paid)
|
|
159
|
+
return;
|
|
160
|
+
const body = req.body ?? {};
|
|
161
|
+
const amount = typeof body.amount === "number" ? body.amount : undefined;
|
|
162
|
+
const riskTolerance = typeof body.riskTolerance === "string" &&
|
|
163
|
+
["low", "medium", "high"].includes(body.riskTolerance)
|
|
164
|
+
? body.riskTolerance
|
|
165
|
+
: "medium";
|
|
166
|
+
const chains = Array.isArray(body.chains) && body.chains.every((c) => typeof c === "string")
|
|
167
|
+
? body.chains
|
|
168
|
+
: ["ethereum", "solana", "arbitrum", "base"];
|
|
169
|
+
// Pre-fetch yields from DeFiLlama
|
|
170
|
+
let yields;
|
|
171
|
+
try {
|
|
172
|
+
yields = await fetchYields(chains);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
res.status(503).json({ error: "Yield data temporarily unavailable" });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const swarmsService = getSwarmsService(runtime);
|
|
179
|
+
if (!swarmsService) {
|
|
180
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const yieldSummary = yields
|
|
184
|
+
.slice(0, 20)
|
|
185
|
+
.map((p) => `${p.project} (${p.chain}) — ${p.symbol}: APY ${p.apy?.toFixed(2)}%, TVL $${(p.tvlUsd / 1e6).toFixed(1)}M`)
|
|
186
|
+
.join("\n");
|
|
187
|
+
try {
|
|
188
|
+
const result = await swarmsService.runSwarm({
|
|
189
|
+
name: `yield-optimizer-${Date.now()}`,
|
|
190
|
+
description: `Yield optimization for ${chains.join(", ")}`,
|
|
191
|
+
agents: [
|
|
192
|
+
{
|
|
193
|
+
agent_name: "RateScanner",
|
|
194
|
+
system_prompt: "You are a DeFi yield rate scanner. Analyze the provided yield data and identify the top 10 opportunities. " +
|
|
195
|
+
"Consider APY, TVL (higher = safer), protocol reputation, and yield sustainability. " +
|
|
196
|
+
"Output a JSON array of the top 10: [{ \"protocol\": \"...\", \"chain\": \"...\", \"symbol\": \"...\", \"apy\": ..., \"tvl\": ..., \"riskLevel\": \"low\"|\"medium\"|\"high\", \"notes\": \"...\" }]",
|
|
197
|
+
model_name: "gpt-4o-mini",
|
|
198
|
+
role: "worker",
|
|
199
|
+
max_loops: 1,
|
|
200
|
+
max_tokens: 4096,
|
|
201
|
+
temperature: 0.2,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
agent_name: "RiskAssessor",
|
|
205
|
+
system_prompt: "You are a DeFi risk assessment specialist. Evaluate the protocol safety of each yield opportunity. " +
|
|
206
|
+
"Consider: smart contract audit status, time in production, TVL trends, protocol team, past incidents, " +
|
|
207
|
+
"complexity of strategy, impermanent loss exposure, and depeg risk for stablecoins. " +
|
|
208
|
+
"Output a JSON array: [{ \"protocol\": \"...\", \"riskScore\": <1-10>, \"auditStatus\": \"...\", \"concerns\": [\"...\"], \"safeForRisk\": \"low\"|\"medium\"|\"high\" }]",
|
|
209
|
+
model_name: "gpt-4o",
|
|
210
|
+
role: "worker",
|
|
211
|
+
max_loops: 1,
|
|
212
|
+
max_tokens: 4096,
|
|
213
|
+
temperature: 0.25,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
agent_name: "StrategyAdvisor",
|
|
217
|
+
system_prompt: "You are a DeFi portfolio strategist. Based on the rate scanner's top opportunities and the risk assessor's evaluations, " +
|
|
218
|
+
`recommend an allocation strategy for a ${riskTolerance} risk tolerance investor` +
|
|
219
|
+
(amount ? ` with $${amount} to deploy` : "") +
|
|
220
|
+
".\n\n" +
|
|
221
|
+
"Recommend 3-5 positions that sum to 100% allocation. " +
|
|
222
|
+
"Output a JSON object:\n" +
|
|
223
|
+
"{\n" +
|
|
224
|
+
' "strategy": "name of the strategy",\n' +
|
|
225
|
+
' "positions": [{ "protocol": "...", "chain": "...", "symbol": "...", "allocation": <percentage>, "expectedApy": ..., "rationale": "..." }],\n' +
|
|
226
|
+
' "expectedApy": <weighted average APY>,\n' +
|
|
227
|
+
' "riskAssessment": "overall risk narrative",\n' +
|
|
228
|
+
' "executionSteps": ["step 1", "step 2", ...]\n' +
|
|
229
|
+
"}",
|
|
230
|
+
model_name: "gpt-4o",
|
|
231
|
+
role: "worker",
|
|
232
|
+
max_loops: 1,
|
|
233
|
+
max_tokens: 8192,
|
|
234
|
+
temperature: 0.35,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
swarm_type: "MixtureOfAgents",
|
|
238
|
+
task: `Optimize DeFi yield allocation for a ${riskTolerance} risk tolerance investor` +
|
|
239
|
+
(amount ? ` with $${amount}` : "") +
|
|
240
|
+
` across chains: ${chains.join(", ")}.\n\n` +
|
|
241
|
+
`Current top yields:\n${yieldSummary}`,
|
|
242
|
+
max_loops: 1,
|
|
243
|
+
});
|
|
244
|
+
const rawOutput = extractSwarmOutput(result);
|
|
245
|
+
const parsed = tryParseJson(rawOutput);
|
|
246
|
+
const strategy = parsed?.strategy ?? "Balanced DeFi Yield";
|
|
247
|
+
const positions = Array.isArray(parsed?.positions) ? parsed.positions : [];
|
|
248
|
+
const expectedApy = typeof parsed?.expectedApy === "number" ? parsed.expectedApy : 0;
|
|
249
|
+
const riskAssessment = parsed?.riskAssessment ?? rawOutput.slice(0, 1000);
|
|
250
|
+
const executionSteps = Array.isArray(parsed?.executionSteps) ? parsed.executionSteps : [];
|
|
251
|
+
// Free tier: show strategy name + expectedApy + disclaimer. Hide positions/riskAssessment.
|
|
252
|
+
let responseBody;
|
|
253
|
+
if (gate.amountUsd > 0) {
|
|
254
|
+
responseBody = {
|
|
255
|
+
strategy,
|
|
256
|
+
positions,
|
|
257
|
+
expectedApy,
|
|
258
|
+
riskAssessment,
|
|
259
|
+
executionSteps,
|
|
260
|
+
disclaimer: YIELD_DISCLAIMER,
|
|
261
|
+
rawOutput,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
responseBody = {
|
|
266
|
+
strategy,
|
|
267
|
+
expectedApy,
|
|
268
|
+
disclaimer: YIELD_DISCLAIMER,
|
|
269
|
+
_preview: true,
|
|
270
|
+
_message: `Strategy: ${strategy}. Expected APY: ${expectedApy}%. Pay $0.10 to see positions and risk assessment.`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
res.json({
|
|
274
|
+
...responseBody,
|
|
275
|
+
template: "YieldOptimizer",
|
|
276
|
+
freeRemaining: gate.freeRemaining,
|
|
277
|
+
payment: {
|
|
278
|
+
amount: "0.10",
|
|
279
|
+
transaction: gate.transaction,
|
|
280
|
+
network: gate.network,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/yield-optimizer] Swarm execution failed");
|
|
286
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
// ── POST /x402/research-report — $0.50 ───────────────────────────────
|
|
291
|
+
{
|
|
292
|
+
type: "POST",
|
|
293
|
+
path: "/x402/research-report",
|
|
294
|
+
handler: async (req, res, runtime) => {
|
|
295
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
296
|
+
amountUsd: "0.50",
|
|
297
|
+
description: "Fact-checked research report (4 agents, SequentialWorkflow)",
|
|
298
|
+
});
|
|
299
|
+
if (!gate.paid)
|
|
300
|
+
return;
|
|
301
|
+
const body = req.body ?? {};
|
|
302
|
+
const topic = requireString(body, "topic", 500);
|
|
303
|
+
if (!topic) {
|
|
304
|
+
res.status(400).json({ error: "Missing required field: topic (non-empty string, max 500 chars)" });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const depth = typeof body.depth === "string" &&
|
|
308
|
+
["brief", "standard", "deep"].includes(body.depth)
|
|
309
|
+
? body.depth
|
|
310
|
+
: "standard";
|
|
311
|
+
const focus = typeof body.focus === "string" ? body.focus : undefined;
|
|
312
|
+
// Check cache
|
|
313
|
+
const cacheKey = topicHash(topic, depth);
|
|
314
|
+
const cached = researchCache.get(cacheKey);
|
|
315
|
+
if (cached) {
|
|
316
|
+
res.json(cached);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const swarmsService = getSwarmsService(runtime);
|
|
320
|
+
if (!swarmsService) {
|
|
321
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const result = await swarmsService.runSwarm({
|
|
326
|
+
name: `research-report-${Date.now()}`,
|
|
327
|
+
description: `Research report: ${topic}`,
|
|
328
|
+
agents: [
|
|
329
|
+
{
|
|
330
|
+
agent_name: "Researcher",
|
|
331
|
+
system_prompt: "You are a thorough researcher. Gather comprehensive information about the topic. " +
|
|
332
|
+
"Tag each claim with a confidence indicator: [HIGH], [MEDIUM], or [LOW]. " +
|
|
333
|
+
"Be exhaustive in your coverage. Structure your findings clearly with headings. " +
|
|
334
|
+
"Include data points, statistics, and source references where possible.",
|
|
335
|
+
model_name: "gpt-4o",
|
|
336
|
+
role: "worker",
|
|
337
|
+
max_loops: 1,
|
|
338
|
+
max_tokens: 8192,
|
|
339
|
+
temperature: 0.6,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
agent_name: "FactChecker",
|
|
343
|
+
system_prompt: "You are a meticulous fact-checker. Cross-reference every significant claim from the researcher. " +
|
|
344
|
+
"Mark each claim as: VERIFIED, UNVERIFIED, DISPUTED, OUTDATED, or FABRICATED. " +
|
|
345
|
+
"If unsure, mark UNVERIFIED not DISPUTED. Do NOT fabricate corrections. " +
|
|
346
|
+
"For each claim, provide your reasoning for the status. " +
|
|
347
|
+
"Output structured findings with the original claim, your verdict, and brief justification.",
|
|
348
|
+
model_name: "gpt-4o",
|
|
349
|
+
role: "worker",
|
|
350
|
+
max_loops: 1,
|
|
351
|
+
max_tokens: 6144,
|
|
352
|
+
temperature: 0.15,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
agent_name: "Analyst",
|
|
356
|
+
system_prompt: "You are an expert analyst. Synthesize the verified research into actionable insights. " +
|
|
357
|
+
"Downweight claims marked UNVERIFIED or DISPUTED. Highlight VERIFIED claims prominently. " +
|
|
358
|
+
"Flag any FABRICATED claims as warnings. " +
|
|
359
|
+
"Identify patterns, trends, implications, and areas that need further investigation.",
|
|
360
|
+
model_name: "gpt-4o",
|
|
361
|
+
role: "worker",
|
|
362
|
+
max_loops: 1,
|
|
363
|
+
max_tokens: 6144,
|
|
364
|
+
temperature: 0.4,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
agent_name: "Writer",
|
|
368
|
+
system_prompt: "You are a skilled report writer. Produce a polished final report with these sections:\n" +
|
|
369
|
+
"1. Executive Summary (2-3 paragraphs)\n" +
|
|
370
|
+
"2. Background\n" +
|
|
371
|
+
"3. Key Findings (with [VERIFIED]/[UNVERIFIED]/[DISPUTED] confidence indicators)\n" +
|
|
372
|
+
"4. Analysis\n" +
|
|
373
|
+
"5. Risks\n" +
|
|
374
|
+
"6. Recommendations\n\n" +
|
|
375
|
+
"Write in a professional, concise style. Use the analyst's insights as the primary input. " +
|
|
376
|
+
"Preserve confidence indicators on key claims.",
|
|
377
|
+
model_name: "gpt-4o",
|
|
378
|
+
role: "worker",
|
|
379
|
+
max_loops: 1,
|
|
380
|
+
max_tokens: 16384,
|
|
381
|
+
temperature: 0.45,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
swarm_type: "SequentialWorkflow",
|
|
385
|
+
task: `Research the following topic (depth: ${depth})${focus ? `, focusing on: ${focus}` : ""}: ${topic}`,
|
|
386
|
+
max_loops: 1,
|
|
387
|
+
rules: "Each agent builds on the previous agent's output. The Researcher gathers raw information with confidence tags, the FactChecker verifies claims, the Analyst synthesizes insights, and the Writer produces the final report.",
|
|
388
|
+
});
|
|
389
|
+
const rawOutput = extractSwarmOutput(result);
|
|
390
|
+
// Extract executive summary (first section or first 300 chars)
|
|
391
|
+
const execMatch = rawOutput.match(/(?:executive\s+summary|summary)[:\s]*([\s\S]{20,2000}?)(?=\n#{1,3}\s|\n\d+\.\s|$)/i);
|
|
392
|
+
const executiveSummary = execMatch?.[1]?.trim() ?? rawOutput.slice(0, 500);
|
|
393
|
+
// Extract key findings
|
|
394
|
+
const findingsMatch = rawOutput.match(/(?:key\s+findings|findings)[:\s]*([\s\S]{20,3000}?)(?=\n#{1,3}\s|\n\d+\.\s(?:analysis|risk|recommendation)|$)/i);
|
|
395
|
+
const keyFindings = findingsMatch?.[1]?.trim() ?? "";
|
|
396
|
+
// Save report
|
|
397
|
+
const reportId = saveReport({
|
|
398
|
+
type: "research-report",
|
|
399
|
+
createdAt: new Date().toISOString(),
|
|
400
|
+
input: { code: topic },
|
|
401
|
+
result: { executiveSummary, keyFindings, fullReport: rawOutput },
|
|
402
|
+
riskScore: null,
|
|
403
|
+
paid: gate.amountUsd > 0,
|
|
404
|
+
});
|
|
405
|
+
const urls = reportUrls(reportId);
|
|
406
|
+
// Free tier: first 300 chars of executive summary + 2 key findings
|
|
407
|
+
let responseBody;
|
|
408
|
+
if (gate.amountUsd > 0) {
|
|
409
|
+
responseBody = {
|
|
410
|
+
executiveSummary,
|
|
411
|
+
keyFindings,
|
|
412
|
+
fullReport: rawOutput,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
// Extract first 2 findings
|
|
417
|
+
const findingLines = keyFindings
|
|
418
|
+
.split("\n")
|
|
419
|
+
.filter((l) => l.trim().length > 10)
|
|
420
|
+
.slice(0, 2);
|
|
421
|
+
responseBody = {
|
|
422
|
+
executiveSummary: executiveSummary.slice(0, 300) + (executiveSummary.length > 300 ? "..." : ""),
|
|
423
|
+
keyFindings: findingLines.join("\n"),
|
|
424
|
+
_preview: true,
|
|
425
|
+
_message: `Report preview. Pay $0.50 to see the full report.`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const fullResponse = {
|
|
429
|
+
...responseBody,
|
|
430
|
+
...urls,
|
|
431
|
+
topic,
|
|
432
|
+
depth,
|
|
433
|
+
template: "ResearchReport",
|
|
434
|
+
freeRemaining: gate.freeRemaining,
|
|
435
|
+
payment: {
|
|
436
|
+
amount: "0.50",
|
|
437
|
+
transaction: gate.transaction,
|
|
438
|
+
network: gate.network,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
// Cache the full response
|
|
442
|
+
researchCache.set(cacheKey, fullResponse);
|
|
443
|
+
res.json(fullResponse);
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/research-report] Swarm execution failed");
|
|
447
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
// ── POST /x402/compliance-check — $0.50 ──────────────────────────────
|
|
452
|
+
{
|
|
453
|
+
type: "POST",
|
|
454
|
+
path: "/x402/compliance-check",
|
|
455
|
+
handler: async (req, res, runtime) => {
|
|
456
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
457
|
+
amountUsd: "0.50",
|
|
458
|
+
description: "Compliance analysis (3 agents, SequentialWorkflow)",
|
|
459
|
+
});
|
|
460
|
+
if (!gate.paid)
|
|
461
|
+
return;
|
|
462
|
+
const body = req.body ?? {};
|
|
463
|
+
const document = requireString(body, "document", 100_000);
|
|
464
|
+
if (!document) {
|
|
465
|
+
res.status(400).json({ error: "Missing required field: document (non-empty string, max 100,000 chars)" });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const jurisdiction = typeof body.jurisdiction === "string" ? body.jurisdiction : undefined;
|
|
469
|
+
const industry = typeof body.industry === "string" ? body.industry : undefined;
|
|
470
|
+
const validFrameworks = ["GDPR", "SOC2", "HIPAA", "MiCA", "AML", "PCI-DSS", "CCPA"];
|
|
471
|
+
const framework = typeof body.framework === "string" && validFrameworks.includes(body.framework)
|
|
472
|
+
? body.framework
|
|
473
|
+
: undefined;
|
|
474
|
+
const swarmsService = getSwarmsService(runtime);
|
|
475
|
+
if (!swarmsService) {
|
|
476
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const frameworkInstruction = framework
|
|
480
|
+
? `Focus specifically on ${framework} compliance.`
|
|
481
|
+
: "Auto-detect the most relevant regulatory frameworks based on the document content, jurisdiction, and industry.";
|
|
482
|
+
try {
|
|
483
|
+
const result = await swarmsService.runSwarm({
|
|
484
|
+
name: `compliance-check-${Date.now()}`,
|
|
485
|
+
description: `Compliance check${framework ? ` (${framework})` : ""}`,
|
|
486
|
+
agents: [
|
|
487
|
+
{
|
|
488
|
+
agent_name: "RegulatoryExpert",
|
|
489
|
+
system_prompt: "You are a regulatory compliance expert. Identify all applicable regulatory frameworks and their specific requirements. " +
|
|
490
|
+
`${frameworkInstruction}\n` +
|
|
491
|
+
(jurisdiction ? `Jurisdiction: ${jurisdiction}. ` : "") +
|
|
492
|
+
(industry ? `Industry: ${industry}. ` : "") +
|
|
493
|
+
"For each framework, cite specific article/section references. " +
|
|
494
|
+
"ANTI-HALLUCINATION: If unsure about a specific article number, say 'approximately'. Note that regulatory info may be outdated. " +
|
|
495
|
+
"Output a structured list of applicable frameworks with their key requirements.",
|
|
496
|
+
model_name: "gpt-4o",
|
|
497
|
+
role: "worker",
|
|
498
|
+
max_loops: 1,
|
|
499
|
+
max_tokens: 6144,
|
|
500
|
+
temperature: 0.2,
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
agent_name: "GapAnalyzer",
|
|
504
|
+
system_prompt: "You are a compliance gap analyst. Compare the provided document against the regulatory requirements identified by the RegulatoryExpert. " +
|
|
505
|
+
"For each requirement, assign a status: compliant, partially-compliant, non-compliant, or not-applicable. " +
|
|
506
|
+
"Group findings by severity: critical (immediate legal risk), high (significant gaps), medium (improvement needed), low (minor issues). " +
|
|
507
|
+
"Output structured gap analysis with requirement, status, severity, and specific evidence from the document.",
|
|
508
|
+
model_name: "gpt-4o",
|
|
509
|
+
role: "worker",
|
|
510
|
+
max_loops: 1,
|
|
511
|
+
max_tokens: 8192,
|
|
512
|
+
temperature: 0.2,
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
agent_name: "ComplianceWriter",
|
|
516
|
+
system_prompt: "You are a compliance report writer. Synthesize the regulatory analysis and gap findings into a comprehensive report.\n\n" +
|
|
517
|
+
"Include:\n" +
|
|
518
|
+
"1. Compliance Scorecard — overall score (0-100) and per-framework scores\n" +
|
|
519
|
+
"2. Critical Findings — issues requiring immediate attention\n" +
|
|
520
|
+
"3. Gap Summary — counts by severity (critical, high, medium, low)\n" +
|
|
521
|
+
"4. Detailed Findings — each gap with status, evidence, and recommendation\n" +
|
|
522
|
+
"5. Remediation Roadmap — prioritized action items with estimated effort\n\n" +
|
|
523
|
+
"Output a JSON object:\n" +
|
|
524
|
+
"{\n" +
|
|
525
|
+
' "overallComplianceScore": <0-100>,\n' +
|
|
526
|
+
' "frameworks": [{ "name": "...", "score": <0-100>, "status": "..." }],\n' +
|
|
527
|
+
' "gaps": { "critical": <count>, "high": <count>, "medium": <count>, "low": <count> },\n' +
|
|
528
|
+
' "criticalFindings": ["..."],\n' +
|
|
529
|
+
' "remediationRoadmap": [{ "priority": <1-N>, "action": "...", "effort": "low|medium|high", "impact": "..." }],\n' +
|
|
530
|
+
' "report": "full text report"\n' +
|
|
531
|
+
"}",
|
|
532
|
+
model_name: "gpt-4o",
|
|
533
|
+
role: "worker",
|
|
534
|
+
max_loops: 1,
|
|
535
|
+
max_tokens: 16384,
|
|
536
|
+
temperature: 0.35,
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
swarm_type: "SequentialWorkflow",
|
|
540
|
+
task: `Analyze the following document for regulatory compliance${framework ? ` (${framework})` : ""}` +
|
|
541
|
+
(jurisdiction ? `, jurisdiction: ${jurisdiction}` : "") +
|
|
542
|
+
(industry ? `, industry: ${industry}` : "") +
|
|
543
|
+
`:\n\n${document}`,
|
|
544
|
+
max_loops: 1,
|
|
545
|
+
rules: "Each agent builds on the previous agent's output. The RegulatoryExpert identifies applicable frameworks, the GapAnalyzer finds compliance gaps, and the ComplianceWriter produces the final report.",
|
|
546
|
+
});
|
|
547
|
+
const rawOutput = extractSwarmOutput(result);
|
|
548
|
+
const parsed = tryParseJson(rawOutput);
|
|
549
|
+
const overallComplianceScore = typeof parsed?.overallComplianceScore === "number"
|
|
550
|
+
? parsed.overallComplianceScore
|
|
551
|
+
: 50;
|
|
552
|
+
const gaps = parsed?.gaps ?? { critical: 0, high: 0, medium: 0, low: 0 };
|
|
553
|
+
const criticalFindings = Array.isArray(parsed?.criticalFindings) ? parsed.criticalFindings : [];
|
|
554
|
+
const frameworks = Array.isArray(parsed?.frameworks) ? parsed.frameworks : [];
|
|
555
|
+
const remediationRoadmap = Array.isArray(parsed?.remediationRoadmap) ? parsed.remediationRoadmap : [];
|
|
556
|
+
const reportText = parsed?.report ?? rawOutput;
|
|
557
|
+
// Free tier: show overallComplianceScore + gap counts by severity + disclaimer
|
|
558
|
+
let responseBody;
|
|
559
|
+
if (gate.amountUsd > 0) {
|
|
560
|
+
responseBody = {
|
|
561
|
+
overallComplianceScore,
|
|
562
|
+
frameworks,
|
|
563
|
+
gaps,
|
|
564
|
+
criticalFindings,
|
|
565
|
+
remediationRoadmap,
|
|
566
|
+
report: reportText,
|
|
567
|
+
disclaimer: COMPLIANCE_DISCLAIMER,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
responseBody = {
|
|
572
|
+
overallComplianceScore,
|
|
573
|
+
gaps,
|
|
574
|
+
disclaimer: COMPLIANCE_DISCLAIMER,
|
|
575
|
+
_preview: true,
|
|
576
|
+
_message: `Compliance score: ${overallComplianceScore}/100. Gaps: ${gaps.critical ?? 0} critical, ${gaps.high ?? 0} high, ${gaps.medium ?? 0} medium, ${gaps.low ?? 0} low. Pay $0.50 to see full report.`,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
res.json({
|
|
580
|
+
...responseBody,
|
|
581
|
+
template: "ComplianceCheck",
|
|
582
|
+
freeRemaining: gate.freeRemaining,
|
|
583
|
+
payment: {
|
|
584
|
+
amount: "0.50",
|
|
585
|
+
transaction: gate.transaction,
|
|
586
|
+
network: gate.network,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/compliance-check] Swarm execution failed");
|
|
592
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
// ── POST /x402/investment-dd — $5.00 ─────────────────────────────────
|
|
597
|
+
{
|
|
598
|
+
type: "POST",
|
|
599
|
+
path: "/x402/investment-dd",
|
|
600
|
+
handler: async (req, res, runtime) => {
|
|
601
|
+
const gate = await x402Gate(runtime, req, res, {
|
|
602
|
+
amountUsd: "5.00",
|
|
603
|
+
description: "Investment due diligence (5+1 agents, ConcurrentWorkflow + synthesis)",
|
|
604
|
+
});
|
|
605
|
+
if (!gate.paid)
|
|
606
|
+
return;
|
|
607
|
+
const body = req.body ?? {};
|
|
608
|
+
const project = requireString(body, "project", 500);
|
|
609
|
+
if (!project) {
|
|
610
|
+
res.status(400).json({ error: "Missing required field: project (non-empty string, max 500 chars)" });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const projectType = typeof body.projectType === "string" &&
|
|
614
|
+
["token", "protocol", "dao", "nft"].includes(body.projectType)
|
|
615
|
+
? body.projectType
|
|
616
|
+
: "token";
|
|
617
|
+
const context = typeof body.context === "string" ? body.context.slice(0, 5000) : undefined;
|
|
618
|
+
// Check cache
|
|
619
|
+
const cacheKey = `dd:${project.toLowerCase().trim()}:${projectType}`;
|
|
620
|
+
const cached = ddCache.get(cacheKey);
|
|
621
|
+
if (cached) {
|
|
622
|
+
res.json(cached);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const swarmsService = getSwarmsService(runtime);
|
|
626
|
+
if (!swarmsService) {
|
|
627
|
+
res.status(503).json({ error: "Swarms service unavailable" });
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const openaiKey = String(runtime.getSetting("OPENAI_API_KEY") ?? "");
|
|
631
|
+
try {
|
|
632
|
+
// Phase 1: 5 concurrent specialist agents
|
|
633
|
+
const phase1Result = await swarmsService.runSwarm({
|
|
634
|
+
name: `investment-dd-${Date.now()}`,
|
|
635
|
+
description: `Investment DD: ${project} (${projectType})`,
|
|
636
|
+
agents: [
|
|
637
|
+
{
|
|
638
|
+
agent_name: "TeamAnalyst",
|
|
639
|
+
system_prompt: `You are a team due diligence analyst evaluating the ${projectType} project "${project}". ` +
|
|
640
|
+
"Analyze: team background, doxxed status, track record, previous projects, " +
|
|
641
|
+
"LinkedIn/Twitter presence, advisory board quality, team size, relevant experience. " +
|
|
642
|
+
"Output a JSON object: { \"score\": <0-100>, \"summary\": \"...\", \"doxxed\": true|false, " +
|
|
643
|
+
"\"trackRecord\": \"...\", \"concerns\": [\"...\"], \"strengths\": [\"...\"] }",
|
|
644
|
+
model_name: "gpt-4o",
|
|
645
|
+
role: "worker",
|
|
646
|
+
max_loops: 1,
|
|
647
|
+
max_tokens: 4096,
|
|
648
|
+
temperature: 0.3,
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
agent_name: "TokenomicsExpert",
|
|
652
|
+
system_prompt: `You are a tokenomics expert evaluating "${project}" (${projectType}). ` +
|
|
653
|
+
"Analyze: token distribution, vesting schedules, emission rates, utility, " +
|
|
654
|
+
"insider allocation percentage, unlock schedules, supply mechanics (inflationary/deflationary), " +
|
|
655
|
+
"staking incentives, treasury management. " +
|
|
656
|
+
"Output a JSON object: { \"score\": <0-100>, \"summary\": \"...\", " +
|
|
657
|
+
"\"insiderAllocation\": <percentage>, \"vestingDetails\": \"...\", " +
|
|
658
|
+
"\"concerns\": [\"...\"], \"strengths\": [\"...\"] }",
|
|
659
|
+
model_name: "gpt-4o",
|
|
660
|
+
role: "worker",
|
|
661
|
+
max_loops: 1,
|
|
662
|
+
max_tokens: 4096,
|
|
663
|
+
temperature: 0.25,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
agent_name: "TechReviewer",
|
|
667
|
+
system_prompt: `You are a technical reviewer evaluating "${project}" (${projectType}). ` +
|
|
668
|
+
"Analyze: architecture design, code quality (if open source), audit history, " +
|
|
669
|
+
"technology stack, scalability approach, security practices, open source status, " +
|
|
670
|
+
"GitHub activity, test coverage, documentation quality. " +
|
|
671
|
+
"Output a JSON object: { \"score\": <0-100>, \"summary\": \"...\", " +
|
|
672
|
+
"\"openSource\": true|false, \"auditHistory\": [\"...\"], " +
|
|
673
|
+
"\"concerns\": [\"...\"], \"strengths\": [\"...\"] }",
|
|
674
|
+
model_name: "gpt-4o",
|
|
675
|
+
role: "worker",
|
|
676
|
+
max_loops: 1,
|
|
677
|
+
max_tokens: 4096,
|
|
678
|
+
temperature: 0.25,
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
agent_name: "CommunityScanner",
|
|
682
|
+
system_prompt: `You are a community analyst evaluating "${project}" (${projectType}). ` +
|
|
683
|
+
"Analyze: social media presence (Twitter, Discord, Telegram), engagement quality, " +
|
|
684
|
+
"community sentiment, bot activity detection, growth trends, developer community, " +
|
|
685
|
+
"governance participation, content quality vs hype ratio. " +
|
|
686
|
+
"Output a JSON object: { \"score\": <0-100>, \"summary\": \"...\", " +
|
|
687
|
+
"\"concerns\": [\"...\"], \"strengths\": [\"...\"] }",
|
|
688
|
+
model_name: "gpt-4o-mini",
|
|
689
|
+
role: "worker",
|
|
690
|
+
max_loops: 1,
|
|
691
|
+
max_tokens: 4096,
|
|
692
|
+
temperature: 0.35,
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
agent_name: "MarketAnalyst",
|
|
696
|
+
system_prompt: `You are a market analyst evaluating "${project}" (${projectType}). ` +
|
|
697
|
+
"Analyze: total addressable market, competitive landscape, market timing, " +
|
|
698
|
+
"comparable projects and their performance, market saturation, " +
|
|
699
|
+
"regulatory environment, macro trends affecting the sector. " +
|
|
700
|
+
"Output a JSON object: { \"score\": <0-100>, \"summary\": \"...\", " +
|
|
701
|
+
"\"competitors\": [\"...\"], \"concerns\": [\"...\"], \"strengths\": [\"...\"] }",
|
|
702
|
+
model_name: "gpt-4o-mini",
|
|
703
|
+
role: "worker",
|
|
704
|
+
max_loops: 1,
|
|
705
|
+
max_tokens: 4096,
|
|
706
|
+
temperature: 0.3,
|
|
707
|
+
},
|
|
708
|
+
],
|
|
709
|
+
swarm_type: "ConcurrentWorkflow",
|
|
710
|
+
task: `Perform investment due diligence on ${projectType} project: "${project}".` +
|
|
711
|
+
(context ? `\n\nAdditional context: ${context}` : ""),
|
|
712
|
+
max_loops: 1,
|
|
713
|
+
});
|
|
714
|
+
const phase1Output = extractSwarmOutput(phase1Result);
|
|
715
|
+
// Phase 2: Synthesis via callOpenAI
|
|
716
|
+
let synthesisOutput;
|
|
717
|
+
if (openaiKey) {
|
|
718
|
+
synthesisOutput = await callOpenAI({
|
|
719
|
+
apiKey: openaiKey,
|
|
720
|
+
model: "gpt-4o",
|
|
721
|
+
systemPrompt: "You are an investment due diligence synthesizer. You receive outputs from 5 specialist analysts " +
|
|
722
|
+
"(Team, Tokenomics, Tech, Community, Market). Synthesize into a final DD report.\n\n" +
|
|
723
|
+
"SCORING WEIGHTS: team 25%, tokenomics 20%, tech 25%, community 15%, market 15%.\n\n" +
|
|
724
|
+
"CROSS-CHECK PENALTIES:\n" +
|
|
725
|
+
"- Anonymous team + closed source code = -15 points from overall score\n" +
|
|
726
|
+
"- >40% insider allocation + poor community engagement = -20 points from overall score\n\n" +
|
|
727
|
+
"RECOMMENDATION SCALE:\n" +
|
|
728
|
+
"- STRONG_BUY: 80-100\n" +
|
|
729
|
+
"- BUY: 65-79\n" +
|
|
730
|
+
"- HOLD: 50-64\n" +
|
|
731
|
+
"- AVOID: 35-49\n" +
|
|
732
|
+
"- STRONG_AVOID: 0-34\n\n" +
|
|
733
|
+
"Output a JSON object:\n" +
|
|
734
|
+
"{\n" +
|
|
735
|
+
` "project": "${project}",\n` +
|
|
736
|
+
` "projectType": "${projectType}",\n` +
|
|
737
|
+
' "overallScore": <0-100>,\n' +
|
|
738
|
+
' "recommendation": "STRONG_BUY"|"BUY"|"HOLD"|"AVOID"|"STRONG_AVOID",\n' +
|
|
739
|
+
' "dimensions": {\n' +
|
|
740
|
+
' "team": { "score": <0-100>, "weight": 25, "summary": "..." },\n' +
|
|
741
|
+
' "tokenomics": { "score": <0-100>, "weight": 20, "summary": "..." },\n' +
|
|
742
|
+
' "tech": { "score": <0-100>, "weight": 25, "summary": "..." },\n' +
|
|
743
|
+
' "community": { "score": <0-100>, "weight": 15, "summary": "..." },\n' +
|
|
744
|
+
' "market": { "score": <0-100>, "weight": 15, "summary": "..." }\n' +
|
|
745
|
+
" },\n" +
|
|
746
|
+
' "keyFindings": ["..."],\n' +
|
|
747
|
+
' "redFlags": ["..."],\n' +
|
|
748
|
+
' "bullCase": "...",\n' +
|
|
749
|
+
' "bearCase": "...",\n' +
|
|
750
|
+
' "executiveSummary": "..."\n' +
|
|
751
|
+
"}",
|
|
752
|
+
userPrompt: `Synthesize the following 5 specialist analyses into a final investment DD report for "${project}" (${projectType}):\n\n${phase1Output}`,
|
|
753
|
+
maxTokens: 16384,
|
|
754
|
+
temperature: 0.3,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
// Fallback: use swarms for synthesis too
|
|
759
|
+
const synthResult = await swarmsService.runAgent({
|
|
760
|
+
agent_name: "DDSynthesizer",
|
|
761
|
+
model_name: "gpt-4o",
|
|
762
|
+
system_prompt: "Synthesize the specialist analyses into a final DD report with overallScore, recommendation, dimensions, keyFindings, redFlags, bullCase, bearCase, executiveSummary. Output JSON.",
|
|
763
|
+
max_loops: 1,
|
|
764
|
+
max_tokens: 16384,
|
|
765
|
+
temperature: 0.3,
|
|
766
|
+
role: "worker",
|
|
767
|
+
}, `Synthesize the following 5 specialist analyses into a final investment DD report for "${project}" (${projectType}):\n\n${phase1Output}`);
|
|
768
|
+
synthesisOutput = String(synthResult.outputs ?? synthResult);
|
|
769
|
+
}
|
|
770
|
+
const parsed = tryParseJson(synthesisOutput);
|
|
771
|
+
const overallScore = typeof parsed?.overallScore === "number" ? parsed.overallScore : 50;
|
|
772
|
+
const recommendation = parsed?.recommendation ?? scoreToRecommendation(overallScore);
|
|
773
|
+
const dimensions = parsed?.dimensions ?? {};
|
|
774
|
+
const keyFindings = Array.isArray(parsed?.keyFindings) ? parsed.keyFindings : [];
|
|
775
|
+
const redFlags = Array.isArray(parsed?.redFlags) ? parsed.redFlags : [];
|
|
776
|
+
const bullCase = parsed?.bullCase ?? "";
|
|
777
|
+
const bearCase = parsed?.bearCase ?? "";
|
|
778
|
+
const executiveSummary = parsed?.executiveSummary ?? synthesisOutput.slice(0, 1000);
|
|
779
|
+
// Save report
|
|
780
|
+
const reportId = saveReport({
|
|
781
|
+
type: "investment-dd",
|
|
782
|
+
createdAt: new Date().toISOString(),
|
|
783
|
+
input: { code: project },
|
|
784
|
+
result: {
|
|
785
|
+
overallScore,
|
|
786
|
+
recommendation,
|
|
787
|
+
dimensions,
|
|
788
|
+
keyFindings,
|
|
789
|
+
redFlags,
|
|
790
|
+
bullCase,
|
|
791
|
+
bearCase,
|
|
792
|
+
executiveSummary,
|
|
793
|
+
},
|
|
794
|
+
riskScore: overallScore,
|
|
795
|
+
paid: gate.amountUsd > 0,
|
|
796
|
+
});
|
|
797
|
+
const urls = reportUrls(reportId);
|
|
798
|
+
// Free tier: show dimension scores + recommendation + red flag count + disclaimer
|
|
799
|
+
let responseBody;
|
|
800
|
+
if (gate.amountUsd > 0) {
|
|
801
|
+
responseBody = {
|
|
802
|
+
project,
|
|
803
|
+
projectType,
|
|
804
|
+
overallScore,
|
|
805
|
+
recommendation,
|
|
806
|
+
dimensions,
|
|
807
|
+
keyFindings,
|
|
808
|
+
redFlags,
|
|
809
|
+
bullCase,
|
|
810
|
+
bearCase,
|
|
811
|
+
executiveSummary,
|
|
812
|
+
disclaimer: DD_DISCLAIMER,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
// Extract just scores from dimensions
|
|
817
|
+
const dimScores = {};
|
|
818
|
+
for (const [key, val] of Object.entries(dimensions)) {
|
|
819
|
+
if (val && typeof val === "object" && "score" in val) {
|
|
820
|
+
dimScores[key] = val.score;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
responseBody = {
|
|
824
|
+
project,
|
|
825
|
+
projectType,
|
|
826
|
+
overallScore,
|
|
827
|
+
recommendation,
|
|
828
|
+
dimensionScores: dimScores,
|
|
829
|
+
redFlagCount: redFlags.length,
|
|
830
|
+
disclaimer: DD_DISCLAIMER,
|
|
831
|
+
_preview: true,
|
|
832
|
+
_message: `Score: ${overallScore}/100 (${recommendation}). ${redFlags.length} red flag(s). Pay $5.00 to see full report.`,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
const fullResponse = {
|
|
836
|
+
...responseBody,
|
|
837
|
+
...urls,
|
|
838
|
+
template: "InvestmentDD",
|
|
839
|
+
freeRemaining: gate.freeRemaining,
|
|
840
|
+
payment: {
|
|
841
|
+
amount: "5.00",
|
|
842
|
+
transaction: gate.transaction,
|
|
843
|
+
network: gate.network,
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
// Cache the full response
|
|
847
|
+
ddCache.set(cacheKey, fullResponse);
|
|
848
|
+
res.json(fullResponse);
|
|
849
|
+
}
|
|
850
|
+
catch (err) {
|
|
851
|
+
runtime.logger.error({ error: err instanceof Error ? err.message : String(err) }, "[x402/investment-dd] Execution failed");
|
|
852
|
+
res.status(500).json({ error: "Service temporarily unavailable" });
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
];
|
|
857
|
+
// ── Helper: score to recommendation mapping ─────────────────────────────
|
|
858
|
+
function scoreToRecommendation(score) {
|
|
859
|
+
if (score >= 80)
|
|
860
|
+
return "STRONG_BUY";
|
|
861
|
+
if (score >= 65)
|
|
862
|
+
return "BUY";
|
|
863
|
+
if (score >= 50)
|
|
864
|
+
return "HOLD";
|
|
865
|
+
if (score >= 35)
|
|
866
|
+
return "AVOID";
|
|
867
|
+
return "STRONG_AVOID";
|
|
868
|
+
}
|
|
869
|
+
//# sourceMappingURL=advancedRoutes.js.map
|